#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
#------------------------------------------------------------------------------
#
# PgBadger - An other PostgreSQL log analyzer that aims to replace and
#           outperforms pgFouine
#
# This program is open source, licensed under the PostgreSQL Licence.
# For license terms, see the LICENSE file.
#------------------------------------------------------------------------------
#
# You must enable SQL query logging : log_min_duration_statement = 0
# Log line prefix should be : log_line_prefix = '%t [%p]: [%l-1] '
# Log line prefix should be : log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d '
# Log line prefix should be : log_line_prefix = '%t [%p]: [%l-1] db=%d,user=%u '
#
# Additional informations that could be collected and reported
#
# log_checkpoints = on
# log_connections = on
# log_disconnections = on
# log_lock_waits = on
# log_temp_files = 0
#------------------------------------------------------------------------------
use vars qw($VERSION);

use strict;
use Getopt::Long qw(:config no_ignore_case bundling);
use IO::File;
use Benchmark;
use File::Basename;
use Time::Local 'timegm_nocheck';
use POSIX qw(setlocale LC_NUMERIC);
setlocale LC_NUMERIC, "";

$VERSION = '2.0';

$| = 1;

# Global variables overriden during install
my $JQGRAPH = 1;

# Command line options
my $zcat                = 'zcat';
my $format              = '';
my $outfile             = '';
my $outdir              = '';
my $help                = '';
my $ver                 = '';
my $dbname              = '';
my $dbuser              = '';
my $ident               = '';
my $top                 = 0;
my $sample              = 0;
my $extension           = '';
my $maxlength           = 0;
my $graph               = 1;
my $nograph             = 0;
my $debug               = 0;
my $nohighlight         = 0;
my $noprettify          = 0;
my $from                = '';
my $to                  = '';
my $regex_prefix_dbname = '';
my $regex_prefix_dbuser = '';
my $quiet               = 0;
my $progress            = 1;
my $error_only          = 0;
my @exclude_query       = ();
my $exclude_file        = '';
my $disable_error       = 0;
my $disable_hourly      = 0;
my $disable_type        = 0;
my $disable_query       = 0;
my $disable_session     = 0;
my $disable_connection  = 0;
my $disable_lock        = 0;
my $disable_temporary   = 0;
my $disable_checkpoint  = 0;
my $avg_minutes         = 5;
my $last_parsed         = '';
my $report_title        = 'PgBadger: PostgreSQL log analyzer';

my $NUMPROGRESS = 10000;
my @DIMENSIONS  = (800, 300);
my $RESRC_URL   = '';
my $img_format  = 'png';
my @log_files   = ();

# Do not display data in pie where percentage is lower than this value
# to avoid label overlaping.
my $pie_percentage_limit = 2;

# Get the decimal separator
my $n       = 5 / 2;
my $num_sep = ',';
$num_sep = ' ' if ($n =~ /,/);

# get the command line parameters
my $result = GetOptions(
	"a|average=i"         => \$avg_minutes,
	"b|begin=s"           => \$from,
	"d|dbname=s"          => \$dbname,
	"e|end=s"             => \$to,
	"f|format=s"          => \$format,
	"G|nograph!"          => \$nograph,
	"h|help!"             => \$help,
	"i|ident=s"           => \$ident,
	"l|last-parsed=s"     => \$last_parsed,
	"m|maxlength=i"       => \$maxlength,
	"n|nohighlight!"      => \$nohighlight,
	"o|outfile=s"         => \$outfile,
	"P|no-prettify!"      => \$noprettify,
	"q|quiet!"            => \$quiet,
	"s|sample=i"          => \$sample,
	"t|top=i"             => \$top,
	"T|title=s"           => \$report_title,
	"u|dbuser=s"          => \$dbuser,
	"v|verbose!"          => \$debug,
	"V|version!"          => \$ver,
	"w|watch-mode!"       => \$error_only,
	"x|extension=s"       => \$extension,
	"z|zcat=s"            => \$zcat,
	"regex-db=s"          => \$regex_prefix_dbname,
	"regex-user=s"        => \$regex_prefix_dbuser,
	"pie-limit=i"         => \$pie_percentage_limit,
	"image-format=s"      => \$img_format,
	"exclude-query=s"     => \@exclude_query,
	"exclude-file=s"      => \$exclude_file,
	"disable-error!"      => \$disable_error,
	"disable-hourly!"     => \$disable_hourly,
	"disable-type!"       => \$disable_type,
	"disable-query!"      => \$disable_query,
	"disable-session!"    => \$disable_session,
	"disable-connection!" => \$disable_connection,
	"disable-lock!"       => \$disable_lock,
	"disable-temporary!"  => \$disable_temporary,
	"disable-checkpoint!" => \$disable_checkpoint,
);

if ($ver) {
	print "pgBadger version $VERSION\n";
	exit 0;
}
&usage() if ($help);

# Log file to be parsed are passed as command line argument
if ($#ARGV >= 0) {
	foreach my $file (@ARGV) {
		if ($file ne '-') {
			die "FATAL: logfile $file must exist!\n" if (!-f $file);
			if (-z $file) {
				print "WARNING: file $file is empty\n";
				next;
			}
		}
		push(@log_files, $file);
	}
}

# Logfile is a mandatory parameter
if ($#log_files < 0) {
	print STDERR "FATAL: you must give a log file as command line parameter.\n\n";
	&usage();
}

# Quiet mode is forced with progress bar
$progress = 0 if ($quiet);

# Set the default number minutes for queries and connections average
$avg_minutes ||= 5;
$avg_minutes = 60 if ($avg_minutes > 60);
$avg_minutes = 1  if ($avg_minutes < 1);

# Set default format
$format ||= &autodetect_format($log_files[0]);

# Set default top query
$top ||= 20;

# Set the default number of samples
$sample ||= 3;

# Set the default extension and output format
if (!$extension) {
	if ($outfile =~ /\.htm[l]*/i) {
		$extension = 'html';
	} elsif ($outfile) {
		$extension = 'txt';
	} else {
		$extension = 'html';
	}
}

# Set default filename of the output file
$outfile ||= 'out.' . $extension;
&logmsg('DEBUG', "Output '$extension' reports will be written to $outfile");

# Set default syslog ident name
$ident ||= 'postgres';

# Set default pie percentage limit or fix value
$pie_percentage_limit = 0   if ($pie_percentage_limit < 0);
$pie_percentage_limit = 2   if ($pie_percentage_limit eq '');
$pie_percentage_limit = 100 if ($pie_percentage_limit > 100);

# Set default download image format
$img_format = lc($img_format);
$img_format = 'jpeg' if ($img_format eq 'jpg');
$img_format = 'png' if ($img_format ne 'jpeg');

# Extract the output directory from outfile so that graphs will
# be created in the same directoty
my @infs = fileparse($outfile);
$outdir = $infs[1] . '/';

# Remove graph support if output is not html
$graph = 0 if ($extension ne 'html');
$graph = 0 if ($nograph);

my $end_top = $top - 1;

# Test file creation before going to parse log
my $tmpfh = new IO::File ">$outfile";
if (not defined $tmpfh) {
	die "FATAL: can't write to $outfile, $!\n";
}
$tmpfh->close();
unlink($outfile) if (-e $outfile);

# -w and --disable-error can't go together
if ($error_only && $disable_error) {
	die "FATAL: please choose between no error report and reporting errors only.\n";
}

# Set default search pattern for database and user name in log_line_prefix
$regex_prefix_dbname = 'db=([^,]*)'   if (!$regex_prefix_dbname);
$regex_prefix_dbuser = 'user=([^,]*)' if (!$regex_prefix_dbuser);

# Loading excluded query from file if any
if ($exclude_file) {
	open(IN, "$exclude_file") or die "FATAL: can't read file $exclude_file: $!\n";
	my @exclq = <IN>;
	close(IN);
	chomp(@exclq);
	map {s/
//;} @exclq;
	foreach my $r (@exclq) {
		&check_regex($r, '--exclude-file');
	}
	push(@exclude_query, @exclq);
}

# Testing regex syntaxe
if ($#exclude_query >= 0) {
	foreach my $r (@exclude_query) {
		&check_regex($r, '--exclude-query');
	}
}
&check_regex($regex_prefix_dbname, '--regex-db');
&check_regex($regex_prefix_dbuser, '--regex-user');

sub check_regex
{
	my ($pattern, $varname) = @_;

	eval {m/$pattern/i;};
	if ($@) {
		die "FATAL: '$varname' invalid regex '$pattern', $!\n";
	}
}

# Check start/end date time
if ($from) {
	if ($from =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
		$from = "$1$2$3$4$5$6";
	} elsif ($from =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
		$from = "$1$2$3" . "000000";
	} else {
		die "FATAL: bad format for begin datetime, shoud be yyyy-mm-dd hh:mm:ss\n";
	}
}
if ($to) {
	if ($to =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
		$to = "$1$2$3$4$5$6";
	} elsif ($to =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
		$to = "$1$2$3" . "000000";
	} else {
		die "FATAL: bad format for ending datetime, shoud be yyyy-mm-dd hh:mm:ss\n";
	}
}

# Stores the last parsed line from log file to allow incremental parsing
my $LAST_LINE = '';

# Set the level of the data aggregator, can be minute, hour or day follow the
# size of the log file.
my $LEVEL = 'hour';

# Month names
my %month_abbr = (
	'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
	'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12'
);
my %abbr_month = (
	'01' => 'Jan', '02' => 'Feb', '03' => 'Mar', '04' => 'Apr', '05' => 'May', '06' => 'Jun',
	'07' => 'Jul', '08' => 'Aug', '09' => 'Sep', '10' => 'Oct', '11' => 'Nov', '12' => 'Dec'
);

# Highlight variables
my @KEYWORDS1 = (
	'ALL',     'ASC',    'AS',   'ALTER', 'AND',     'ADD', 'AUTO_INCREMENT', 'ANY', 'ANALYZE',
	'BETWEEN', 'BINARY', 'BOTH', 'BY',    'BOOLEAN', 'BEGIN',
	'CHANGE',    'CHECK',    'COLUMNS', 'COLUMN',  'CROSS',    'CREATE', 'CASE',     'COMMIT', 'COALESCE', 'CLUSTER', 'COPY',
	'DATABASES', 'DATABASE', 'DATA',    'DELAYED', 'DESCRIBE', 'DESC',   'DISTINCT', 'DELETE', 'DROP',     'DEFAULT',
	'ENCLOSED',  'ESCAPED',  'EXISTS',  'EXPLAIN', 'ELSE',     'END',    'EXCEPT',
	'FIELDS', 'FIELD', 'FLUSH', 'FOR', 'FOREIGN', 'FUNCTION', 'FROM', 'FULL',
	'GROUP',  'GRANT', 'GREATEST',
	'HAVING',
	'IGNORE', 'INDEX', 'INFILE', 'INSERT', 'INNER', 'INTO', 'IDENTIFIED', 'IN', 'IS', 'IF', 'INTERSECT', 'INHERIT',
	'JOIN',
	'KEYS', 'KILL', 'KEY',
	'LEADING', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOW_PRIORITY', 'LEFT', 'LANGUAGE', 'LEAST', 'LOGIN',
	'MODIFY',
	'NATURAL', 'NOT', 'NULL', 'NEXTVAL', 'NULLIF', 'NOSUPERUSER', 'NOCREATEDB', 'NOCREATEROLE',
	'OPTIMIZE',  'OPTION',     'OPTIONALLY', 'ORDER', 'OUTFILE', 'OR', 'OUTER', 'ON', 'OVERLAPS', 'OWNER',
	'PROCEDURE', 'PROCEDURAL', 'PRIMARY',
	'READ', 'REFERENCES', 'REGEXP', 'RENAME',        'REPLACE', 'RETURN', 'REVOKE', 'RLIKE', 'RIGHT', 'ROLE', 'ROLLBACK',
	'SHOW', 'SONAME',     'STATUS', 'STRAIGHT_JOIN', 'SELECT',  'SETVAL', 'SET',    'SOME',  'SEQUENCE',
	'TABLES', 'TEMINATED', 'TO',    'TRAILING', 'TRUNCATE', 'TABLE',    'TEMPORARY', 'TRIGGER', 'TRUSTED', 'THEN',
	'UNIQUE', 'UNLOCK',    'USE',   'USING',    'UPDATE',   'UNSIGNED', 'UNION',
	'VALUES', 'VARIABLES', 'VIEW',  'VACUUM',   'VERBOSE',
	'WITH',   'WRITE',     'WHERE', 'WHEN',
	'ZEROFILL',
	'XOR',
);
my @KEYWORDS2 = (
	'ascii',      'age',
	'bit_length', 'btrim',
	'char_length', 'character_length', 'convert', 'chr', 'current_date', 'current_time', 'current_timestamp', 'count',
	'decode',      'date_part',        'date_trunc',
	'encode',      'extract',
	'get_byte',    'get_bit',
	'initcap',       'isfinite', 'interval',
	'justify_hours', 'justify_days',
	'lower', 'length', 'lpad', 'ltrim', 'localtime', 'localtimestamp',
	'md5',
	'now',
	'octet_length', 'overlay',
	'position',     'pg_client_encoding',
	'quote_ident',  'quote_literal',
	'repeat', 'replace', 'rpad', 'rtrim',
	'substring', 'split_part', 'strpos', 'substr', 'set_byte', 'set_bit',
	'trim', 'to_ascii', 'to_hex', 'translate', 'to_char', 'to_date', 'to_timestamp', 'to_number', 'timeofday',
	'upper',
);
my @KEYWORDS3 = ('STDIN', 'STDOUT');
my %SYMBOLS = (
	'='  => '=', '<'  => '&lt;', '>' => '&gt;', '\|' => '|', ',' => ',', '\.' => '.', '\+' => '+', '\-' => '-', '\*' => '*',
	'\/' => '/', '!=' => '!='
);
my @BRACKETS = ('(', ')');
map {$_ = quotemeta($_)} @BRACKETS;

# Where statistic are stored
my %STATS           = ();
my $first_log_date  = '';
my $last_log_date   = '';
my %overall_stat    = ();
my @top_slowest     = ();
my %normalyzed_info = ();
my %error_info      = ();
my %logs_type       = ();
my %per_hour_info   = ();
my %per_minute_info = ();
my %lock_info       = ();
my %tempfile_info   = ();
my %connection_info = ();
my %session_info    = ();
my %conn_received   = ();
my %checkpoint_info = ();
my @graph_values    = ();
my %cur_info        = ();
my $nlines          = 0;
my %last_line       = ();
my %saved_last_line = ();

my $t0 = Benchmark->new;

# Reading last line parsed
if ($last_parsed && -e $last_parsed) {
	if (open(IN, "$last_parsed")) {
		my $line = <IN>;
		close(IN);
		($saved_last_line{datetime}, $saved_last_line{orig}) = split(/\t/, $line, 2);
	} else {
		die "FATAL: can't read last parsed line from $last_parsed, $!\n";
	}
}

# Main loop reading log files
foreach my $logfile (@log_files) {
	&logmsg('DEBUG', "Starting to parse log file: $logfile");

	my $curdate = localtime(time);

	# Syslog do not have year information, so take care of year overlapping
	my ($gsec, $gmin, $ghour, $gmday, $gmon, $gyear, $gwday, $gyday, $gisdst) = localtime(time);
	$gyear += 1900;
	my $CURRENT_DATE = $gyear . sprintf("%02d", $gmon + 1) . sprintf("%02d", $gmday);

	# Get size of the file
	my $totalsize = (stat("$logfile"))[7] || 0;
	my $cursize = 0;

	if ($format eq 'csv') {
		require Text::CSV;
		my $csv = Text::CSV->new({binary => 1, eol => $/});
		open(my $io, "<", $logfile) or die "FATAL: cannot read csvlog file $logfile. $!\n";

		# Parse csvlog lines
		while (my $row = $csv->getline($io)) {

			# Set progress statistics
			$cursize += length(join(',', @$row));
			$nlines++;
			if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
				if ($totalsize) {
					print progress_bar($cursize, $totalsize, 25, '=');
				} else {
					print ".";
				}
			}

			# Extract the date
			$row->[0] =~ m/^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)\.(\d+)/;
			my @date = ($1, $2, $3, $4, $5, $6);
			my $cur_date = join('', @date);
			my $end_time = timegm_nocheck($6, $5, $4, $3, $2, $1);
			my $milli = $7 || 0;
			$row->[8] =~ m/^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)/;
			my $start_time = timegm_nocheck($6, $5, $4, $3, $2, $1);
			my $duration = (($end_time - $start_time) * 1000) - $milli;
			$duration = 0 if ($duration < 0);

			# Skip unwanted lines
			next if ($from && ($from > $cur_date));
			last if ($to   && ($to < $cur_date));

			# Jump to the last line parsed if required
			next if (!&check_incremental_position($cur_date, join(',', @$row)));

			# Set approximative session duration
			if ($row->[13] && ($row->[13] !~ m/duration: \d+\.\d+ ms/)) {
				$row->[13] = "duration: $duration ms $row->[13]";
			}

			# Process the log line
			&parse_query(
				@date,
				$row->[4],                                   # connection from
				$row->[3],                                   # pid
				$row->[5],                                   # session
				'user=' . $row->[1] . ',db=' . $row->[2],    # logprefix
				$row->[11],                                  # loglevel
				$row->[13] || $row->[14] || $row->[15],      #  message || detail || hint
			);
		}
		$csv->eof or die "FATAL: cannot use CSV, " . $csv->error_diag() . "\n";
		close $io;
	} else {

		# Open log file for reading
		my $lfile = new IO::File;
		if ($logfile !~ /\.gz/) {
			$lfile->open($logfile) || die "FATAL: cannot read log file $logfile. $!\n";
		} else {

			# Open a pipe to zcat program for compressed log
			$lfile->open("$zcat $logfile |") || die "FATAL: cannot read from pipe to $zcat $logfile. $!\n";

			# Real size of the file is unknow
			$totalsize = 0;
		}

		my $cur_pid = '';
		while (my $line = <$lfile>) {
			$cursize += length($line);
			chomp($line);
			$line =~ s/
//;
			$nlines++;
			next if (!$line);

			if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
				if ($totalsize) {
					print progress_bar($cursize, $totalsize, 25, '=');
				} else {
					print ".";
				}
			}

			# Parse syslog lines
			if ($format eq 'syslog') {

				if ($line =~
					/^(...)\s+(\d+)\s(\d+):(\d+):(\d+)\s([^\s]+)\s([^\[]+)\[(\d+)\]:\s\[([0-9\-]+)\]\s*([^:]*?)\s*([A-Z]+?):\s+(.*)/)
				{

					# skip non postgresql lines
					next if ($7 ne $ident);

					# Syslog do not have year information, so take care of year overlapping
					my $tmp_year = $gyear;
					if ("$tmp_year$month_abbr{$1}$2" > $CURRENT_DATE) {
						$tmp_year = substr($CURRENT_DATE, 1, 4) - 1;
					}

					# Fix single digit days
					my $day = sprintf("%02d", $2);
					my $cur_date = "$tmp_year$month_abbr{$1}$day$3$4$5";

					# Skip unwanted lines
					next if ($from && ($from > $cur_date));
					last if ($to   && ($to < $cur_date));
					$cur_pid = $8;

					# Jump to the last line parsed if required
					next if (!&check_incremental_position($cur_date, $line));

					# Process the log line
					&parse_query($tmp_year, $month_abbr{$1}, $day, $3, $4, $5, $6, $8, $9, $10, $11, $12);

				} elsif ($line =~ /^(...)\s+(\d+)\s(\d+):(\d+):(\d+)\s([^\s]+)\s([^\[]+)\[(\d+)\]:\s\[([0-9\-]+)\]\s*(.*)/) {

					$cur_pid = $8;
					my $t_query = $10;
					$t_query =~ s/#011/\t/g;
					if ($cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $t_query;
					} elsif ($cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $t_query;
					} elsif ($cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $t_query;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $t_query;
					}

					# Collect orphans lines of multiline queries
				} elsif ($line !~ /^...\s+\d+\s\d+:\d+:\d+\s[^\s]+\s[^\[]+\[\d+\]:/) {

					if ($cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $line;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $line;
					}

				} else {
					&logmsg('DEBUG', "Unknown syslog line format: $line");
				}

			} elsif ($format eq 'stderr') {

				# Parse stderr lines
				if ($line =~
					/(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)\s([^\s]+)\s\[(\d+)\]:\s\[([0-9\-]+)\]\s*([^:]*?)\s*([A-Z]+?):\s+(.*)/)
				{

					# Skip unwanted lines
					my $cur_date = "$1$2$3$4$5$6";
					next if ($from && ($from > $cur_date));
					last if ($to   && ($to < $cur_date));
					$cur_pid = $8;

					# Jump to the last line parsed if required
					next if (!&check_incremental_position($cur_date, $line));

					# Process the log line
					&parse_query($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);

					# Collect orphans lines of multiline queries
				} elsif ($line !~ /^\d+-\d+-\d+\s\d+:\d+:\d+\s[^\s]+\s\[\d+\]:/) {

					if ($cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $line;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $line;
					}

				} else {
					$cur_info{$cur_pid}{query} .= "\n" . $line if ($cur_info{$cur_pid}{query});
				}

			} else {

				# unknown format
				&logmsg('DEBUG', "Unknown line format: $line");
			}
		}
		$lfile->close();
	}

	# Get stats from all pending temporary storage
	foreach my $pid (sort {$cur_info{$a}{date} <=> $cur_info{$b}{date}} %cur_info) {
		&store_queries($pid);
	}
	%cur_info = ();

	if ($progress) {
		if ($totalsize) {
			print progress_bar($cursize, $totalsize, 25, '=');
		}
		print STDERR "\n";
	}

}

# Save last line parsed
if ($last_parsed && scalar keys %last_line) {
	if (open(OUT, ">$last_parsed")) {
		print OUT "$last_line{datetime}\t$last_line{orig}\n";
		close(OUT);
	} else {
		&logmsg('ERROR', "can't save last parsed line into $last_parsed, $!");
	}
}

my $t1 = Benchmark->new;
my $td = timediff($t1, $t0);
&logmsg('DEBUG', "the log statistics gathering tooks:" . timestr($td));

&logmsg('DEBUG', "Ok, generating $extension report...");

# Open filehandle
my $fh = new IO::File ">$outfile";
if (not defined $fh) {
	die "FATAL: can't write to $outfile, $!\n";
}
if (($extension eq 'text') || ($extension eq 'txt')) {
	if ($error_only) {
		&dump_error_as_text();
	} else {
		&dump_as_text();
	}
} else {
	if ($error_only) {
		&dump_error_as_html();
	} else {
		&dump_as_html();
	}
}
$fh->close;

my $t2 = Benchmark->new;
$td = timediff($t2, $t1);
&logmsg('DEBUG', "the generating of reports tooks:" . timestr($td));
$td = timediff($t2, $t0);
&logmsg('DEBUG', "the total execution time tooks:" . timestr($td));

exit 0;

#-------------------------------------------------------------------------------

# Show PgBadger command line usage
sub usage
{
	print qq{
Usage: pgbadger [options] logfile [...]

	PostgreSQL log analyzer with fully detailed reports and graphs.

Arguments:²

    logfile can be a single log file, a list of files or a shell command 
    returning a list of file. If you want to pass log content from stdin
    use - as filename.

Options:

    -a | --average minutes : number of minutes to build the average graphs of
			     queries and connections.
    -b | --begin datetime  : start date/time for the data to be parsed in log.
    -d | --dbname database : only report what concern the given database
    -e | --end datetime    : end date/time for the data to be parsed in log.
    -f | --format logtype  : possible values: syslog,stderr,csv. Default: stderr
    -G | --nograph         : disable graphs on HTML output. Enable by default.
    -h | --help            : show this message and exit.
    -i | --ident name      : programname used as syslog ident. Default: postgres
    -l | --last-parsed file: allow incremental log parsing by registering the
			     last datetime and line parsed. Useful if you want
			     to watch errors since last run or if you want one
			     report per day with a log rotated each week.
    -m | --maxlength size  : maximum length of a query, it will be cutted above
			     the given size. Default: no truncate
    -o | --outfile filename: define the filename for the output. Default depends
			     of the output format: out.html or out.txt. To dump
			     output to stdout use - as filename.
    -P | --no-prettify     : disable SQL queries prettify formatter.
    -q | --quiet           : don't print anything to stdout, even not a progress bar.
    -s | --sample number   : number of query sample to store/display. Default: 3
    -t | --top number      : number of query to store/display. Default: 20
    -T | --title string    : change title of the HTML page report.
    -u | --dbuser username : only report what concern the given user
    -v | --verbose         : enable verbose or debug mode. Disabled by default.
    -V | --version         : show pgBadger version and exit.
    -w | --watch-mode      : only report errors just like logwatch could do.
    -x | --extension       : output format. Values: text or html. Default: html
    -z | --zcat exec_path  : set the full path to the zcat program. Use it if
			     zcat is not on your path or you want to use gzcat.
    --pie-limit num        : pie data lower than num% will show a sum instead.
    --exclude-query regex  : any query matching the given regex will be excluded
			     from the report. For example: "^(VACUUM|COMMIT)"
			     you can use this option multiple time.
    --exclude_file filename: path of the file which contains all the regex to use
			     to exclude queries from the report. One regex per line.
    --disable-error        : do not generate error report.
    --disable-hourly       : do not generate hourly reports.
    --disable-type         : do not generate query type report.
    --disable-query        : do not generate queries reports (slowest, most
			     frequent, ...).
    --disable-session      : do not generate session report.
    --disable-connection   : do not generate connection report.
    --disable-lock         : do not generate lock report.
    --disable-temporary    : do not generate temporary report.
    --disable-checkpoint   : do not generate checkpoint report.

Examples:

	pgbadger /var/log/postgresql.log
	pgbadger /var/log/postgres.log.2.gz /var/log/postgres.log.1.gz \
		       /var/log/postgres.log
	pgbadger /var/log/postgresql/postgresql-2012-05-*
	pgbadger --exclude-query="^(COPY|COMMIT)" /var/log/postgresql.log
	cat /var/log/postgres.log | pgbadger -

Reporting errors every week by cron job:

    30 23 * * 1 /usr/bin/pgbadger -q -w /var/log/postgresql.log -o /var/reports/pg_errors.html

This supposes that your log file and HTML report are also rotated every weeks.

};

	exit 0;
}

sub check_incremental_position
{
	my ($cur_date, $line) = @_;

	if ($last_parsed) {
		if ($saved_last_line{datetime}) {
			if ($cur_date < $saved_last_line{datetime}) {
				return 0;
			} elsif (!$last_line{datetime} && ($cur_date == $saved_last_line{datetime})) {
				return 0 if ($line ne $saved_last_line{orig});
			}
		}
		$last_line{datetime} = $cur_date;
		$last_line{orig}     = $line;
	}

	return 1;
}

# Display message following the log level
sub logmsg
{
	my ($level, $str) = @_;

	return if ($quiet && ($level ne 'FATAL'));
	return if (!$debug && ($level eq 'DEBUG'));

	if ($level =~ /(\d+)/) {
		print STDERR "\t" x $1;
	}

	print STDERR "$level: $str\n";
}

# Normalyze SQL queries by removing parameters
sub normalize_query
{
	my $orig_query = shift;

	return if (!$orig_query);

	$orig_query = lc($orig_query);

	# Remove extra space, new line and tab caracters by a single space
	$orig_query =~ s/[\t\s\r\n]+/ /gs;

	# Remove string content
	$orig_query =~ s/\\'//g;
	$orig_query =~ s/'[^']*'/''/g;
	$orig_query =~ s/''('')+/''/g;

	# Remove NULL parameters
	$orig_query =~ s/=\s*NULL/=''/g;

	# Remove numbers
	$orig_query =~ s/([^a-z_\$-])-?([0-9]+)/${1}0/g;

	# Remove hexadecimal numbers
	$orig_query =~ s/([^a-z_\$-])0x[0-9a-f]{1,10}/${1}0x/g;

	# Remove IN values
	$orig_query =~ s/in\s*\([\'0x,\s]*\)/in (...)/g;

	return $orig_query;
}

# Format numbers with comma for better reading
sub comma_numbers
{
	return 0 if ($#_ < 0);

	my $text = reverse $_[0];

	$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1$num_sep/g;

	return scalar reverse $text;
}

# Format duration
sub convert_time
{
	my $time = shift;

	my $days = int($time / 86400000);
	$time -= ($days * 86400000);
	my $hours = int($time / 3600000);
	$time -= ($hours * 3600000);
	my $minutes = int($time / 60000);
	$time -= ($minutes * 60000);
	my $seconds = sprintf("%0.2f", $time / 1000);

	$days    = $days < 1    ? '' : $days . 'd';
	$hours   = $hours < 1   ? '' : $hours . 'h';
	$minutes = $minutes < 1 ? '' : $minutes . 'm';
	$time    = $days . $hours . $minutes . $seconds . 's';

	return $time;
}

# Stores the top N slowest queries
sub set_top_slowest
{
	my ($q, $dt, $date) = @_;

	push(@top_slowest, [($dt, $date, $q)]);

	@top_slowest = (sort {$b->[0] <=> $a->[0]} @top_slowest)[0 .. $end_top];

}

# Stores top N slowest sample queries
sub set_top_sample
{
	my ($norm, $q, $dt, $date) = @_;

	$normalyzed_info{$norm}{samples}{$dt}{query} = $q;
	$normalyzed_info{$norm}{samples}{$dt}{date}  = $date;

	my $i = 1;
	foreach my $k (sort {$b <=> $a} keys %{$normalyzed_info{$norm}{samples}}) {
		if ($i > $sample) {
			delete $normalyzed_info{$norm}{samples}{$k};
		}
		$i++;
	}
}

# Stores top N error sample queries
sub set_top_error_sample
{
	my ($q, $date, $real_error, $detail, $context, $statement, $hint) = @_;

	# Stop when we have our number of samples
	if (!exists $error_info{$q}{date} || ($#{$error_info{$q}{date}} < $sample)) {
		if (($q =~ /deadlock detected/) || !grep(/\Q$real_error\E/, @{$error_info{$q}{error}})) {
			push(@{$error_info{$q}{date}},      $date);
			push(@{$error_info{$q}{detail}},    $detail);
			push(@{$error_info{$q}{context}},   $context);
			push(@{$error_info{$q}{statement}}, $statement);
			push(@{$error_info{$q}{hint}},      $hint);
			push(@{$error_info{$q}{error}},     $real_error);
		}
	}
}

sub dump_as_text
{

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
$report_title

- Global informations --------------------------------------------------

Generated on $curdate
Log file: $logfile_str
Parsed $fmt_nlines log entries in $total_time
Log start from $first_log_date to $last_log_date
};

	# Overall statistics
	my $fmt_unique   = &comma_numbers(scalar keys %normalyzed_info)     || 0;
	my $fmt_queries  = &comma_numbers($overall_stat{'queries_number'})  || 0;
	my $fmt_duration = &convert_time($overall_stat{'queries_duration'}) || 0;
	print $fh qq{

- Overall statistics ---------------------------------------------------

Number of unique normalized queries: $fmt_unique
Number of queries: $fmt_queries
Total query duration: $fmt_duration
First query: $overall_stat{'first_query'}
Last query: $overall_stat{'last_query'}
};
	foreach (sort {$overall_stat{'query_peak'}{$b} <=> $overall_stat{'query_peak'}{$a}} keys %{$overall_stat{'query_peak'}}) {
		print $fh "Query peak: ", &comma_numbers($overall_stat{'query_peak'}{$_}), " queries/s at $_";
		last;
	}
	if (!$disable_error) {
		my $fmt_errors = &comma_numbers($overall_stat{'errors_number'}) || 0;
		my $fmt_unique_error = &comma_numbers(scalar keys %{$overall_stat{'unique_normalized_errors'}}) || 0;
		print $fh qq{
Number of errors: $fmt_errors
Number of unique normalized errors: $fmt_unique_error
};
	}
	if ($tempfile_info{count}) {
		my $fmt_temp_maxsise = &comma_numbers($tempfile_info{maxsize}) || 0;
		my $fmt_temp_avsize = &comma_numbers(sprintf("%.2f", ($tempfile_info{maxsize} / $tempfile_info{count})));
		print $fh qq{Number temporary file: $tempfile_info{count}
Max size of temporary file: $fmt_temp_maxsise
Average size of temporary file: $fmt_temp_avsize
};
	}
	if (!$disable_session && $session_info{count}) {
		my $avg_session_duration = &convert_time($session_info{duration} / $session_info{count});
		my $tot_session_duration = &convert_time($session_info{duration});
		print $fh qq{Total number of sessions: $session_info{count}
Total duration of sessions: $tot_session_duration
Average duration of sessions: $avg_session_duration
};
	}
	if (!$disable_connection && $connection_info{count}) {
		print $fh "Total number of connections: $connection_info{count}\n";
	}
	if (!$disable_hourly) {
		print $fh qq{

- Hourly statistics ----------------------------------------------------

Report not supported by text format

};
	}

	if (!$disable_type) {

		# INSERT/DELETE/UPDATE/SELECT repartition
		my $totala = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
		my $total  = $overall_stat{'queries_number'};
		print $fh "\n- Queries by type ------------------------------------------------------\n\n";
		print $fh "Type     Count     Percentage\n";
		print $fh "SELECT: ", &comma_numbers($overall_stat{'SELECT'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'SELECT'} * 100) / $total), "%\n";
		print $fh "INSERT: ", &comma_numbers($overall_stat{'INSERT'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'INSERT'} * 100) / $total), "%\n";
		print $fh "UPDATE: ", &comma_numbers($overall_stat{'UPDATE'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'UPDATE'} * 100) / $total), "%\n";
		print $fh "DELETE: ", &comma_numbers($overall_stat{'DELETE'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'DELETE'} * 100) / $total), "%\n";
		print $fh "OTHERS: ", &comma_numbers($total - $totala) || 0, " ", sprintf("%0.2f", (($total - $totala) * 100) / $total), "%\n"
			if (($total - $totala) > 0);
		print $fh "\n";
	}

	if (!$disable_lock && scalar keys %lock_info > 0) {
		print $fh "\n- Locks by type ------------------------------------------------------\n\n";
		print $fh "Type     Object     Count     Total Duration     Av. duration (s)\n";
		my $total_count    = 0;
		my $total_duration = 0;
		foreach my $t (sort keys %lock_info) {
			print $fh "$t\t\t", &comma_numbers($lock_info{$t}{count}), " ", &convert_time($lock_info{$t}{duration}), " ",
				&convert_time($lock_info{$t}{duration} / $lock_info{$t}{count}), "\n";
			foreach my $o (sort keys %{$lock_info{$t}}) {
				next if (($o eq 'count') || ($o eq 'duration') || ($o eq 'chronos'));
				print $fh "\t$o\t", &comma_numbers($lock_info{$t}{$o}{count}), " ", &convert_time($lock_info{$t}{$o}{duration}), " ",
					&convert_time($lock_info{$t}{$o}{duration} / $lock_info{$t}{$o}{count}), "\n";
			}
			$total_count    += $lock_info{$t}{count};
			$total_duration += $lock_info{$t}{duration};
		}
		print $fh "Total:\t\t\t", &comma_numbers($total_count), " ", &convert_time($total_duration), " ",
			&convert_time($total_duration / ($total_count || 1)), "\n";
	}

	# Show session per database statistics
	if (!$disable_session && exists $session_info{database}) {
		print $fh "\n- Sessions per database ------------------------------------------------------\n\n";
		print $fh "Database     Count     Total Duration     Av. duration (s)\n";
		foreach my $d (sort keys %{$session_info{database}}) {
			print $fh "$d - ", &comma_numbers($session_info{database}{$d}{count}), " ",
				&convert_time($session_info{database}{$d}{duration}), " ",
				&convert_time($session_info{database}{$d}{duration} / $session_info{database}{$d}{count}), "\n";
		}
	}

	# Show session per user statistics
	if (!$disable_session && exists $session_info{user}) {
		print $fh "\n- Sessions per user ------------------------------------------------------\n\n";
		print $fh "User     Count     Total Duration     Av. duration (s)\n";
		foreach my $d (sort keys %{$session_info{user}}) {
			print $fh "$d - ", &comma_numbers($session_info{user}{$d}{count}), " ", &convert_time($session_info{user}{$d}{duration}),
				" ", &convert_time($session_info{user}{$d}{duration} / $session_info{user}{$d}{count}), "\n";
		}
	}

	# Show session per host statistics
	if (!$disable_session && exists $session_info{host}) {
		print $fh "\n- Sessions per host ------------------------------------------------------\n\n";
		print $fh "User     Count     Total Duration     Av. duration (s)\n";
		foreach my $d (sort keys %{$session_info{host}}) {
			print $fh "$d - ", &comma_numbers($session_info{host}{$d}{count}), " ", &convert_time($session_info{host}{$d}{duration}),
				" ", &convert_time($session_info{host}{$d}{duration} / $session_info{host}{$d}{count}), "\n";
		}
	}

	# Show connection per database statistics
	if (!$disable_connection && exists $connection_info{database}) {
		print $fh "\n- Connections per database ------------------------------------------------------\n\n";
		print $fh "Database     User     Count\n";
		foreach my $d (sort keys %{$connection_info{database}}) {
			print $fh "$d - ", &comma_numbers($connection_info{database}{$d}), "\n";
			foreach my $u (sort keys %{$connection_info{user}}) {
				next if (!exists $connection_info{database_user}{$d}{$u});
				print $fh "\t$u ", &comma_numbers($connection_info{database_user}{$d}{$u}), "\n";
			}
		}
	}

	# Show connection per user statistics
	if (!$disable_connection && exists $connection_info{user}) {
		print $fh "\n- Connections per user ------------------------------------------------------\n\n";
		print $fh "User     Count\n";
		foreach my $d (sort keys %{$connection_info{user}}) {
			print $fh "$d - ", &comma_numbers($connection_info{user}{$d}), "\n";
		}
	}

	# Show connection per host statistics
	if (!$disable_connection && exists $connection_info{host}) {
		print $fh "\n- Connections per host ------------------------------------------------------\n\n";
		print $fh "User     Count\n";
		foreach my $d (sort keys %{$connection_info{host}}) {
			print $fh "$d - ", &comma_numbers($connection_info{host}{$d}), "\n";
		}
	}

	# Show top informations
	if (!$disable_query) {
		print $fh "\n- Slowest queries ------------------------------------------------------\n\n";
		print $fh "Rank     Duration (s)     Query\n";
		for (my $i = 0 ; $i <= $#top_slowest ; $i++) {
			print $fh $i + 1, ") " . &convert_time($top_slowest[$i]->[0]) . " - $top_slowest[$i]->[2]\n";
			print $fh "--\n";
		}
		@top_slowest = ();

		print $fh "\n- Queries that took up the most time (N) -------------------------------\n\n";
		print $fh "Rank     Total duration      Times executed     Av. duration (s)     Query\n";
		my $idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{duration} <=> $normalyzed_info{$a}{duration}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach (keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$_}{query};
					last;
				}
			}
			$normalyzed_info{$k}{average} = $normalyzed_info{$k}{duration} / $normalyzed_info{$k}{count};
			print $fh "$idx) "
				. &convert_time($normalyzed_info{$k}{duration}) . " - "
				. &comma_numbers($normalyzed_info{$k}{count}) . " - "
				. &convert_time($normalyzed_info{$k}{average})
				. " - $q\n";
			print $fh "--\n";
			my $i = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
				print $fh "\t- Example $i: ", &convert_time($d), " - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}

		print $fh "\n- Most frequent queries (N) --------------------------------------------\n\n";
		print $fh "Rank     Times executed     Total duration     Av. duration (s)     Query\n";
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{count} <=> $normalyzed_info{$a}{count}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach (keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$_}{query};
					last;
				}
			}
			print $fh "$idx) "
				. &comma_numbers($normalyzed_info{$k}{count}) . " - "
				. &convert_time($normalyzed_info{$k}{duration}) . " - "
				. &convert_time($normalyzed_info{$k}{duration} / $normalyzed_info{$k}{count})
				. " - $q\n";
			print $fh "--\n";
			my $i = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
				print $fh "\tExample $i: ", &convert_time($d), " - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}

		print $fh "\n- Slowest queries (N) --------------------------------------------------\n\n";
		print $fh "Rank     Av. duration (s)     Times executed     Total duration     Query\n";
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{average} <=> $normalyzed_info{$a}{average}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach (keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$_}{query};
					last;
				}
			}
			print $fh "$idx) "
				. &convert_time($normalyzed_info{$k}{average}) . " - "
				. &comma_numbers($normalyzed_info{$k}{count}) . " - "
				. &convert_time($normalyzed_info{$k}{duration})
				. " - $q\n";
			print $fh "--\n";
			my $i = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
				print $fh "\tExample $i: ", &convert_time($d), " - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}
	}

	if (!$disable_error) {
		&show_error_as_text();
	}

	print $fh "\n\n";
	print $fh "Report generated by PgBadger $VERSION (http://projects.dalibo.org/pgbadger/).\n";

}

sub dump_error_as_text
{

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
$report_title

- Global informations --------------------------------------------------

Generated on $curdate
Log file: $logfile_str
Parsed $fmt_nlines log entries in $total_time
Log start from $first_log_date to $last_log_date
};

	&show_error_as_text();

	print $fh "\n\n";
	print $fh "Report generated by pgBadger $VERSION (http://projects.dalibo.org/pgbadger/).\n";
}

sub show_error_as_text
{
	print $fh "\n- Most frequent errors (N) ---------------------------------------------\n\n";
	my $idx = 1;
	foreach my $k (sort {$error_info{$b}{count} <=> $error_info{$a}{count}} keys %error_info) {
		next if (!$error_info{$k}{count});
		last if ($idx > $top);
		if ($error_info{$k}{count} > 1) {
			print $fh "$idx) " . &comma_numbers($error_info{$k}{count}) . " - $k\n";
			print $fh "--\n";
			my $j = 1;
			for (my $i = 0 ; $i <= $#{$error_info{$k}{date}} ; $i++) {
				print $fh "\t- Example $j: $error_info{$k}{date}[$i] - $error_info{$k}{error}[$i]\n";
				print $fh "\t\tDetail: $error_info{$k}{detail}[$i]\n"       if ($error_info{$k}{detail}[$i]);
				print $fh "\t\tContext: $error_info{$k}{context}[$i]\n"     if ($error_info{$k}{context}[$i]);
				print $fh "\t\tHint: $error_info{$k}{hint}[$i]\n"           if ($error_info{$k}{hint}[$i]);
				print $fh "\t\tStatement: $error_info{$k}{statement}[$i]\n" if ($error_info{$k}{statement}[$i]);
				$j++;
			}
		} else {
			print $fh "$idx) " . &comma_numbers($error_info{$k}{count}) . " - $error_info{$k}{error}[0]\n";
			print $fh "--\n";
			print $fh "\t- Date: $error_info{$k}{date}[0]\n";
			print $fh "\t\tDetail: $error_info{$k}{detail}[0]\n"       if ($error_info{$k}{detail}[0]);
			print $fh "\t\tContext: $error_info{$k}{context}[0]\n"     if ($error_info{$k}{context}[0]);
			print $fh "\t\tHint: $error_info{$k}{hint}[0]\n"           if ($error_info{$k}{hint}[0]);
			print $fh "\t\tStatement: $error_info{$k}{statement}[0]\n" if ($error_info{$k}{statement}[0]);
		}
		$idx++;
	}

	if (scalar keys %logs_type > 0) {
		print $fh "\n- Logs per type ---------------------------------------------\n\n";

		my $total_logs = 0;
		foreach my $d (keys %logs_type) {
			$total_logs += $logs_type{$d};
		}
		print $fh "Logs type	Count	Percentage\n";
		foreach my $d (sort keys %logs_type) {
			print $fh "$d\t\t", &comma_numbers($logs_type{$d}), "\t", sprintf("%0.2f", ($logs_type{$d} * 100) / $total_logs), "%\n";
		}
	}
}

sub html_header
{
	my $date = localtime(time);
	print $fh qq{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>$report_title</title>
<meta NAME="robots" CONTENT="noindex,nofollow">
<meta HTTP-EQUIV="Expires" CONTENT="$date">
<meta HTTP-EQUIV="Generator" CONTENT="PgBadger v$VERSION">
<meta HTTP-EQUIV="Date" CONTENT="$date">
<style type="text/css">
body { background-color: #dedede; font-family: Verdana ;font-size: 14px;font-style: normal;font-weight: normal;text-transform: normal;letter-spacing: normal;line-height: 17px;padding: 0px; color: rgb(69,54,37); }
div, p, th, td { font-size:12px; }
a {color:rgb(180, 80, 80);text-decoration:underline}
a:hover {color:rgb(180, 80, 80);text-decoration:none}
h1 { border-left:8px #0c57a3 solid; color: #0c57a3; font: lighter 30px Arial, Helvetica, sans-serif; margin: 0px; padding: 7px;border-top: 1px solid #CCCCCC;}
h1 a, h1 a:visited { color: #007DFC; text-decoration: none; }
h1 a:hover { color: #888888; text-decoration: none; }
h2 { border-bottom: 1px solid #CCCCCC; color: #0c57a3; font: lighter 18px Arial, Helvetica, sans-serif; letter-spacing: -1px; margin: 0px 0px 2px; padding-bottom: 3px; padding-top:7px;}
h2 a, h2 a:visited { color: #007dfc; text-decoration: none; }
h2 a:hover { color: #6f6e6e; text-decoration: none; }

div.menu {background-color:#EFEFEF; margin-bottom:15px; padding:4px; -moz-border-radius:8px; padding-left:10px; color:#8F8F8F; border:1px solid #D8D8D8; text-align: left; width: 1050px; -moz-box-shadow: 3px 3px 5px #000000; -webkit-box-shadow: 3px 3px 5px #000000; -khtml-box-shadow: 3px 3px 5px #000000; -o-box-shadow: 3px 3px 5px #000000; box-shadow: 3px 3px 5px #000000; border-color: #DBDBCC; }
div.menu a { color: #007dfc; text-decoration: none; }
div.menu a:hover { color: #6f6e6e; text-decoration: none; }
div.menu a:visited { color: #007dfc; text-decoration: none; }

div.information {
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
	width: 1035px;
}

input {
	background:#F3F2ED;
	border:4px double white;
	padding:2 2px;
	margin:3px 3px 3px 3px;
	border-radius:6px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
}

ul { padding-left: 14px; padding-top: 0px; padding-bottom: 0px; margin-bottom: 0px; margin-top: 0px; }
ul li { list-style-type: square; }
div.reports { padding:4px; }

table { border-collapse:collapse;}
table th.left { text-align:left !important; }
table tr.row0 td { background-color: #FFFFFF; border: Opx solid #E7E7E7;}
table tr.row1 td { background-color: #E7E7E7; border: Opx solid #FFFFFF;}
table td.top { vertical-align:top; }
table td.right { text-align:right; }
table td.center { text-align:center; }
table td.relevantInformation { font-weight:bold; }

table.HourStatsTable {
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	/*css3*/
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
	width: 1065px;
}

table.HourStatsTable th { background-color: #770818; border:1px solid #FEE3C4; color: white; font-weight: normal;}
table.SmallTableList {
        background:#F3F2ED;
        border:4px double white;
        padding:0 10px;
        margin:30px 0 30px 0;
        /*css3*/
        border-radius:10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
        box-shadow:3px 3px 6px 2px #A9A9A9;
        -moz-box-shadow:3px 3px 6px 2px #A9A9A9;
        -webkit-box-shadow:3px 3px 6px #A9A9A9;
        width: 520px;
}
table.SmallTableList th { background-color: #770818; border:1px solid #FEE3C4; color: white; font-weight: normal;}

table.queryList {
        background:#F3F2ED;
        border:4px double white;
        padding:2 2px;
        margin:3px 3px 3px 3px;
        /*css3*/
        border-radius:6px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
        box-shadow:3px 3px 6px 2px #A9A9A9;
        -moz-box-shadow:3px 3px 6px 2px #A9A9A9;
        -webkit-box-shadow:3px 3px 6px #A9A9A9;
}
table.queryList th { background-color: #770818; border:1px solid #FEE3C4; color: white; font-weight: normal;}
table.queryList td, table.queryList th { padding: 2px;}
table.queryList div.sql { width: 800px; overflow: auto; }
table.queryList div.examples { background-color:#E7E7E7; border:1px solid #FFFFFF; -moz-border-radius:10px; padding:6px; margin:5px;}
table.queryList div.examples div.example0 { padding:2px; }
table.queryList div.examples div.example1 { background-color:#FFFFFF; padding:2px; border:1px solid #EBF0FC; -moz-border-radius:5px; }
table.queryList div.error { color: #D53131; font-weight:bold; }
table.queryList div.errorInformation0 { background-color:#E7E7E7; color: #8D8D8D; font-style:italic; }
table.queryList div.errorInformation1 { background-color:#FFFFFF; color: #8D8D8D; font-style:italic; }
table.queryList input { border:1px solid black; background-color:#FFFFFF; padding:1px; font-size:11px; }

div.tooltipLink { position:relative; cursor:pointer; }
div.tooltipLink span.information { border-bottom:1px dotted gray; z-index:10; }
div.tooltipLink div.tooltip { display:none; background-color:#EBF0FC; border:1px solid #FFFFFF; -moz-border-radius:10px; padding:6px; width:250px; }
div.tooltipLink div.tooltip table { background-color:white; width:250px; }
div.tooltipLink div.tooltip table tr.row0 td { background-color: #FFFFFF; border: 1px solid #EEEEEE; }
div.tooltipLink div.tooltip table tr.row1 td { background-color: #EEEEEE; border: 1px solid #EEEEEE; }
div.tooltipLink div.tooltip th { font-size:10px; }
div.tooltipLink div.tooltip td { font-size:9px; font-weight:normal; padding:1px; }
div.tooltipLink:hover div.tooltip { display:block; z-index:20; position:absolute; top:1.5em; left:2em; }

div.footer { font: 14px Helvetica, Arial, sans-serif;clear: both; height:30px; color: #dddddd; padding:13px 0px 0 0;margin-left: auto; margin-right: auto;  text-align: center; background-color: #0c57a3; }
div.footer a strong { color: #eeeeee; font-weight: bold;}
div.footer a, #footer a:visited { color: #eeeeee; }
div.footer a:hover { color: #eeeeee; }

div#littleToc { display:none; }
html>body div#littleToc { display:block; background-color:white; color:black; position:fixed; bottom:10px; right:10px; width:160px; font-size:11px; text-align:left; border:1px dotted #BBBBBB; }
div#littleToc div#littleTocContent { display:none; padding:2px; }
div#littleToc:hover { width:205px; }
div#littleToc:hover div#littleTocContent { display:block; border-right:5px solid #BBBBBB; }

div#littleToc div#littleTocTitle { font-weight:bold; text-align:center;padding:2px; }
div#littleToc:hover div#littleTocTitle { display:none; }

div#littleToc ul { padding:0px; text-indent:0px; margin:0px; }
div#littleToc li { font-size:11px; list-style-type:none; padding:0px; text-indent:0px; margin:0px; }

div#littleToc a { color:#000000; padding:2px; margin:2px; display:block; text-decoration:none; border:1px solid #CCCCCC; }
div#littleToc a:hover { text-decoration:none; background-color:#DDDDDD; }

.sql  {font-family:monospace;}
.sql .imp {font-weight: bold; color: red;}
.sql .kw1 {color: #993333; font-weight: bold; text-transform: uppercase;}
.sql .kw2 {color: #993333; font-style: italic;}
.sql .kw3 {color: #993333; text-transform: uppercase;}
.sql .co1 {color: #808080; font-style: italic;}
.sql .co2 {color: #808080; font-style: italic;}
.sql .coMULTI {color: #808080; font-style: italic;}
.sql .es0 {color: #000099; font-weight: bold;}
.sql .br0 {color: #66cc66;}
.sql .sy0 {color: #000000;}
.sql .st0 {color: #ff0000;}
.sql .nu0 {color: #cc66cc;}
.sql span.xtra { display:block; }

#queriespersecond_graph, #connectionspersecond_graph, #allqueries_graph, #checkpointwritebuffers_graph, #checkpointfiles_graph, #temporaryfile_graph, #selectqueries_graph, #writequeries_graph {
	width : 1025px;
	height: 400px;
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
}
#queriesbytype_graph, #lockbytype_graph, #databasesessions_graph, #usersessions_graph, #hostsessions_graph, #databaseconnections_graph, #userconnections_graph, #hostconnections_graph, #logstype_graph {
	width : 500px;
	height: 250px;
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	/*css3*/
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
}

</style>
<script type="text/javascript">
 /* <![CDATA[ */
function toggle(idButton, idDiv, label) {
	if(document.getElementById(idDiv)) {
		if(document.getElementById(idDiv).style.display == 'none') {
			document.getElementById(idDiv).style.display = 'block';
			document.getElementById(idButton).value = 'Hide '+label;
		} else {
			document.getElementById(idDiv).style.display = 'none';
			document.getElementById(idButton).value = 'Show '+label;
		}
	}
}


/* ]]> */
</script>
};
	if ($JQGRAPH) {
		my @jslib = <DATA>;
		print $fh <<EOF;
@jslib
EOF
	}
	print $fh qq{
</head>
<body>
<div id="content">

<h1 id="top">$report_title</h1>
};
	print $fh qq{
<div class="menu">
<a href="#OverallStatsReport">Overall statistics</a> | 
};
	if (!$error_only) {
		if (!$disable_hourly) {
			print $fh qq{<a href="#HourlyStatsReport">Hourly statistics</a> | };
		}
		if (!$disable_type) {
			print $fh qq{<a href="#QueriesByTypeReport">Queries by type</a> | };
		}
		if (!$disable_query) {
			print $fh qq{
<a href="#SlowestQueriesReport">Slowest queries</a> | 
<a href="#NormalizedQueriesMostTimeReport">Queries that took up the most time (N)</a> |
<a href="#NormalizedQueriesMostFrequentReport">Most frequent queries (N)</a> |
<a href="#NormalizedQueriesSlowestAverageReport">Slowest queries (N)</a><br />
		}
		}
		if (!$disable_lock && scalar keys %lock_info > 0) {
			print $fh qq{<a href="#LocksByTypeReport">Locks by type</a> |};
		}
		if (!$disable_session) {
			if (exists $session_info{database}) {
				print $fh qq{<a href="#SessionsDatabaseReport">Sessions per database</a> |};
			}
			if (exists $session_info{user}) {
				print $fh qq{<a href="#SessionsUserReport">Sessions per user</a> |};
			}
			if (exists $session_info{host}) {
				print $fh qq{<a href="#SessionsHostReport">Sessions per host</a> |};
			}
		}
		if (!$disable_connection) {
			if (exists $connection_info{database}) {
				print $fh qq{<a href="#ConnectionsDatabaseReport">Connections per database</a> |};
			}
			if (exists $connection_info{user}) {
				print $fh qq{<a href="#ConnectionsUserReport">Connections per user</a> |};
			}
			if (exists $connection_info{host}) {
				print $fh qq{<a href="#ConnectionsHostReport">Connections per host</a><br>};
			}
		}
	}
	if (!$disable_error) {
		print $fh qq{<a href="#NormalizedErrorsMostFrequentReport">Most frequent errors (N)</a> | };
		print $fh qq{<a href="#LogsTypeReport">Logs per type</a>};
	}
	print $fh "</div>\n";
	print $fh "<p>Normalized reports are marked with a \"(N)\".</p>\n";

}

sub html_footer
{
	print $fh qq{
	<p>&nbsp;</p>
	<div class="footer">
		Report generated by <a href="http://projects.dalibo.org/pgbadger/" target="_new">pgBadger</a> $VERSION.
	</div>
</div>
};
	print $fh qq{
<div id="littleToc">
	<div id="littleTocContent">

		<ul>
			<li><a href="#top">^ Back to top</a></li><li><a href="#OverallStatsReport">Overall statistics</a></li>
};
	if (!$error_only) {
		if (!$disable_hourly) {
			print $fh qq{<li><a href="#HourlyStatsReport">Hourly statistics</a></li>};
		}
		if (!$disable_type) {
			print $fh qq{<li><a href="#QueriesByTypeReport">Queries by type</a></li>};
		}
		if (!$disable_lock && scalar keys %lock_info > 0) {
			print $fh qq{<li><a href="#LocksByTypeReport">Locks by type</a></li>};
		}
		if (!$disable_session) {
			if (exists $session_info{database}) {
				print $fh qq{<li><a href="#SessionsDatabaseReport">Sessions per database</a></li>};
			}
			if (exists $session_info{user}) {
				print $fh qq{<li><a href="#SessionsUserReport">Sessions per user</a></li>};
			}
			if (exists $session_info{host}) {
				print $fh qq{<li><a href="#SessionsHostReport">Sessions per host</a></li><li>};
			}
		}
		if (!$disable_connection) {
			if (exists $connection_info{database}) {
				print $fh qq{<li><a href="#ConnectionsDatabaseReport">Connections per database</a></li>};
			}
			if (exists $connection_info{user}) {
				print $fh qq{<li><a href="#ConnectionsUserReport">Connections per user</a></li>};
			}
			if (exists $connection_info{host}) {
				print $fh qq{<li><a href="#ConnectionsHostReport">Connections per host</a></li><li>};
			}
		}
		if (!$disable_query) {
			print $fh
qq{<a href="#SlowestQueriesReport">Slowest queries</a></li><li><a href="#NormalizedQueriesMostTimeReport">Queries that took up the most time (N)</a></li><li><a href="#NormalizedQueriesMostFrequentReport">Most frequent queries (N)</a></li><li><a href="#NormalizedQueriesSlowestAverageReport">Slowest queries (N)</a></li>};
		}
	}
	if (!$disable_error) {
		print $fh "<li><a href=\"#NormalizedErrorsMostFrequentReport\">Most frequent errors (N)</a></li>\n";
		print $fh qq{<li><a href="#LogsTypeReport">Logs per type</a></li>\n};
	}
	print $fh qq{</ul>
	</div>
	<div id="littleTocTitle">Table of contents</div>
</div>
};
	print $fh qq{
</body>
</html>
};

}

sub dump_as_html
{

	# Dump the html header
	&html_header();

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
<div class="information">
<ul>
<li>Generated on $curdate</li>
<li>Log file: $logfile_str</li>
<li>Parsed $fmt_nlines log entries in $total_time</li>
<li>Log start from $first_log_date to $last_log_date</li>
</ul>
</div>
};

	# Overall statistics
	my $fmt_unique   = &comma_numbers(scalar keys %normalyzed_info)     || 0;
	my $fmt_queries  = &comma_numbers($overall_stat{'queries_number'})  || 0;
	my $fmt_duration = &convert_time($overall_stat{'queries_duration'}) || 0;
	print $fh qq{
<div class="reports">
<h2 id="OverallStatsReport">Overall statistics <a href="#top" title="Back to top">^</a></h2>
<div class="information">
<table><tr><td valign="top">
<ul>
<li>Number of unique normalized queries: $fmt_unique</li>
<li>Number of queries: $fmt_queries</li>
<li>Total query duration: $fmt_duration</li>
<li>First query: $overall_stat{'first_query'}</li>
<li>Last query: $overall_stat{'last_query'}</li>
};
	foreach (sort {$overall_stat{'query_peak'}{$b} <=> $overall_stat{'query_peak'}{$a}} keys %{$overall_stat{'query_peak'}}) {
		print $fh "<li>Query peak: ", &comma_numbers($overall_stat{'query_peak'}{$_}), " queries/s at $_</li>";
		last;
	}
	if (!$disable_error) {
		my $fmt_errors = &comma_numbers($overall_stat{'errors_number'}) || 0;
		my $fmt_unique_error = &comma_numbers(scalar keys %{$overall_stat{'unique_normalized_errors'}}) || 0;
		print $fh qq{
<li>Number of errors: $fmt_errors</li>
<li>Number of unique normalized errors: $fmt_unique_error</li>
</ul>
</td><td valign="top">
<ul>
};
	}
	if ($tempfile_info{count}) {
		my $fmt_temp_maxsise = &comma_numbers($tempfile_info{maxsize}) || 0;
		my $fmt_temp_avsize = &comma_numbers(sprintf("%.2f", $tempfile_info{maxsize} / $tempfile_info{count}));
		print $fh qq{
<li>Number temporary file: $tempfile_info{count}</li>
<li>Max size of temporary file: $fmt_temp_maxsise</li>
<li>Average size of temporary file: $fmt_temp_avsize</li>
};
	}
	if (!$disable_session && $session_info{count}) {
		my $avg_session_duration = &convert_time($session_info{duration} / $session_info{count});
		my $tot_session_duration = &convert_time($session_info{duration});
		print $fh qq{
<li>Total number of sessions: $session_info{count}</li>
<li>Total duration of sessions: $tot_session_duration</li>
<li>Average duration of sessions: $avg_session_duration</li>
};
	}
	if (!$disable_connection && $connection_info{count}) {
		print $fh qq{
<li>Total number of connections: $connection_info{count}</li>
}
	}
	print $fh qq{
</ul>
</td></tr></table>
</div>
};

	if (!$disable_hourly) {
		print $fh qq{
<h2 id="HourlyStatsReport">Hourly statistics <a href="#top" title="Back to top">^</a></h2>

<table class="HourStatsTable">
	<tr>
		<th rowspan="2">Day</th>
		<th rowspan="2">Time</th>
		<th colspan="2">Queries</th>
		<th colspan="2">SELECT queries</th>
		<th colspan="4">Write queries</th>
};
		if (exists $connection_info{chronos}) {
			print $fh "		<th colspan=\"2\">Connections</th>\n";
		}
		if (exists $session_info{chronos}) {
			print $fh "		<th colspan=\"2\">Sessions</th>\n";
		}
		print $fh qq{
	</tr>
	<tr>
		<th>Count</th>
		<th>Av.&nbsp;duration&nbsp;</th>
		<th>Count</th>
		<th>Av.&nbsp;duration&nbsp;</th>
		<th>INSERT</th>
		<th>UPDATE</th>
		<th>DELETE</th>
		<th>Av.&nbsp;duration&nbsp;</th>
};
		if (exists $connection_info{chronos}) {
			print $fh "		<th>Count</th><th>Av./s</th>\n";
		}
		if (exists $session_info{chronos}) {
			print $fh "		<th>Count</th><th>Av.&nbsp;duration&nbsp;</th>\n";
		}
		print $fh qq{
	</tr>
};

		foreach my $d (sort {$a <=> $b} keys %per_hour_info) {
			my $c = 1;
			$d =~ /^\d{4}(\d{2})(\d{2})$/;
			my $zday = "$abbr_month{$1} $2";
			foreach my $h (sort {$a <=> $b} keys %{$per_hour_info{$d}}) {
				my $colb = $c % 2;
				$zday = "&nbsp;" if ($c > 1);
				$per_hour_info{$d}{$h}{average} = $per_hour_info{$d}{$h}{duration} / ($per_hour_info{$d}{$h}{count} || 1);
				$per_hour_info{$d}{$h}{'SELECT'}{average} =
					$per_hour_info{$d}{$h}{'SELECT'}{duration} / ($per_hour_info{$d}{$h}{'SELECT'}{count} || 1);
				my $write_average = (
					(
						$per_hour_info{$d}{$h}{'INSERT'}{duration} +
							$per_hour_info{$d}{$h}{'UPDATE'}{duration} +
							$per_hour_info{$d}{$h}{'DELETE'}{duration}
					)
						|| 0
					) / (
					(
						$per_hour_info{$d}{$h}{'INSERT'}{count} +
							$per_hour_info{$d}{$h}{'UPDATE'}{count} +
							$per_hour_info{$d}{$h}{'DELETE'}{count}
					)
						|| 1
					);
				print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{count}),  "</td><td class=\"right\">",
					&convert_time($per_hour_info{$d}{$h}{average}), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'SELECT'}{count}  || 0), "</td><td class=\"right\">",
					&convert_time($per_hour_info{$d}{$h}{'SELECT'}{average} || 0), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'INSERT'}{count}  || 0), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'UPDATE'}{count}  || 0), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'DELETE'}{count}  || 0), "</td><td class=\"right\">",
					&convert_time($write_average), "</td>";
				if (exists $connection_info{chronos}) {
					print $fh "<td class=\"right\">", &comma_numbers($connection_info{chronos}{"$d"}{"$h"}{count} || 0),
						"</td><td class=\"right\">",
						&comma_numbers(sprintf("%0.2f", $connection_info{chronos}{"$d"}{"$h"}{count} / 3600)), "/s</td>";
				}
				if (exists $session_info{chronos}) {
					$per_hour_info{$d}{$h}{'session'}{average} =
						$session_info{chronos}{"$d"}{"$h"}{duration} / ($session_info{chronos}{"$d"}{"$h"}{count} || 1);
					print $fh "<td class=\"right\">", &comma_numbers($session_info{chronos}{"$d"}{"$h"}{count} || 0),
						"</td><td  class=\"right\">", &convert_time($per_hour_info{$d}{$h}{'session'}{average}), "</td>";
				}
				print $fh "</tr>\n";
				$c++;
			}
		}

		print $fh "</table>\n";

		if ($graph) {
			my @labels = ();
			my @data1  = ();
			my @data2  = ();
			my @data3  = ();
			my $d1     = '';
			my $d2     = '';
			my $d3     = '';
			my @avgs   = ();
			for (my $i = 0 ; $i < 59 ; $i += $avg_minutes) {
				push(@avgs, sprintf("%02d", $i));
			}
			push(@avgs, 59);
			foreach my $tm (sort {$a <=> $b} keys %{$per_minute_info{query}}) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my %dataavg = ();
					foreach my $m ("00" .. "59") {
						my $rd = &average_per_minutes($m, $avg_minutes);
						if (exists $per_minute_info{query}{$tm}{$h}{$m}) {

							# Average per minute
							$dataavg{average}{"$rd"} += $per_minute_info{query}{$tm}{$h}{$m}{count};

							# Search minimum and maximum during this minute
							foreach my $s (keys %{$per_minute_info{query}{$tm}{$h}{$m}{second}}) {
								$dataavg{max}{"$rd"} = $per_minute_info{query}{$tm}{$h}{$m}{second}{$s}
									if ($per_minute_info{query}{$tm}{$h}{$m}{second}{$s} > $dataavg{max}{"$rd"});
								$dataavg{min}{"$rd"} = $per_minute_info{query}{$tm}{$h}{$m}{second}{$s}
									if (not exists $dataavg{min}{"$rd"}
									|| ($per_minute_info{query}{$tm}{$h}{$m}{second}{$s} < $dataavg{min}{"$rd"}));
							}
						}
					}
					foreach my $rd (@avgs) {
						my $t = timegm_nocheck(0, $rd, $h, $d, $mo, $y) * 1000;

						# Average per minutes
						$d2 .= "[$t, " . int(($dataavg{average}{"$rd"} || 0) / (60 * $avg_minutes)) . "],";

						# Maxi per minute
						$d1 .= "[$t, " . ($dataavg{max}{"$rd"} || 0) . "],";

						# Mini per minute
						$d3 .= "[$t, " . ($dataavg{min}{"$rd"} || 0) . "],";
					}
				}
			}
			delete $per_minute_info{query};
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			$d3 =~ s/,$//;
			&flotr2_graph(
				1, 'queriespersecond_graph', $d1, $d2, $d3, 'Queries per second (' . $avg_minutes . ' minutes average)',
				'Queries per second', 'Maximum', 'Average', 'Minimum'
			);
			$d1 = '';
			$d2 = '';
			$d3 = '';

			foreach my $tm (sort {$a <=> $b} keys %{$per_minute_info{connection}}) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my %dataavg = ();
					foreach my $m ("00" .. "59") {
						my $rd = &average_per_minutes($m, $avg_minutes);
						if (exists $per_minute_info{connection}{$tm}{$h}{$m}) {

							# Average per minute
							$dataavg{average}{"$rd"} += $per_minute_info{connection}{$tm}{$h}{$m}{count};

							# Search minimum and maximum during this minute
							foreach my $s (keys %{$per_minute_info{connection}{$tm}{$h}{$m}{second}}) {
								$dataavg{max}{"$rd"} = $per_minute_info{connection}{$tm}{$h}{$m}{second}{$s}
									if ($per_minute_info{connection}{$tm}{$h}{$m}{second}{$s} > $dataavg{max}{"$rd"});
								$dataavg{min}{"$rd"} = $per_minute_info{connection}{$tm}{$h}{$m}{second}{$s}
									if (not exists $dataavg{min}{"$rd"}
									|| ($per_minute_info{connection}{$tm}{$h}{$m}{second}{$s} < $dataavg{min}{"$rd"}));
							}
						}
					}
					foreach my $rd (@avgs) {
						my $t = timegm_nocheck(0, $rd, $h, $d, $mo, $y) * 1000;

						# Average per minutes
						$d2 .= "[$t, " . int(($dataavg{average}{"$rd"} || 0) / (60 * $avg_minutes)) . "],";

						# Maxi per minute
						$d1 .= "[$t, " . ($dataavg{max}{"$rd"} || 0) . "],";

						# Mini per minute
						$d3 .= "[$t, " . ($dataavg{min}{"$rd"} || 0) . "],";
					}
				}
			}
			delete $per_minute_info{connection};
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			$d3 =~ s/,$//;
			&flotr2_graph(
				2, 'connectionspersecond_graph', $d1, $d2, $d3, 'Connections per second (' . $avg_minutes . ' minutes average)',
				'Connections per second', 'Maximum', 'Average', 'Minimum'
			);
			$d1 = '';
			$d2 = '';
			$d3 = '';

			# All queries
			foreach my $tm (sort {$a <=> $b} keys %per_hour_info) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
					$d1 .= "[$t, " . ($per_hour_info{$tm}{$h}{count} || 0) . "],";
					$d2 .= "[$t, "
						. sprintf("%.2f", (($per_hour_info{$tm}{$h}{duration} || 0) / ($per_hour_info{$tm}{$h}{count} || 1)) / 1000)
						. "],";
				}
			}
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			&flotr2_graph(
				3, 'allqueries_graph', $d1, '', '', 'All queries',
				'Queries', 'Number of queries', '', '', 'Duration', $d2, 'Average duration (s)'
			);
			$d1 = '';
			$d2 = '';

			# Select queries
			foreach my $tm (sort {$a <=> $b} keys %per_hour_info) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
					$d1 .= "[$t, " . ($per_hour_info{$tm}{$h}{'SELECT'}{count} || 0) . "],";
					$d2 .= "[$t, "
						. sprintf(
						"%.2f",
						(($per_hour_info{$tm}{$h}{'SELECT'}{duration} || 0) / ($per_hour_info{$tm}{$h}{'SELECT'}{count} || 1)) / 1000
						) . "],";
				}
			}
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			&flotr2_graph(
				4, 'selectqueries_graph', $d1, '', '', 'SELECT queries',
				'Queries', 'Number of queries', '', '', 'Duration', $d2, 'Average duration (s)'
			);
			$d1 = '';
			$d2 = '';

			# Write queries
			my $d4 = '';
			foreach my $tm (sort {$a <=> $b} keys %per_hour_info) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
					my $wcount =
						$per_hour_info{$tm}{$h}{'UPDATE'}{count} +
						$per_hour_info{$tm}{$h}{'DELETE'}{count} +
						$per_hour_info{$tm}{$h}{'INSERT'}{count};
					my $wduration =
						$per_hour_info{$tm}{$h}{'UPDATE'}{duration} +
						$per_hour_info{$tm}{$h}{'DELETE'}{duration} +
						$per_hour_info{$tm}{$h}{'INSERT'}{duration};
					$d1 .= "[$t, " . ($per_hour_info{$tm}{$h}{'DELETE'}{count} || 0) . "],";
					$d2 .= "[$t, " . ($per_hour_info{$tm}{$h}{'INSERT'}{count} || 0) . "],";
					$d3 .= "[$t, " . ($per_hour_info{$tm}{$h}{'UPDATE'}{count} || 0) . "],";
					$d4 .= "[$t, " . sprintf("%.2f", (($wduration || 0) / ($wcount || 1)) / 1000) . "],";
				}
			}
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			$d3 =~ s/,$//;
			$d4 =~ s/,$//;
			&flotr2_graph(
				5, 'writequeries_graph', $d1, $d2, $d3, 'Write queries',
				'Queries', 'DELETE queries', 'INSERT queries', 'UPDATE queries', 'Duration', $d4, 'Average duration (s)'
			);
			$d1 = '';
			$d2 = '';
			$d3 = '';
			$d4 = '';

			if ($tempfile_info{count} || exists $checkpoint_info{chronos}) {
				print $fh qq{<table class="HourStatsTable" <tr><th rowspan="2">Day</th><th rowspan="2">Time</th>};
			}
			if ($tempfile_info{count}) {
				print $fh qq{<th colspan="2">Temporary files</th>};
			}
			if (exists $checkpoint_info{chronos}) {
				print $fh qq{<th colspan="7">Checkpoints</th>};
			}
			if (exists $checkpoint_info{warning}) {
				print $fh qq{<th colspan="2">Checkpoint warning</th>};
			}
			if ($tempfile_info{count} || exists $checkpoint_info{chronos}) {
				print $fh qq{</tr><tr>};
			}
			if ($tempfile_info{count}) {
				print $fh qq{<th>Count</th><th>Av.&nbsp;size</th>};
			}
			if (exists $checkpoint_info{chronos}) {
				print $fh
qq{<th>Wrote buffers</th><th>Added</th><th>Removed</th><th>Recycled</th><th>Write time (sec)</th><th>Sync time (sec)</th><th>Total time (sec)</th>};
			}
			if (exists $checkpoint_info{warning}) {
				print $fh qq{<th>Count</th><th>Av. time (sec)</th>};
			}
			if ($tempfile_info{count} || exists $checkpoint_info{chronos}) {
				print $fh qq{</tr>};
				foreach my $d (sort {$a <=> $b} keys %per_hour_info) {
					my $c = 1;
					$d =~ /^\d{4}(\d{2})(\d{2})$/;
					my $zday = "$abbr_month{$1} $2";
					foreach my $h (sort {$a <=> $b} keys %{$per_hour_info{$d}}) {
						my $colb = $c % 2;
						$zday = "&nbsp;" if ($c > 1);
						print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td>";
						if ($tempfile_info{count}) {
							my $temp_average = '0';
							if ($tempfile_info{chronos}{$d}{$h}{count}) {
								$temp_average = &comma_numbers(
									sprintf("%.2f", $tempfile_info{chronos}{$d}{$h}{size} / $tempfile_info{chronos}{$d}{$h}{count}));
							}
							print $fh "<td class=\"right\">", &comma_numbers($tempfile_info{chronos}{$d}{$h}{count} || 0),
								"</td><td class=\"right\">$temp_average</td>";
						}
						if (exists $checkpoint_info{chronos}) {
							if (exists $checkpoint_info{chronos}{$d}{$h}) {
								print $fh "<td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{wbuffer}) || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{file_added})   || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{file_removed}) || 0,
									"</td><td class=\"right\">",
									&comma_numbers($checkpoint_info{chronos}{$d}{$h}{file_recycled}) || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{write}) || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{sync})  || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{total}) || 0,
									"</td>";
								if ($checkpoint_info{warning}) {
									print $fh "<td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{warning}) || 0,
										"</td><td class=\"right\">",
										&comma_numbers(
										sprintf(
											"%.2f",
											($checkpoint_info{chronos}{$d}{$h}{warning_seconds} || 0) /
												($checkpoint_info{chronos}{$d}{$h}{warning} || 1)
											   )
										) || 0, "</td>";
								}
							} else {
								print $fh
"<td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td>";
								if ($checkpoint_info{warning}) {
									print $fh "<td class=\"right\">0</td><td class=\"right\">0</td>";
								}
							}
						}
						print $fh "</tr>\n";
						$c++;
					}
				}
				print $fh "</table>\n";
			}

			# checkpoint size
			if (exists $checkpoint_info{chronos}) {
				foreach my $tm (sort {$a <=> $b} keys %{$checkpoint_info{chronos}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						$d1 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{wbuffer} || 0) . "],";
					}
				}
				$d1 =~ s/,$//;
				&flotr2_graph(
					6,         'checkpointwritebuffers_graph', $d1, '', '', 'Checkpoint write buffers',
					'Buffers', 'Write buffers',                '',  ''
				);
				$d1 = '';

				foreach my $tm (sort {$a <=> $b} keys %{$checkpoint_info{chronos}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						$d1 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{file_added}    || 0) . "],";
						$d2 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{file_removed}  || 0) . "],";
						$d3 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{file_recycled} || 0) . "],";
					}
				}
				$d1 =~ s/,$//;
				$d2 =~ s/,$//;
				$d3 =~ s/,$//;
				&flotr2_graph(
					7,                 'checkpointfiles_graph', $d1,       $d2, $d3, 'Checkpoint Wal files usage',
					'Number of files', 'Added',                 'Removed', 'Recycled'
				);
				$d1 = '';
				$d2 = '';
				$d3 = '';
			}

			# Temporary file size
			if (exists $tempfile_info{chronos}) {
				foreach my $tm (sort {$a <=> $b} keys %{$tempfile_info{chronos}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						$d1 .= "[$t, " . ($tempfile_info{chronos}{$tm}{$h}{size}  || 0) . "],";
						$d2 .= "[$t, " . ($tempfile_info{chronos}{$tm}{$h}{count} || 0) . "],";
					}
				}
				$d1 =~ s/,$//;
				$d2 =~ s/,$//;
				&flotr2_graph(
					8, 'temporaryfile_graph', $d1, '', '', 'Temporary files',
					'Size of files', 'Size of files', '', '', 'Number of files', $d2, 'Number of files'
				);
				$d1 = '';
				$d2 = '';
			}
		}
	}

	# INSERT/DELETE/UPDATE/SELECT repartition
	if (!$disable_type) {
		print $fh qq{
<h2 id="QueriesByTypeReport">Queries by type <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Type</th>

		<th>Count</th>
		<th>Percentage</th>
	</tr>
};
		$overall_stat{'SELECT'} ||= 0;
		$overall_stat{'INSERT'} ||= 0;
		$overall_stat{'UPDATE'} ||= 0;
		$overall_stat{'DELETE'} ||= 0;
		my $totala = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
		my $total = $overall_stat{'queries_number'} || 1;

		print $fh "<tr class=\"row0\"><td>SELECT</td><td class=\"right\">", &comma_numbers($overall_stat{'SELECT'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'SELECT'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row1\"><td>INSERT</td><td class=\"right\">", &comma_numbers($overall_stat{'INSERT'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'INSERT'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row0\"><td>UPDATE</td><td class=\"right\">", &comma_numbers($overall_stat{'UPDATE'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'UPDATE'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row1\"><td>DELETE</td><td class=\"right\">", &comma_numbers($overall_stat{'DELETE'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'DELETE'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row0\"><td>OTHERS</td><td class=\"right\">", &comma_numbers($total - $totala),
			"</td><td class=\"right\">", sprintf("%0.2f", (($total - $totala) * 100) / $total), "%</td></tr>\n"
			if (($total - $totala) > 0);
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";

		if ($graph && $totala) {
			my %data = ();
			foreach my $t ('SELECT', 'INSERT', 'UPDATE', 'DELETE') {
				if ((($overall_stat{$t} * 100) / $total) > $pie_percentage_limit) {
					$data{$t} = $overall_stat{$t} || 0;
				} else {
					$data{"Sum types < $pie_percentage_limit%"} += $overall_stat{$t} || 0;
				}
			}
			if (((($total - $totala) * 100) / $total) > $pie_percentage_limit) {
				$data{'Others'} = $total - $totala;
			} else {
				$data{"Sum types < $pie_percentage_limit%"} += $total - $totala;
			}
			&flotr2_piegraph(9, 'queriesbytype_graph', 'Type of queries', %data);
		}
		print $fh "</td></tr></table>\n";
	}

	# Lock stats per type
	if (!$disable_lock && scalar keys %lock_info > 0) {
		print $fh qq{
<h2 id="LocksByTypeReport">Locks by type <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Type</th>
		<th>Object</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count    = 0;
		my $total_duration = 0;
		foreach my $t (sort keys %lock_info) {
			print $fh "<tr class=\"row1\"><td colspan=\"2\">$t</td><td class=\"right\">", &comma_numbers($lock_info{$t}{count}),
				"</td><td class=\"right\">", &convert_time($lock_info{$t}{duration}), "</td><td class=\"right\">",
				&convert_time($lock_info{$t}{duration} / $lock_info{$t}{count}), "</td></tr>\n";
			foreach my $o (sort keys %{$lock_info{$t}}) {
				next if (($o eq 'count') || ($o eq 'duration') || ($o eq 'chronos'));
				print $fh "<tr class=\"row0\"><td  class=\"right\" colspan=\"2\">$o</td><td class=\"right\">",
					&comma_numbers($lock_info{$t}{$o}{count}),   "</td><td class=\"right\">",
					&convert_time($lock_info{$t}{$o}{duration}), "</td><td class=\"right\">",
					&convert_time($lock_info{$t}{$o}{duration} / $lock_info{$t}{$o}{count}), "</td></tr>\n";
			}
			$total_count    += $lock_info{$t}{count};
			$total_duration += $lock_info{$t}{duration};
		}
		print $fh "<tr class=\"row1\"><td colspan=\"2\"><b>Total</b></td><td class=\"right\">", &comma_numbers($total_count),
			"</td><td class=\"right\">", &convert_time($total_duration), "</td><td class=\"right\">",
			&convert_time($total_duration / ($total_count || 1)), "</td></tr>\n";
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %locktype = ();
			my @small    = ();
			foreach my $d (sort keys %lock_info) {
				if ((($lock_info{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$locktype{$d} = $lock_info{$d}{count} || 0;
				} else {
					$locktype{"Sum types < $pie_percentage_limit%"} += $lock_info{$d}{count} || 0;
					push(@small, $d);

				}
			}
			if ($#small == 0) {
				$locktype{$small[0]} = $locktype{"Sum types < $pie_percentage_limit%"};
				delete $locktype{"Sum types < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(10, 'lockbytype_graph', 'Type of locks', %locktype);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show session per database statistics
	if (!$disable_session && exists $session_info{database}) {
		print $fh qq{
<h2 id="SessionsDatabaseReport">Sessions per database <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Database</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count = 0;
		my $c           = 0;
		foreach my $d (sort keys %{$session_info{database}}) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($session_info{database}{$d}{count}),
				"</td><td class=\"right\">", &convert_time($session_info{database}{$d}{duration}), "</td><td class=\"right\">",
				&convert_time($session_info{database}{$d}{duration} / $session_info{database}{$d}{count}), "</td></tr>\n";
			$total_count += $session_info{database}{$d}{count};
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$session_info{database}}) {
				if ((($session_info{database}{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $session_info{database}{$d}{count} || 0;
				} else {
					$infos{"Sum sessions < $pie_percentage_limit%"} += $session_info{database}{$d}{count} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum sessions < $pie_percentage_limit%"};
				delete $infos{"Sum sessions < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(11, 'databasesessions_graph', 'Sessions per database', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show session per user statistics
	if (!$disable_session && exists $session_info{user}) {
		print $fh qq{
<h2 id="SessionsUserReport">Sessions per user <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>User</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count = 0;
		my $c           = 0;
		foreach my $d (sort keys %{$session_info{user}}) {
			my $colb = $c % 2;
			$total_count += $session_info{user}{$d}{count};
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($session_info{user}{$d}{count}),
				"</td><td class=\"right\">", &convert_time($session_info{user}{$d}{duration}), "</td><td class=\"right\">",
				&convert_time($session_info{user}{$d}{duration} / $session_info{user}{$d}{count}), "</td></tr>\n";
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$session_info{user}}) {
				if ((($session_info{user}{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $session_info{user}{$d}{count} || 0;
				} else {
					$infos{"Sum sessions < $pie_percentage_limit%"} += $session_info{user}{$d}{count} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum sessions < $pie_percentage_limit%"};
				delete $infos{"Sum sessions < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(12, 'usersessions_graph', 'Sessions per user', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show session per host statistics
	if (!$disable_session && exists $session_info{host}) {
		print $fh qq{
<h2 id="SessionsHostReport">Sessions per host <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Host</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count = 0;
		my $c           = 0;
		foreach my $d (sort keys %{$session_info{host}}) {
			my $colb = $c % 2;
			$total_count += $session_info{host}{$d}{count};
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($session_info{host}{$d}{count}),
				"</td><td class=\"right\">", &convert_time($session_info{host}{$d}{duration}), "</td><td class=\"right\">",
				&convert_time($session_info{host}{$d}{duration} / $session_info{host}{$d}{count}), "</td></tr>\n";
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$session_info{host}}) {
				if ((($session_info{host}{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $session_info{host}{$d}{count} || 0;
				} else {
					$infos{"Sum sessions < $pie_percentage_limit%"} += $session_info{host}{$d}{count} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum sessions < $pie_percentage_limit%"};
				delete $infos{"Sum sessions < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(13, 'hostsessions_graph', 'Sessions per host', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show connection per database statistics
	if (!$disable_connection && exists $connection_info{database}) {
		print $fh qq{
<h2 id="ConnectionsDatabaseReport">Connections per database <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Database</th>
		<th>User</th>
		<th>Count</th>
	</tr>
};
		my $total_count = 0;
		foreach my $d (sort keys %{$connection_info{database}}) {
			print $fh "<tr class=\"row1\"><td colspan=\"2\">$d</td><td class=\"right\">",
				&comma_numbers($connection_info{database}{$d}), "</td></tr>\n";
			$total_count += $connection_info{database}{$d};
			foreach my $u (sort keys %{$connection_info{user}}) {
				next if (!exists $connection_info{database_user}{$d}{$u});
				print $fh "<tr class=\"row0\"><td colspan=\"2\" class=\"right\">$u</td><td class=\"right\">",
					&comma_numbers($connection_info{database_user}{$d}{$u}), "</td></tr>\n";
			}
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$connection_info{database}}) {
				if ((($connection_info{database}{$d} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $connection_info{database}{$d} || 0;
				} else {
					$infos{"Sum connections < $pie_percentage_limit%"} += $connection_info{database}{$d} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum connections < $pie_percentage_limit%"};
				delete $infos{"Sum connections < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(14, 'databaseconnections_graph', 'Connections per database', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show connection per user statistics
	if (!$disable_connection && exists $connection_info{user}) {
		print $fh qq{
<h2 id="ConnectionsUserReport">Connections per user <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>User</th>
		<th>Count</th>
	</tr>
};

		my $total_count = 0;
		my $c           = 0;
		foreach my $u (sort keys %{$connection_info{user}}) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$u</td><td class=\"right\">", &comma_numbers($connection_info{user}{$u}),
				"</td></tr>\n";
			$total_count += $connection_info{user}{$u};
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$connection_info{user}}) {
				if ((($connection_info{user}{$d} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $connection_info{user}{$d} || 0;
				} else {
					$infos{"Sum connections < $pie_percentage_limit%"} += $connection_info{user}{$d} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum connections < $pie_percentage_limit%"};
				delete $infos{"Sum connections < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(15, 'userconnections_graph', 'Connections per user', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show connection per host statistics
	if (!$disable_connection && exists $connection_info{host}) {
		print $fh qq{
<h2 id="ConnectionsHostReport">Connections per host <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Host</th>
		<th>Count</th>
	</tr>
};

		my $total_count = 0;
		my $c           = 0;
		foreach my $h (sort keys %{$connection_info{host}}) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$h</td><td class=\"right\">", &comma_numbers($connection_info{host}{$h}),
				"</td></tr>\n";
			$total_count += $connection_info{host}{$h};
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$connection_info{host}}) {
				if ((($connection_info{host}{$d} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $connection_info{host}{$d} || 0;
				} else {
					$infos{"Sum connections < $pie_percentage_limit%"} += $connection_info{host}{$d} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum connections < $pie_percentage_limit%"};
				delete $infos{"Sum connections < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(16, 'hostconnections_graph', 'Connections per host', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show top informations
	if (!$disable_query) {
		print $fh qq{
<h2 id="SlowestQueriesReport">Slowest queries <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>

		<th>Duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		for (my $i = 0 ; $i <= $#top_slowest ; $i++) {
			my $col = $i % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">", $i + 1, "</td><td class=\"relevantInformation top center\">",
				&convert_time($top_slowest[$i]->[0]),
				"</td><td title=\"$top_slowest[$i]->[1]\"><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($top_slowest[$i]->[2]), "</div></td></tr>\n";
		}
		print $fh "</table>\n";
		@top_slowest = ();

		print $fh qq{
<h2 id="NormalizedQueriesMostTimeReport">Queries that took up the most time (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>
		<th>Total duration</th>
		<th>Times executed</th>

		<th>Av.&nbsp;duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		my $idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{duration} <=> $normalyzed_info{$a}{duration}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$d}{query};
					last;
				}
			}
			$normalyzed_info{$k}{average} = $normalyzed_info{$k}{duration} / $normalyzed_info{$k}{count};
			my $col = $idx % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\">",
				&convert_time($normalyzed_info{$k}{duration}),
				"</td><td class=\"top center\"><div class=\"tooltipLink\"><span class=\"information\">",
				&comma_numbers($normalyzed_info{$k}{count}),
"</span><div class=\"tooltip\"><table><tr><th>Day</th><th>Time</th><th>Count</th><th>Duration</th><th>Av.&nbsp;Duration</th></tr>";
			foreach my $d (sort keys %{$normalyzed_info{$k}{chronos}}) {
				my $c = 1;
				$d =~ /^\d{4}(\d{2})(\d{2})$/;
				my $zday = "$abbr_month{$1} $2";
				foreach my $h (sort keys %{$normalyzed_info{$k}{chronos}{$d}}) {
					$normalyzed_info{$k}{chronos}{$d}{$h}{average} =
						$normalyzed_info{$k}{chronos}{$d}{$h}{duration} / $normalyzed_info{$k}{chronos}{$d}{$h}{count};
					my $colb = $c % 2;
					$zday = "&nbsp;" if ($c > 1);
					print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
						&comma_numbers($normalyzed_info{$k}{chronos}{$d}{$h}{count}),   "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{duration}), "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{average}),  "</td></tr>";
					$c++;
				}
			}
			print $fh "</table></div></div></td>";
			print $fh "<td class=\"top center\">", &convert_time($normalyzed_info{$k}{average}),
				"</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($q), "</div>";

			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedQueriesMostTimeReport_$idx\" name=\"button_NormalizedQueriesMostTimeReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedQueriesMostTimeReport_$idx', 'examples_NormalizedQueriesMostTimeReport_$idx', 'examples');\" /><div id=\"examples_NormalizedQueriesMostTimeReport_$idx\" class=\"examples\" style=\"display:none;\">";
				my $i = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $i % 2;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$i++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";

		print $fh qq{
<h2 id="NormalizedQueriesMostFrequentReport">Most frequent queries (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>
		<th>Times executed</th>
		<th>Total duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{count} <=> $normalyzed_info{$a}{count}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$d}{query};
					last;
				}
			}
			my $col = $idx % 2;
			print $fh
"<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\"><div class=\"tooltipLink\"><span class=\"information\">",
				&comma_numbers($normalyzed_info{$k}{count}),
"</span><div class=\"tooltip\"><table><tr><th>Day</th><th>Time</th><th>Count</th><th>Duration</th><th>Av.&nbsp;Duration</th></tr>";
			foreach my $d (sort keys %{$normalyzed_info{$k}{chronos}}) {
				my $c = 1;
				$d =~ /^\d{4}(\d{2})(\d{2})$/;
				my $zday = "$abbr_month{$1} $2";
				foreach my $h (sort keys %{$normalyzed_info{$k}{chronos}{$d}}) {
					$normalyzed_info{$k}{chronos}{$d}{$h}{average} =
						$normalyzed_info{$k}{chronos}{$d}{$h}{duration} / $normalyzed_info{$k}{chronos}{$d}{$h}{count};
					my $colb = $c % 2;
					$zday = "&nbsp;" if ($c > 1);
					print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
						&comma_numbers($normalyzed_info{$k}{chronos}{$d}{$h}{count}),   "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{duration}), "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{average}),  "</td></tr>";
					$c++;
				}
			}
			print $fh "</table></div></div></td>";
			print $fh "<td class=\"top center\">", &convert_time($normalyzed_info{$k}{duration}), "</td><td class=\"top center\">",
				&convert_time($normalyzed_info{$k}{average}), "</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($q), "</div>";

			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedQueriesMostFrequentReport_$idx\" name=\"button_NormalizedQueriesMostFrequentReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedQueriesMostFrequentReport_$idx', 'examples_NormalizedQueriesMostFrequentReport_$idx', 'examples');\" /><div id=\"examples_NormalizedQueriesMostFrequentReport_$idx\" class=\"examples\" style=\"display:none;\">";
				my $i = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $i % 2;
					print $fh
"<div class=\"example$d\" title=\"$normalyzed_info{$k}{samples}{$d}{date}\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$i++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";

		print $fh qq{
<h2 id="NormalizedQueriesSlowestAverageReport">Slowest queries (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>

		<th>Times executed</th>
		<th>Total duration</th>
		<th>Query</th>
	</tr>
};
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{average} <=> $normalyzed_info{$a}{average}} keys %normalyzed_info) {
			next if (!$k || !$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$d}{query};
					last;
				}
			}
			my $col = $idx % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\">",
				&convert_time($normalyzed_info{$k}{average}),
				"</td><td class=\"top center\"><div class=\"tooltipLink\"><span class=\"information\">",
				&comma_numbers($normalyzed_info{$k}{count}),
"</span><div class=\"tooltip\"><table><tr><th>Day</th><th>Time</th><th>Count</th><th>Duration</th><th>Av.&nbsp;Duration</th></tr>";
			foreach my $d (sort keys %{$normalyzed_info{$k}{chronos}}) {
				my $c = 1;
				$d =~ /^\d{4}(\d{2})(\d{2})$/;
				my $zday = "$abbr_month{$1} $2";
				foreach my $h (sort keys %{$normalyzed_info{$k}{chronos}{$d}}) {
					$normalyzed_info{$k}{chronos}{$d}{$h}{average} =
						$normalyzed_info{$k}{chronos}{$d}{$h}{duration} / $normalyzed_info{$k}{chronos}{$d}{$h}{count};
					my $colb = $c % 2;
					$zday = "&nbsp;" if ($c > 1);
					print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
						&comma_numbers($normalyzed_info{$k}{chronos}{$d}{$h}{count}),   "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{duration}), "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{average}),  "</td></tr>";
					$c++;
				}
			}
			print $fh "</table></div></div></td>";
			print $fh "<td class=\"top center\">", &convert_time($normalyzed_info{$k}{duration}),
				"</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($q), "</div>";
			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedQueriesSlowestAverageReport_$idx\" name=\"button_NormalizedQueriesSlowestAverageReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedQueriesSlowestAverageReport_$idx', 'examples_NormalizedQueriesSlowestAverageReport_$idx', 'examples');\" /><div id=\"examples_NormalizedQueriesSlowestAverageReport_$idx\" class=\"examples\" style=\"display:none;\">";
				my $i = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $i % 2;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$i++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";
	}

	if (!$disable_error) {
		&show_error_as_html();
	}

	# Dump the html footer
	&html_footer();

}

sub dump_error_as_html
{

	# Dump the html header
	&html_header();

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
<div class="information">
<ul>
<li>Generated on $curdate</li>
<li>Log file: $logfile_str</li>
<li>Parsed $fmt_nlines log entries in $total_time</li>
<li>Log start from $first_log_date to $last_log_date</li>
</ul>
</div>
};
	my $fmt_errors = &comma_numbers($overall_stat{'errors_number'}) || 0;
	my $fmt_unique_error = &comma_numbers(scalar keys %{$overall_stat{'unique_normalized_errors'}}) || 0;
	print $fh qq{
<div class="reports">
<h2 id="OverallStatsReport">Overall statistics <a href="#top" title="Back to top">^</a></h2>
<div class="information">
<table><tr><td valign="top">
<ul>
<li>Number of errors: $fmt_errors</li>
<li>Number of unique normalized errors: $fmt_unique_error</li>
</ul>
</td></tr></table>
</div>
};

	&show_error_as_html();

	# Dump the html footer
	&html_footer();
}

sub show_error_as_html
{
	print $fh qq{
<h2 id="NormalizedErrorsMostFrequentReport">Most frequent errors (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
<tr>
	<th>Rank</th>
	<th>Times reported</th>
	<th>Error</th>

</tr>
};
	my $idx = 1;
	foreach my $k (sort {$error_info{$b}{count} <=> $error_info{$a}{count}} keys %error_info) {
		next if (!$error_info{$k}{count});
		last if ($idx > $top);
		my $col = $idx % 2;
		print $fh
"<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\"><div class=\"tooltipLink\"><span class=\"information\">",
			&comma_numbers($error_info{$k}{count}), "</span>";
		print $fh "<div class=\"tooltip\"><table><tr><th>Day</th><th>Time</th><th>Count</th></tr>";
		foreach my $d (sort keys %{$error_info{$k}{chronos}}) {
			my $c = 1;
			$d =~ /^\d{4}(\d{2})(\d{2})$/;
			my $zday = "$abbr_month{$1} $2";
			foreach my $h (sort keys %{$error_info{$k}{chronos}{$d}}) {
				my $colb = $c % 2;
				$zday = "&nbsp;" if ($c > 1);
				print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
					&comma_numbers($error_info{$k}{chronos}{$d}{$h}{count}), "</td></tr>";
				$c++;
			}
		}
		print $fh "</table></div></div></td>\n";
		if ($error_info{$k}{count} > 1) {
			print $fh "<td><div class=\"error\">$k</div>";
			print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedErrorsMostFrequentReport_$idx\" name=\"button_NormalizedErrorsMostFrequentReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedErrorsMostFrequentReport_$idx', 'examples_NormalizedErrorsMostFrequentReport_$idx', 'examples');\" /><div id=\"examples_NormalizedErrorsMostFrequentReport_$idx\" class=\"examples\" style=\"display:none;\">";
			for (my $i = 0 ; $i <= $#{$error_info{$k}{date}} ; $i++) {
				my $c = $i % 2;
				print $fh "<div class=\"example$c\" title=\"$error_info{$k}{date}[$i]\">$error_info{$k}{error}[$i]</div>\n";
				print $fh "<div class=\"errorInformation$c\">Detail: $error_info{$k}{detail}[$i]</div>\n"
					if ($error_info{$k}{detail}[$i]);
				print $fh "<div class=\"errorInformation$c\">Context: $error_info{$k}{context}[$i]</div>\n"
					if ($error_info{$k}{context}[$i]);
				print $fh "<div class=\"errorInformation$c\">Hint: $error_info{$k}{hint}[$i]</div>\n" if ($error_info{$k}{hint}[$i]);
				print $fh "<div class=\"errorInformation$c\">Statement: $error_info{$k}{statement}[$i]</div>\n"
					if ($error_info{$k}{statement}[$i]);
			}
			print $fh "</div>";
		} else {
			print $fh "<td><div class=\"error\" title=\"$error_info{$k}{date}[0]\">$error_info{$k}{error}[0]</div>";
			print $fh "<div class=\"errorInformation\">Detail: $error_info{$k}{detail}[0]</div>\n"   if ($error_info{$k}{detail}[0]);
			print $fh "<div class=\"errorInformation\">Context: $error_info{$k}{context}[0]</div>\n" if ($error_info{$k}{context}[0]);
			print $fh "<div class=\"errorInformation\">Hint: $error_info{$k}{hint}[0]</div>\n"       if ($error_info{$k}{hint}[0]);
			print $fh "<div class=\"errorInformation\">Statement: $error_info{$k}{statement}[0]</div>\n"
				if ($error_info{$k}{statement}[0]);
		}
		print $fh "</td></tr>\n";
		$idx++;
	}
	print $fh "</table>\n";

	if (scalar keys %logs_type > 0) {

		# Show log' types
		print $fh qq{
	<h2 id="LogsTypeReport">Logs per type <a href="#top" title="Back to top">^</a></h2>
	<table>
	<tr><td width="500" align="left" valign="top">
	<table class="SmallTableList">
	  <tr>
	    <th>Type</th>
	    <th>Count</th>
	    <th>Percentage</th>
	  </tr>
	};

		my $total_logs = 0;
		foreach my $d (sort keys %logs_type) {
			$total_logs += $logs_type{$d};
		}

		my $c = 0;

		foreach my $d (sort keys %logs_type) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($logs_type{$d}),
				"</td><td class=\"right\">", sprintf("%0.2f", ($logs_type{$d} * 100) / $total_logs), "%</td></tr>\n";
			$c++;
		}

		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_logs) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %logs_type) {
				if ((($logs_type{$d} * 100) / $total_logs) > $pie_percentage_limit) {
					$infos{$d} = $logs_type{$d} || 0;
				} else {
					$infos{"Sum log types < $pie_percentage_limit%"} += $logs_type{$d} || 0;
					push(@small, $d);
				}
			}

			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum log types < $pie_percentage_limit%"};
				delete $infos{"Sum log types < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(17, 'logstype_graph', 'Logs per type', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

}

# Highlight SQL code
sub highlight_code
{
	my $code = shift;

	# prettify SQL query
	if (!$noprettify) {
		my $sql = SQL::Beautify->new;
		$sql->query($code);
		$code = $sql->beautify;
	}

	return $code if ($nohighlight);

	foreach my $x (keys %SYMBOLS) {
		$code =~ s/$x/\$\$CLASSSY0A\$\$$SYMBOLS{$x}\$\$CLASSSY0B\$\$/gs;
	}

	#$code =~ s/("[^"]*")/<span class="st0">$1<\/span>/igs;
	$code =~ s/('[^']*')/<span class="st0">$1<\/span>/gs;
	$code =~ s/(`[^`]*`)/<span class="st0">$1<\/span>/gs;

	for (my $x = 0 ; $x <= $#KEYWORDS1 ; $x++) {

		#$code =~ s/\b($KEYWORDS1[$x])\b/<span class="kw1">$1<\/span>/igs;
		$code =~ s/\b$KEYWORDS1[$x]\b/<span class="kw1">$KEYWORDS1[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#KEYWORDS2 ; $x++) {

		#$code =~ s/\b($KEYWORDS2[$x])\b/<span class="kw2">$1<\/span>/igs;
		$code =~ s/\b$KEYWORDS2[$x]\b/<span class="kw2">$KEYWORDS2[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#KEYWORDS3 ; $x++) {

		#$code =~ s/\b($KEYWORDS3[$x])\b/<span class="kw3">$1<\/span>/igs;
		$code =~ s/\b$KEYWORDS3[$x]\b/<span class="kw3">$KEYWORDS3[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#BRACKETS ; $x++) {
		$code =~ s/($BRACKETS[$x])/<span class="br0">$1<\/span>/igs;
	}
	$code =~ s/\$\$CLASSSY0A\$\$([^\$]+)\$\$CLASSSY0B\$\$/<span class="sy0">$1<\/span>/gs;

	$code =~ s/\b(\d+)\b/<span class="nu0">$1<\/span>/igs;

	return $code;
}

sub parse_query
{
	my ($t_year, $t_month, $t_day, $t_hour, $t_min, $t_sec, $t_host, $t_pid, $t_session, $t_logprefix, $t_loglevel, $t_query) = @_;

	# Get log level and pass if not supported following the context
	if ($t_loglevel !~ /^(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT)$/) {

		# Unrecognized loglevel format
		if ($t_loglevel ne 'NOTICE') {
			&logmsg('DEBUG', "Unrecognized loglevel format: $t_loglevel");
		}
		return;
	}

	# Do not parse lines that are not an error like message
	if ($error_only && ($t_loglevel !~ /(WARNING|ERROR|FATAL|PANIC|DETAIL|HINT|STATEMENT|CONTEXT)/)) {
		if (exists $cur_info{$t_pid} && ($t_session != $cur_info{$t_pid}{session})) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
		return;
	}

	# Do not parse lines that are an error like message
	if ($disable_error && ($t_loglevel =~ /WARNING|ERROR|FATAL|PANIC|HINT|CONTEXT|DETAIL|STATEMENT/)) {
		if (exists $cur_info{$t_pid} && ($t_session != $cur_info{$t_pid}{session})) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
		return;
	}

	# Extract user and database information from the logprefix part
	my $t_dbname = '';
	my $t_dbuser = '';

	# Remove timestamp in log_line_prefix and extra space character
	$t_logprefix =~ s/(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)\s([^\s]+)//;
	$t_logprefix =~ s/\s+$//;
	$t_logprefix =~ s/^\s+//;

	if ($t_logprefix) {

		# Search for database user
		if ($t_logprefix =~ /$regex_prefix_dbuser/) {
			$t_dbuser = $1;
		}

		# Search for database name
		if ($t_logprefix =~ /$regex_prefix_dbname/) {
			$t_dbname = $1;
		}

		# Check user and/or database if require
		if ($dbname) {

			# Log line do not match the required dbname
			if (!$t_dbname || ($dbname ne $t_dbname)) {
				delete $cur_info{$t_pid};
				return;
			}
		}
		if ($dbuser) {

			# Log line do not match the required dbuser
			if (!$t_dbuser || ($dbuser ne $t_dbuser)) {
				delete $cur_info{$t_pid};
				return;
			}
		}
	}

	# Store the current timestamp of the log line
	$first_log_date = "$t_year-$t_month-$t_day $t_hour:$t_min:$t_sec" if (!$first_log_date);
	$last_log_date = "$t_year-$t_month-$t_day $t_hour:$t_min:$t_sec";

	# Store a counter of logs type
	$logs_type{$t_loglevel}++;

	# Replace syslog tablulation rewrite
	$t_query =~ s/#011/\t/g if ($format eq 'syslog');

	# Remove line from session number
	$t_session =~ s/\-\d+//;

	# Stores lock activity
	if (($t_loglevel eq 'LOG') && ($t_query =~ /acquired ([^\s]+) on ([^\s]+) .* after ([0-9\.]+) ms/)) {
		return if ($disable_lock);
		$lock_info{$1}{count}++;
		$lock_info{$1}{duration} += $3;
		$lock_info{$1}{$2}{count}++;
		$lock_info{$1}{$2}{duration} += $3;
		$lock_info{$1}{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{count}++;
		$lock_info{$1}{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{duration}++;
		return;
	}

	# Stores temporary files activity
	if (($t_loglevel eq 'LOG') && ($t_query =~ /temporary file: path .*, size (\d+)/)) {
		return if ($disable_temporary);
		$tempfile_info{count}++;
		$tempfile_info{size} += $1;
		$tempfile_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{count}++;
		$tempfile_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{size} += $1;
		$tempfile_info{maxsize} = $1 if ($tempfile_info{maxsize} < $1);
		return;
	}

	# Stores pre connection activity
	if (($t_loglevel eq 'LOG') && ($t_query =~ /connection received: host=([^\s]+) port=(\d+)/)) {
		return if ($disable_connection);
		$conn_received{$t_pid} = $1;
		return;
	}

	# Stores connection activity
	if (($t_loglevel eq 'LOG') && ($t_query =~ /connection authorized: user=([^\s]+) database=([^\s]+)/)) {
		return if ($disable_connection);
		my $usr = $1;
		my $db  = $2;
		$connection_info{count}++;
		$connection_info{user}{$usr}++;
		$connection_info{database}{$db}++;
		$connection_info{database_user}{$db}{$usr}++;
		$connection_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{count}++;
		$connection_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{user}{$usr}++;
		$connection_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{database}{$db}++;
		$connection_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{database_user}{$db}{$usr}++;

		if ($graph) {
			$per_minute_info{connection}{"$t_year$t_month$t_day"}{"$t_hour"}{"$t_min"}{count}++;
			$per_minute_info{connection}{"$t_year$t_month$t_day"}{"$t_hour"}{"$t_min"}{second}{$t_sec}++;
		}
		if (exists $conn_received{$t_pid}) {
			$connection_info{host}{$conn_received{$t_pid}}++;
			$connection_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{host}{$conn_received{$t_pid}}++;
			delete $conn_received{$t_pid};
		}
		return;
	}

	# Stores session duration
	if (   ($t_loglevel eq 'LOG')
		&& ($t_query =~ /disconnection: session time: ([^\s]+) user=([^\s]+) database=([^\s]+) host=([^\s]+) port=(\d+)/))
	{
		return if ($disable_session);
		my $time = $1;
		my $usr  = $2;
		my $db   = $3;
		my $host = $4;

		# Store time in millisecond
		$time =~ /(\d+):(\d+):(\d+\.\d+)/;
		$time = ($3 * 1000) + ($2 * 60 * 1000) + ($1 * 60 * 60 * 1000);
		$session_info{count}++;
		$session_info{duration} += $time;
		$session_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{count}++;
		$session_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{duration} += $time;
		$session_info{database}{$db}{count}++;
		$session_info{database}{$db}{duration} += $time;
		$session_info{user}{$usr}{count}++;
		$session_info{user}{$usr}{duration} += $time;
		$session_info{host}{$host}{count}++;
		$session_info{host}{$host}{duration} += $time;
		return;
	}

	# Store checkpoint information
	if (
		($t_loglevel eq 'LOG')
		&& ($t_query =~
/checkpoint complete: wrote (\d+) buffers \(([^\)]+)\); (\d+) transaction log file\(s\) added, (\d+) removed, (\d+) recycled; write=([0-9\.]+) s, sync=([0-9\.]+) s, total=([0-9\.]+) s/
		   )
	   )
	{
		return if ($disable_checkpoint);
		$checkpoint_info{wbuffer} += $1;

		#$checkpoint_info{percent_wbuffer} += $2;
		$checkpoint_info{file_added}    += $3;
		$checkpoint_info{file_removed}  += $4;
		$checkpoint_info{file_recycled} += $5;
		$checkpoint_info{write}         += $6;
		$checkpoint_info{sync}          += $7;
		$checkpoint_info{total}         += $8;

		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{wbuffer} += $1;

		#$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{percent_wbuffer} += $2;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{file_added}    += $3;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{file_removed}  += $4;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{file_recycled} += $5;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{write}         += $6;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{sync}          += $7;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{total}         += $8;
		return;
	}
	if (($t_loglevel eq 'LOG') && ($t_query =~ /checkpoints are occurring too frequently \((\d+) seconds apart\)/)) {
		return if ($disable_checkpoint);
		$checkpoint_info{warning}++;
		$checkpoint_info{warning_seconds} += $1;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{warning}++;
		$checkpoint_info{chronos}{"$t_year$t_month$t_day"}{"$t_hour"}{warning_seconds} += $1;
		return;
	}

	# Store the detail of the error
	if ($cur_info{$t_pid}{loglevel} =~ /WARNING|ERROR|FATAL|PANIC/) {
		if ($t_loglevel =~ /(DETAIL|STATEMENT|CONTEXT|HINT)/) {
			$cur_info{$t_pid}{"\L$1\E"} .= $t_query;
			return;
		}
	}

	# Process current query following context
	if ($cur_info{$t_pid}{query}) {

		# Remove obsolete connexion storage
		delete $conn_received{$cur_info{$t_pid}{pid}};

		# The query is complete but we are missing some debug/info/bind parameter logs
		if ($cur_info{$t_pid}{loglevel} eq 'LOG') {

			# Apply bind parameters if any
			if (($t_loglevel eq 'DETAIL') && ($t_query =~ /parameters: (.*)/)) {
				my @t_res = split(/[,\s]*\$(\d+)\s=\s/, $1);
				shift(@t_res);
				for (my $i = 0 ; $i < $#t_res ; $i += 2) {
					$cur_info{$t_pid}{query} =~ s/\$$t_res[$i]\b/$t_res[$i+1]/s;
				}
				&store_queries($t_pid);
				delete $cur_info{$t_pid};
			}
		}

		# When we are ready to overwrite the last storage, add it to the global stats
		if (   ($t_loglevel =~ /LOG|FATAL|PANIC|ERROR|WARNING|HINT/)
			&& exists $cur_info{$t_pid}
			&& (($format eq 'csv') || ($t_session != $cur_info{$t_pid}{session})))
		{
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
	}

	# Extract the duration and the query parts from the entry
	my $duration_required = 1;
	if ($error_only || ($disable_hourly && $disable_query)) {
		$duration_required = 0;
	}
	my $t_action   = '';
	my $t_duration = '';
	if ($t_query =~ s/duration:\s([0-9\.]+)\sms\s+(query|statement):\s//is) {
		$t_duration = $1;
		$t_action   = $2;
	} elsif ($t_query =~ s/duration:\s([0-9\.]+)\sms\s+(prepare|parse|bind|execute|execute from fetch)\s+[^:]+:\s//is) {
		$t_duration = $1;
		$t_action   = $2;

		# Skiping parse and bind logs
		return if ($t_action !~ /query|statement|execute/);
	} elsif (!$duration_required && ($t_query =~ s/(query|statement):\s//is)) {
		$t_action = $1;
	} elsif (!$duration_required && ($t_query =~ s/(prepare|parse|bind|execute|execute from fetch)\s+[^:]+:\s//is)) {
		$t_action = $1;

		# Skiping parse and bind logs
		return if ($t_action !~ /query|statement|execute/);
	} elsif ($t_loglevel eq 'LOG') {
		if ($t_query !~
/incomplete startup packet|connection|receive|unexpected EOF|still waiting for [^\s]+Lock|checkpoint starting:|could not send data to client|parameter .*configuration file/
		   )
		{
			&logmsg('WARNING', "Unrecognized line: $t_loglevel: $t_query at line $nlines");
		}
		if (exists $cur_info{$t_pid} && ($t_session != $cur_info{$t_pid}{session})) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
		return;
	}

	$cur_info{$t_pid}{year}     = $t_year;
	$cur_info{$t_pid}{month}    = $t_month;
	$cur_info{$t_pid}{day}      = $t_day;
	$cur_info{$t_pid}{hour}     = $t_hour;
	$cur_info{$t_pid}{min}      = $t_min;
	$cur_info{$t_pid}{sec}      = $t_sec;
	$cur_info{$t_pid}{date}     = "$t_year$t_month$t_day$t_hour$t_min$t_sec";
	$cur_info{$t_pid}{host}     = $t_host;
	$cur_info{$t_pid}{pid}      = $t_pid;
	$cur_info{$t_pid}{session}  = $t_session;
	$cur_info{$t_pid}{loglevel} = $t_loglevel;
	$cur_info{$t_pid}{query}    = $t_query;
	$cur_info{$t_pid}{duration} = $t_duration;
	$cur_info{$t_pid}{dbname}   = $t_dbname;
	$cur_info{$t_pid}{dbuser}   = $t_dbuser;

	return 1;
}

sub store_queries
{
	my $t_pid = shift;

	# Cleanup and normalize the current query
	$cur_info{$t_pid}{query} =~ s/^[\t\s]+//s;
	$cur_info{$t_pid}{query} =~ s/[\t\s]+$//s;

	# Should we have to exclude some queries
	if ($#exclude_query >= 0) {
		foreach (@exclude_query) {
			if ($cur_info{$t_pid}{query} =~ /$_/i) {
				$cur_info{$t_pid}{query} = '';
				return;
			}
		}
	}

	# Truncate the query if requested by the user
	$cur_info{$t_pid}{query} = substr($cur_info{$t_pid}{query}, 0, $maxlength) . '[...]'
		if (($maxlength > 0) && (length($cur_info{$t_pid}{query}) > $maxlength));

	# Store the collected informations into global statistics
	if ($cur_info{$t_pid}{loglevel} =~ /WARNING|ERROR|FATAL|PANIC|HINT/) {

		# Add log level at beginning of the query and normalyze it
		$cur_info{$t_pid}{query} = $cur_info{$t_pid}{loglevel} . ":  " . $cur_info{$t_pid}{query};
		my $normalized_error = &normalize_error($cur_info{$t_pid}{query});

		# Stores total and normalyzed error count
		$overall_stat{'errors_number'}++;
		$overall_stat{'unique_normalized_errors'}{"$normalized_error"}++;
		$error_info{$normalized_error}{count}++;

		# Stores normalyzed error count per time
		$error_info{$normalized_error}{chronos}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
			{"$cur_info{$t_pid}{hour}"}{count}++;

		# Stores normalyzed query samples
		my $cur_last_log_date =
"$cur_info{$t_pid}{year}-$cur_info{$t_pid}{month}-$cur_info{$t_pid}{day} $cur_info{$t_pid}{hour}:$cur_info{$t_pid}{min}:$cur_info{$t_pid}{sec}";
		&set_top_error_sample(
			$normalized_error,          $cur_last_log_date,           $cur_info{$t_pid}{query}, $cur_info{$t_pid}{detail},
			$cur_info{$t_pid}{context}, $cur_info{$t_pid}{statement}, $cur_info{$t_pid}{hint}
		);

	} elsif ($cur_info{$t_pid}{loglevel} eq 'LOG') {

		# Add a semi-colon at end of the query
		$cur_info{$t_pid}{query} .= ';' if (substr($cur_info{$t_pid}{query}, -1, 1) ne ';');

		# Normalyze query
		my $normalized = &normalize_query($cur_info{$t_pid}{query});

		# Stores global statistics
		my $cur_last_log_date =
"$cur_info{$t_pid}{year}-$cur_info{$t_pid}{month}-$cur_info{$t_pid}{day} $cur_info{$t_pid}{hour}:$cur_info{$t_pid}{min}:$cur_info{$t_pid}{sec}";
		$overall_stat{'queries_number'}++;
		$overall_stat{'queries_duration'} += $cur_info{$t_pid}{duration};
		$overall_stat{'first_query'} = $cur_last_log_date if (!$overall_stat{'first_query'});
		$overall_stat{'last_query'} = $cur_last_log_date;
		$overall_stat{'query_peak'}{$cur_last_log_date}++;
		$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}{count}++;
		$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
			{duration} += $cur_info{$t_pid}{duration};

		if ($graph) {
			$per_minute_info{query}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{$cur_info{$t_pid}{hour}}
				{$cur_info{$t_pid}{min}}{count}++;
			$per_minute_info{query}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{$cur_info{$t_pid}{hour}}
				{$cur_info{$t_pid}{min}}{second}{$cur_info{$t_pid}{sec}}++;
			$per_minute_info{query}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{$cur_info{$t_pid}{hour}}
				{$cur_info{$t_pid}{min}}{duration} += $cur_info{$t_pid}{duration};
		}
		if ($normalized =~ /delete from/) {
			$overall_stat{'DELETE'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'DELETE'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'DELETE'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{delete}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{$cur_info{$t_pid}{hour}}{$cur_info{$t_pid}{min}}{count}++;
				$per_minute_info{delete}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{$cur_info{$t_pid}{hour}}{$cur_info{$t_pid}{min}}{duration} += $cur_info{$t_pid}{duration};
			}
		} elsif ($normalized =~ /insert into/) {
			$overall_stat{'INSERT'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'INSERT'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'INSERT'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{insert}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{count}++;
				$per_minute_info{insert}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{duration} += $cur_info{$t_pid}{duration};
			}
		} elsif ($normalized =~ /update.*set\b/) {
			$overall_stat{'UPDATE'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'UPDATE'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'UPDATE'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{update}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{count}++;
				$per_minute_info{update}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{duration} += $cur_info{$t_pid}{duration};
			}
		} elsif ($normalized =~ /\bselect\b/is) {
			$overall_stat{'SELECT'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'SELECT'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'SELECT'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{select}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{count}++;
				$per_minute_info{select}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{duration} += $cur_info{$t_pid}{duration};
			}
		}
		&set_top_slowest($cur_info{$t_pid}{query}, $cur_info{$t_pid}{duration}, $cur_last_log_date);

		# Store normalyzed query count
		$normalyzed_info{$normalized}{count}++;

		# Store normalyzed query total duration
		$normalyzed_info{$normalized}{duration} += $cur_info{$t_pid}{duration};

		# Store normalyzed query count and duration per time
		$normalyzed_info{$normalized}{chronos}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
			{"$cur_info{$t_pid}{hour}"}{count}++;
		$normalyzed_info{$normalized}{chronos}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
			{"$cur_info{$t_pid}{hour}"}{duration} += $cur_info{$t_pid}{duration};

		# Store normalyzed query samples
		&set_top_sample($normalized, $cur_info{$t_pid}{query}, $cur_info{$t_pid}{duration}, $last_log_date);
	}
}

# Normalyze error messages
sub normalize_error
{
	my $orig_query = shift;

	return if (!$orig_query);

	# Remove character position
	$orig_query =~ s/ at character \d+//;

	# Remove encoding detail
	$orig_query =~ s/(byte sequence for encoding).*/$1/;

	# Replace changing parameter by ...
	$orig_query =~ s/"[^"]*"/"..."/g;
	$orig_query =~ s/\(.*\)/\(...\)/g;
	$orig_query =~ s/column .* does not exist/column "..." does not exist/;

	# Need more normalization stuff here

	return $orig_query;
}

sub average_per_minutes
{
	my $val = shift;
	my $idx = shift;

	my @avgs = ();
	for (my $i = 0 ; $i < 59 ; $i += $idx) {
		push(@avgs, sprintf("%02d", $i));
	}
	push(@avgs, 59);

	for (my $i = 0 ; $i <= $#avgs ; $i++) {
		if ($val == $avgs[$i]) {
			return "$avgs[$i]";
		} elsif ($avgs[$i] == $avgs[-1]) {
			return "$avgs[$i-1]";
		} elsif (($val > $avgs[$i]) && ($val < $avgs[$i + 1])) {
			return "$avgs[$i]";
		}
	}
	return $val;
}

sub autodetect_format
{
	my $file = shift;

	# Open log file for reading
	my $nfound = 0;
	my $nline  = 0;
	my $fmt    = '';
	my $tfile  = new IO::File;
	if ($file !~ /\.gz/) {
		$tfile->open($file) || die "FATAL: cannot read logfile $file. $!\n";
	} else {

		# Open a pipe to zcat program for compressed log
		$tfile->open("$zcat $file |") || die "FATAL: cannot read from pipe to $zcat $file. $!\n";
	}
	my $duration = 'duration:';
	if ($error_only || ($disable_hourly && $disable_query)) {
		$duration = '';
	}
	my %ident_name = ();
	while (my $line = <$tfile>) {
		chomp($line);
		$line =~ s/
//;
		next if (!$line);
		$nline++;

		# Is syslog lines ?
		if ($line =~ /^...\s+\d+\s\d+:\d+:\d+\s[^\s]+\s([^\[]+)\[\d+\]:\s\[[0-9\-]+\][^:]+:\s+$duration/) {
			$fmt = 'syslog';
			$nfound++;
			$ident_name{$1}++;

			# Is stderr lines
		} elsif ($line =~ /\d+-\d+-\d+\s\d+:\d+:\d+\s[^\s]+\s\[\d+\]:\s\[[0-9\-]+\][^:]+:\s+$duration/) {
			$fmt = 'stderr';
			$nfound++;
		} elsif ($line =~ tr/,/,/ >= 12) {
			$fmt = 'csv';
			$nfound++;
		}
		last if (($nfound > 10) || ($nline > 5000));
	}
	$tfile->close();
	if (!$fmt || ($nfound < 10)) {
		die "FATAL: unable to detect log file format from $file, please use -f option.\n";
	}

	if (($fmt eq 'syslog') && !$ident && (scalar keys %ident_name == 1)) {
		$ident = (keys %ident_name)[0];
	}

	&logmsg('DEBUG', "Autodetected log format '$fmt' from $file");

	return $fmt;
}

sub progress_bar
{
	my ($got, $total, $width, $char) = @_;
	$width ||= 25;
	$char  ||= '=';
	my $num_width = length $total;
	sprintf(
		"[%-${width}s] Parsed %${num_width}s bytes of %s (%.2f%%)\r",
		$char x (($width - 1) * $got / $total) . '>',
		$got, $total, 100 * $got / +$total
	);
}

sub flotr2_graph
{
	my ($buttonid, $divid, $data1, $data2, $data3, $title, $ytitle, $legend1, $legend2, $legend3, $ytitle2, $data4, $legend4) = @_;

	$data1 = "var d1 = [$data1];" if ($data1);
	$data2 = "var d2 = [$data2];" if ($data2);
	$data3 = "var d3 = [$data3];" if ($data3);
	$data4 = "var d4 = [$data4];" if ($data4);

	$legend1 = "{ data: d1, label: \"$legend1\" },"          if ($legend1);
	$legend2 = "{ data: d2, label: \"$legend2\" },"          if ($legend2);
	$legend3 = "{ data: d3, label: \"$legend3\" },"          if ($legend3);
	$legend4 = "{ data: d4, label: \"$legend4\",yaxis: 2 }," if ($legend4);

	my $yaxis2 = '';
	if ($ytitle2) {
		$yaxis2 = "y2axis: { title: \"$ytitle2\", min: 0, color: \"#4DA74D\" },";
	}

	print $fh <<EOF;
<div id="$divid"></div>
<script type="text/javascript">
(function mouse_zoom(container) {

document.writeln('<table align="center"><tr><td><input type="button" class="dldButton" value="To Image" id="toimage$buttonid" onclick="return false;">'+
	'<input type="button" class="dldButton" value="Download" id="download$buttonid" onclick="return false;">' +
	'<input type="button" class="dldButton" value="Reset" id="reset$buttonid" onclick="return false;"></td></tr><tr><td>&nbsp;</td></tr></table>'
	);
    $data1
    $data2
    $data3
    $data4
    var options = {
        xaxis: {
            mode: "time",
            labelsAngle: 45
        },
        yaxis: {
            mode: "normal",
            title: "$ytitle",
        },
	$yaxis2
        selection: {
            mode: "x",
            fps: 30
        },
        title: "$title",
        legend: {
            position: "nw",
            backgroundColor: "#D2E8FF"
        },
	mouse: {
            track: true,
	    trackFormatter: function(obj){ return dateTracker(obj) },
            relative: true
        },
        HtmlText: false,
    };

    function drawGraph(opts) {
        var o = Flotr._.extend(Flotr._.clone(options), opts );
        return Flotr.draw(
        	container,
        	[
        		$legend1
        		$legend2
        		$legend3
			$legend4
    		],
    		o
    	);
    }

    var graph = drawGraph();
    Flotr.EventAdapter.observe(container, "flotr:select", function(area) {
        f = drawGraph({
            xaxis: {
            mode: "time",
            labelsAngle: 45,
                min: area.x1,
                max: area.x2
            },
            yaxis: {
                min: area.y1,
                max: area.y2
            }
        });
    });
    Flotr.EventAdapter.observe(container, "flotr:click", function() {
        drawGraph();
    });
    document.getElementById('reset$buttonid').onclick = function() {
      graph.download.restoreCanvas();
    };
    document.getElementById('download$buttonid').onclick = function(){
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format');
    };
    document.getElementById('toimage$buttonid').onclick = function() {
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format', null, null, true);
    };

})(document.getElementById("$divid"));
</script>
EOF

}

sub flotr2_piegraph
{
	my ($buttonid, $divid, $title, %data) = @_;

	my @datadef = ();
	my @contdef = ();
	my $i       = 1;
	foreach my $k (sort keys %data) {
		push(@datadef, "var d$i = [ [0,$data{$k}] ];\n");
		push(@contdef, "{ data: d$i, label: \"$k\" },\n");
		$i++;
	}
	print $fh <<EOF;
<div id="$divid"></div>
<script type="text/javascript">
(function basic_pie(container) {


document.writeln('<input type="button" class="dldButton" value="To Image" id="toimage$buttonid" onclick="return false;">'+
	'<input type="button" class="dldButton" value="Download" id="download$buttonid" onclick="return false;">' +
	'<input type="button" class="dldButton" value="Reset" id="reset$buttonid" onclick="return false;">'
	);

    @datadef
    var graph = Flotr.draw(container, [
    @contdef
    ], {
        title: "$title",
        HtmlText: false,
        grid: {
            verticalLines: false,
            horizontalLines: false,
	    backgroundColor: '#ffffff',
	    outline: '',
        },
        xaxis: {
            showLabels: false
        },
        yaxis: {
            showLabels: false
        },
        pie: {
            show: true,
	    explode: 6
        },
        mouse: {
            track: true,
	    trackFormatter: function(obj){ return obj.y },
        },
        legend: {
            position: "sw",
            backgroundColor: "#D2E8FF"
        }
    });
    document.getElementById('reset$buttonid').onclick = function() {
      graph.download.restoreCanvas();
    };
    document.getElementById('download$buttonid').onclick = function(){
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format');
    };
    document.getElementById('toimage$buttonid').onclick = function() {
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format', null, null, true);
    };


})(document.getElementById("$divid"));
</script>
EOF

}

{

	package SQL::Beautify;

	use strict;
	use warnings;

	our $VERSION = 0.04;

	use Carp;

	# Keywords from SQL-92, SQL-99 and SQL-2003.
	use constant KEYWORDS => qw(
		ABSOLUTE ACTION ADD AFTER ALL ALLOCATE ALTER AND ANY ARE ARRAY AS ASC
		ASENSITIVE ASSERTION ASYMMETRIC AT ATOMIC AUTHORIZATION AVG BEFORE BEGIN
		BETWEEN BIGINT BINARY BIT BIT_LENGTH BLOB BOOLEAN BOTH BREADTH BY CALL
		CALLED CASCADE CASCADED CASE CAST CATALOG CHAR CHARACTER CHARACTER_LENGTH
		CHAR_LENGTH CHECK CLOB CLOSE COALESCE COLLATE COLLATION COLUMN COMMIT
		CONDITION CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONSTRUCTOR CONTAINS
		CONTINUE CONVERT CORRESPONDING COUNT CREATE CROSS CUBE CURRENT CURRENT_DATE
		CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_PATH CURRENT_ROLE CURRENT_TIME
		CURRENT_TIMESTAMP CURRENT_TRANSFORM_GROUP_FOR_TYPE CURRENT_USER CURSOR
		CYCLE DATA DATE DAY DEALLOCATE DEC DECIMAL DECLARE DEFAULT DEFERRABLE
		DEFERRED DELETE DEPTH DEREF DESC DESCRIBE DESCRIPTOR DETERMINISTIC
		DIAGNOSTICS DISCONNECT DISTINCT DO DOMAIN DOUBLE DROP DYNAMIC EACH ELEMENT
		ELSE ELSEIF END EPOCH EQUALS ESCAPE EXCEPT EXCEPTION EXEC EXECUTE EXISTS
		EXIT EXTERNAL EXTRACT FALSE FETCH FILTER FIRST FLOAT FOR FOREIGN FOUND FREE
		FROM FULL FUNCTION GENERAL GET GLOBAL GO GOTO GRANT GROUP GROUPING HANDLER
		HAVING HOLD HOUR IDENTITY IF IMMEDIATE IN INDICATOR INITIALLY INNER INOUT
		INPUT INSENSITIVE INSERT INT INTEGER INTERSECT INTERVAL INTO IS ISOLATION
		ITERATE JOIN KEY LANGUAGE LARGE LAST LATERAL LEADING LEAVE LEFT LEVEL LIKE
		LIMIT LOCAL LOCALTIME LOCALTIMESTAMP LOCATOR LOOP LOWER MAP MATCH MAX
		MEMBER MERGE METHOD MIN MINUTE MODIFIES MODULE MONTH MULTISET NAMES
		NATIONAL NATURAL NCHAR NCLOB NEW NEXT NO NONE NOT NULL NULLIF NUMERIC
		OBJECT OCTET_LENGTH OF OLD ON ONLY OPEN OPTION OR ORDER ORDINALITY OUT
		OUTER OUTPUT OVER OVERLAPS PAD PARAMETER PARTIAL PARTITION PATH POSITION
		PRECISION PREPARE PRESERVE PRIMARY PRIOR PRIVILEGES PROCEDURE PUBLIC RANGE
		READ READS REAL RECURSIVE REF REFERENCES REFERENCING RELATIVE RELEASE
		REPEAT RESIGNAL RESTRICT RESULT RETURN RETURNS REVOKE RIGHT ROLE ROLLBACK
		ROLLUP ROUTINE ROW ROWS SAVEPOINT SCHEMA SCOPE SCROLL SEARCH SECOND SECTION
		SELECT SENSITIVE SESSION SESSION_USER SET SETS SIGNAL SIMILAR SIZE SMALLINT
		SOME SPACE SPECIFIC SPECIFICTYPE SQL SQLCODE SQLERROR SQLEXCEPTION SQLSTATE
		SQLWARNING START STATE STATIC SUBMULTISET SUBSTRING SUM SYMMETRIC SYSTEM
		SYSTEM_USER TABLE TABLESAMPLE TEMPORARY TEXT THEN TIME TIMESTAMP
		TIMEZONE_HOUR TIMEZONE_MINUTE TINYINT TO TRAILING TRANSACTION TRANSLATE
		TRANSLATION TREAT TRIGGER TRIM TRUE UNDER UNDO UNION UNIQUE UNKNOWN UNNEST
		UNTIL UPDATE UPPER USAGE USER USING VALUE VALUES VARCHAR VARYING VIEW WHEN
		WHENEVER WHERE WHILE WINDOW WITH WITHIN WITHOUT WORK WRITE YEAR ZONE
		);

	sub tokenize_sql
	{
		my ($query, $remove_white_tokens) = @_;

		my $re = qr{
    (
        (?:--|\#)[\ \t\S]*      # single line comments
        |
        (?:<>|<=>|>=|<=|==|=|!=|!|<<|>>|<|>|\|\||\||&&|&|-|\+|\*(?!/)|/(?!\*)|\%|~|\^|\?)
                                # operators and tests
        |
        [\[\]\(\),;.]            # punctuation (parenthesis, comma)
        |
        \'\'(?!\')              # empty single quoted string
        |
        \"\"(?!\"")             # empty double quoted string
        |
        "(?>(?:(?>[^"\\]+)|""|\\.)*)+"
                                # anything inside double quotes, ungreedy
        |
        `(?>(?:(?>[^`\\]+)|``|\\.)*)+`
                                # anything inside backticks quotes, ungreedy
        |
        '(?>(?:(?>[^'\\]+)|''|\\.)*)+'
                                # anything inside single quotes, ungreedy.
        |
        /\*[\ \t\r\n\S]*?\*/      # C style comments
        |
        (?:[\w:@]+(?:\.(?:\w+|\*)?)*)
                                # words, standard named placeholders, db.table.*, db.*
        |
        (?: \$_\$ | \$\d+ | \${1,2} )
                                # dollar expressions - eg $_$ $3 $$
        |
        \n                      # newline
        |
        [\t\ ]+                 # any kind of white spaces
    )
}smx;

		my @query = ();
		@query = $query =~ m{$re}smxg;

		if ($remove_white_tokens) {
			@query = grep(!/^[\s\n\r]*$/, @query);
		}

		return wantarray ? @query : \@query;
	}

	sub new
	{
		my ($class, %options) = @_;

		my $self = bless {%options}, $class;

		# Set some defaults.
		$self->{query}       = ''   unless defined($self->{query});
		$self->{spaces}      = 4    unless defined($self->{spaces});
		$self->{space}       = ' '  unless defined($self->{space});
		$self->{break}       = "\n" unless defined($self->{break});
		$self->{wrap}        = {}   unless defined($self->{wrap});
		$self->{keywords}    = []   unless defined($self->{keywords});
		$self->{rules}       = {}   unless defined($self->{rules});
		$self->{uc_keywords} = 0    unless defined $self->{uc_keywords};

		push @{$self->{keywords}}, KEYWORDS;

		# Initialize internal stuff.
		$self->{_level} = 0;

		return $self;
	}

	# Add more SQL.
	sub add
	{
		my ($self, $addendum) = @_;

		$addendum =~ s/^\s*/ /;

		$self->{query} .= $addendum;
	}

	# Set SQL to beautify.
	sub query
	{
		my ($self, $query) = @_;

		$self->{query} = $query if (defined($query));

		return $self->{query};
	}

	# Beautify SQL.
	sub beautify
	{
		my ($self) = @_;

		$self->{_output}      = '';
		$self->{_level_stack} = [];
		$self->{_new_line}    = 1;

		my $last;
		$self->{_tokens} = [tokenize_sql($self->query, 1)];

		while (defined(my $token = $self->_token)) {
			my $rule = $self->_get_rule($token);

			# Allow custom rules to override defaults.
			if ($rule) {
				$self->_process_rule($rule, $token);
			}

			elsif ($token eq '(') {
				$self->_add_token($token);
				$self->_new_line;
				push @{$self->{_level_stack}}, $self->{_level};
				$self->_over unless $last and uc($last) eq 'WHERE';
			}

			elsif ($token eq ')') {
				$self->_new_line;
				$self->{_level} = pop(@{$self->{_level_stack}}) || 0;
				$self->_add_token($token);
				$self->_new_line;
			}

			elsif ($token eq ',') {
				$self->_add_token($token);
				$self->_new_line;
			}

			elsif ($token eq ';') {
				$self->_add_token($token);
				$self->_new_line;

				# End of statement; remove all indentation.
				@{$self->{_level_stack}} = ();
				$self->{_level} = 0;
			}

			elsif ($token =~ /^(?:SELECT|FROM|WHERE|HAVING)$/i) {
				$self->_back unless $last and $last eq '(';
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line if ($self->_next_token and $self->_next_token ne '(');
				$self->_over;
			}

			elsif ($token =~ /^(?:GROUP|ORDER|LIMIT)$/i) {
				$self->_back;
				$self->_new_line;
				$self->_add_token($token);
			}

			elsif ($token =~ /^(?:BY)$/i) {
				$self->_add_token($token);
				$self->_new_line;
				$self->_over;
			}

			elsif ($token =~ /^(?:UNION|INTERSECT|EXCEPT)$/i) {
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line;
			}

			elsif ($token =~ /^(?:LEFT|RIGHT|INNER|OUTER|CROSS)$/i) {
				$self->_back;
				$self->_new_line;
				$self->_add_token($token);
				$self->_over;
			}

			elsif ($token =~ /^(?:JOIN)$/i) {
				if ($last and $last !~ /^(?:LEFT|RIGHT|INNER|OUTER|CROSS)$/) {
					$self->_new_line;
				}

				$self->_add_token($token);
			}

			elsif ($token =~ /^(?:AND|OR)$/i) {
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line;
			}

			else {
				$self->_add_token($token, $last);
			}

			$last = $token;
		}

		$self->_new_line;

		$self->{_output};
	}

	# Add a token to the beautified string.
	sub _add_token
	{
		my ($self, $token, $last_token) = @_;

		if ($self->{wrap}) {
			my $wrap;
			if ($self->_is_keyword($token)) {
				$wrap = $self->{wrap}->{keywords};
			} elsif ($self->_is_constant($token)) {
				$wrap = $self->{wrap}->{constants};
			}

			if ($wrap) {
				$token = $wrap->[0] . $token . $wrap->[1];
			}
		}

		my $last_is_dot = defined($last_token) && $last_token eq '.';

		if (!$self->_is_punctuation($token) and !$last_is_dot) {
			$self->{_output} .= $self->_indent;
		}

		# uppercase keywords
		$token = uc $token
			if $self->_is_keyword($token)
				and $self->{uc_keywords};

		$self->{_output} .= $token;

		# This can't be the beginning of a new line anymore.
		$self->{_new_line} = 0;
	}

	# Increase the indentation level.
	sub _over
	{
		my ($self) = @_;

		++$self->{_level};
	}

	# Decrease the indentation level.
	sub _back
	{
		my ($self) = @_;

		--$self->{_level} if ($self->{_level} > 0);
	}

	# Return a string of spaces according to the current indentation level and the
	# spaces setting for indenting.
	sub _indent
	{
		my ($self) = @_;

		if ($self->{_new_line}) {
			return $self->{space} x ($self->{spaces} * $self->{_level});
		} else {
			return $self->{space};
		}
	}

	# Add a line break, but make sure there are no empty lines.
	sub _new_line
	{
		my ($self) = @_;

		$self->{_output} .= $self->{break} unless ($self->{_new_line});
		$self->{_new_line} = 1;
	}

	# Have a look at the token that's coming up next.
	sub _next_token
	{
		my ($self) = @_;

		return @{$self->{_tokens}} ? $self->{_tokens}->[0] : undef;
	}

	# Get the next token, removing it from the list of remaining tokens.
	sub _token
	{
		my ($self) = @_;

		return shift @{$self->{_tokens}};
	}

	# Check if a token is a known SQL keyword.
	sub _is_keyword
	{
		my ($self, $token) = @_;

		return ~~ grep {$_ eq uc($token)} @{$self->{keywords}};
	}

	# Add new keywords to highlight.
	sub add_keywords
	{
		my $self = shift;

		for my $keyword (@_) {
			push @{$self->{keywords}}, ref($keyword) ? @{$keyword} : $keyword;
		}
	}

	# Add new rules.
	sub add_rule
	{
		my ($self, $format, $token) = @_;

		my $rules = $self->{rules}    ||= {};
		my $group = $rules->{$format} ||= [];

		push @{$group}, ref($token) ? @{$token} : $token;
	}

	# Find custom rule for a token.
	sub _get_rule
	{
		my ($self, $token) = @_;

		values %{$self->{rules}};    # Reset iterator.

		while (my ($rule, $list) = each %{$self->{rules}}) {
			return $rule if (grep {uc($token) eq uc($_)} @$list);
		}

		return undef;
	}

	sub _process_rule
	{
		my ($self, $rule, $token) = @_;

		my $format = {
			break => sub {$self->_new_line},
			over  => sub {$self->_over},
			back  => sub {$self->_back},
			token => sub {$self->_add_token($token)},
			push  => sub {push @{$self->{_level_stack}}, $self->{_level}},
			pop   => sub {$self->{_level} = pop(@{$self->{_level_stack}}) || 0},
			reset => sub {$self->{_level} = 0; @{$self->{_level_stack}} = ();},
		};

		for (split /-/, lc $rule) {
			&{$format->{$_}} if ($format->{$_});
		}
	}

	# Check if a token is a constant.
	sub _is_constant
	{
		my ($self, $token) = @_;

		return ($token =~ /^\d+$/ or $token =~ /^(['"`]).*\1$/);
	}

	# Check if a token is punctuation.
	sub _is_punctuation
	{
		my ($self, $token) = @_;
		return ($token =~ /^[,;.]$/);
	}

}

__DATA__

<script type="text/javascript">
function sql_format (obj)
{
	if (obj.style == undefined || obj.style.whiteSpace == 'pre') {
		obj.style.whiteSpace ='normal';
	} else {
		obj.style.whiteSpace = 'pre';
	}
}

function dateTracker(obj) 
{
	var dateToDisplay = new Date(parseInt(obj.x)); 
	return '('+dateToDisplay.toGMTString()+' , '+obj.y+')';
}

/*!
  * bean.js - copyright Jacob Thornton 2011
  * https://github.com/fat/bean
  * MIT License
  * special thanks to:
  * dean edwards: http://dean.edwards.name/
  * dperini: https://github.com/dperini/nwevents
  * the entire mootools team: github.com/mootools/mootools-core
  *//*global module:true, define:true*/
!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c<b.length;c++)a[b[c]]=1;return a}({},("click dblclick mouseup mousedown contextmenu mousewheel DOMMouseScroll mouseover mouseout mousemove selectstart selectend keydown keypress keyup orientationchange focus blur change reset select submit load unload beforeunload resize move DOMContentLoaded readystatechange error abort scroll "+(n?"show input invalid touchstart touchmove touchend touchcancel gesturestart gesturechange gestureend message readystatechange pageshow pagehide popstate hashchange offline online afterprint beforeprint dragstart dragenter dragover dragleave drag drop dragend loadstart progress suspend emptied stalled loadmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate play pause ratechange volumechange cuechange checking noupdate downloading cached updateready obsolete ":"")).split(" ")),u=function(){function a(a,b){while((b=b.parentNode)!==null)if(b===a)return!0;return!1}function b(b){var c=b.relatedTarget;return c?c!==this&&c.prefix!=="xul"&&!/document/.test(this.toString())&&!a(this,c):c===null}return{mouseenter:{base:"mouseover",condition:b},mouseleave:{base:"mouseout",condition:b},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}}}(),v=function(){var a="altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which".split(" "),b=a.concat("button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" ")),c=a.concat("char charCode key keyCode".split(" ")),d=a.concat("touches targetTouches changedTouches scale rotation".split(" ")),f="preventDefault",g=function(a){return function(){a[f]?a[f]():a.returnValue=!1}},h="stopPropagation",i=function(a){return function(){a[h]?a[h]():a.cancelBubble=!0}},j=function(a){return function(){a[f](),a[h](),a.stopped=!0}},k=function(a,b,c){var d,e;for(d=c.length;d--;)e=c[d],!(e in b)&&e in a&&(b[e]=a[e])};return function(n,o){var p={originalEvent:n,isNative:o};if(!n)return p;var s,t=n.type,u=n.target||n.srcElement;p[f]=g(n),p[h]=i(n),p.stop=j(p),p.target=u&&u.nodeType===3?u.parentNode:u;if(o){if(t.indexOf("key")!==-1)s=c,p.keyCode=n.which||n.keyCode;else if(q.test(t)){s=b,p.rightClick=n.which===3||n.button===2,p.pos={x:0,y:0};if(n.pageX||n.pageY)p.clientX=n.pageX,p.clientY=n.pageY;else if(n.clientX||n.clientY)p.clientX=n.clientX+l.body.scrollLeft+m.scrollLeft,p.clientY=n.clientY+l.body.scrollTop+m.scrollTop;e.test(t)&&(p.relatedTarget=n.relatedTarget||n[(t==="mouseover"?"from":"to")+"Element"])}else r.test(t)&&(s=d);k(n,p,s||a)}return p}}(),w=function(a,b){return!n&&!b&&(a===l||a===c)?m:a},x=function(){function a(a,b,c,d,e){this.element=a,this.type=b,this.handler=c,this.original=d,this.namespaces=e,this.custom=u[b],this.isNative=t[b]&&a[o],this.eventType=n||this.isNative?b:"propertychange",this.customType=!n&&!this.isNative&&b,this.target=w(a,this.isNative),this.eventSupport=this.target[o]}return a.prototype={inNamespaces:function(a){var b,c;if(!a)return!0;if(!this.namespaces)return!1;for(b=a.length;b--;)for(c=this.namespaces.length;c--;)if(a[b]===this.namespaces[c])return!0;return!1},matches:function(a,b,c){return this.element===a&&(!b||this.original===b)&&(!c||this.handler===c)}},a}(),y=function(){var a={},b=function(c,d,e,f,g){if(!d||d==="*")for(var h in a)h.charAt(0)==="$"&&b(c,h.substr(1),e,f,g);else{var i=0,j,k=a["$"+d],l=c==="*";if(!k)return;for(j=k.length;i<j;i++)if(l||k[i].matches(c,e,f))if(!g(k[i],k,i,d))return}},c=function(b,c,d){var e,f=a["$"+c];if(f)for(e=f.length;e--;)if(f[e].matches(b,d,null))return!0;return!1},d=function(a,c,d){var e=[];return b(a,c,d,null,function(a){return e.push(a)}),e},e=function(b){return(a["$"+b.type]||(a["$"+b.type]=[])).push(b),b},f=function(c){b(c.element,c.type,null,c.handler,function(b,c,d){return c.splice(d,1),c.length===0&&delete a["$"+b.type],!1})},g=function(){var b,c=[];for(b in a)b.charAt(0)==="$"&&(c=c.concat(a[b]));return c};return{has:c,get:d,put:e,del:f,entries:g}}(),z=n?function(a,b,c,d){a[d?h:j](b,c,!1)}:function(a,b,c,d,e){e&&d&&a["_on"+e]===null&&(a["_on"+e]=0),a[d?i:k]("on"+b,c)},A=function(a,b,d){return function(e){return e=v(e||((this.ownerDocument||this.document||this).parentWindow||c).event,!0),b.apply(a,[e].concat(d))}},B=function(a,b,d,e,f,g){return function(h){if(e?e.apply(this,arguments):n?!0:h&&h.propertyName==="_on"+d||!h)h&&(h=v(h||((this.ownerDocument||this.document||this).parentWindow||c).event,g)),b.apply(a,h&&(!f||f.length===0)?arguments:p.call(arguments,h?0:1).concat(f))}},C=function(a,b,c,d,e){return function(){a(b,c,e),d.apply(this,arguments)}},D=function(a,b,c,d){var e,f,h,i=b&&b.replace(g,""),j=y.get(a,i,c);for(e=0,f=j.length;e<f;e++)j[e].inNamespaces(d)&&((h=j[e]).eventSupport&&z(h.target,h.eventType,h.handler,!1,h.type),y.del(h))},E=function(a,b,c,d,e){var h,i=b.replace(g,""),j=b.replace(f,"").split(".");if(y.has(a,i,c))return a;i==="unload"&&(c=C(D,a,i,c,d)),u[i]&&(u[i].condition&&(c=B(a,c,i,u[i].condition,!0)),i=u[i].base||i),h=y.put(new x(a,i,c,d,j[0]&&j)),h.handler=h.isNative?A(a,h.handler,e):B(a,h.handler,i,!1,e,!1),h.eventSupport&&z(h.target,h.eventType,h.handler,!0,h.customType)},F=function(a,b,c){return function(d){var e,f,g=typeof a=="string"?c(a,this):a;for(e=d.target;e&&e!==this;e=e.parentNode)for(f=g.length;f--;)if(g[f]===e)return b.apply(e,arguments)}},G=function(a,b,c){var d,e,h,i,j,k=D,l=b&&typeof b=="string";if(l&&b.indexOf(" ")>0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e<h;e++)j[e].inNamespaces(i)&&j[e].handler.apply(a,c)}}return a},L=function(a,b,c){var d=0,e=y.get(b,c),f=e.length;for(;d<f;d++)e[d].original&&H(a,e[d].type,e[d].original);return a},M={add:H,one:I,remove:G,clone:L,fire:K,noConflict:function(){return b[a]=d,this}};if(c[i]){var N=function(){var a,b=y.entries();for(a in b)b[a].type&&b[a].type!=="unload"&&G(b[a].element,b[a].type);c[k]("onunload",N),c.CollectGarbage&&c.CollectGarbage()};c[i]("onunload",N)}return M});
//     Underscore.js 1.1.7
//     (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
//     Underscore is freely distributable under the MIT license.
//     Portions of Underscore are inspired or borrowed from Prototype,
//     Oliver Steele's Functional, and John Resig's Micro-Templating.
//     For all details and documentation:
//     http://documentcloud.github.com/underscore

(function(){var a=this,b=a._,c={},d=Array.prototype,e=Object.prototype,f=Function.prototype,g=d.slice,h=d.unshift,i=e.toString,j=e.hasOwnProperty,k=d.forEach,l=d.map,m=d.reduce,n=d.reduceRight,o=d.filter,p=d.every,q=d.some,r=d.indexOf,s=d.lastIndexOf,t=Array.isArray,u=Object.keys,v=f.bind,w=function(a){return new B(a)};typeof module!="undefined"&&module.exports?(module.exports=w,w._=w):a._=w,w.VERSION="1.1.7";var x=w.each=w.forEach=function(a,b,d){if(a==null)return;if(k&&a.forEach===k)a.forEach(b,d);else if(a.length===+a.length){for(var e=0,f=a.length;e<f;e++)if(e in a&&b.call(d,a[e],e,a)===c)return}else for(var g in a)if(j.call(a,g)&&b.call(d,a[g],g,a)===c)return};w.map=function(a,b,c){var d=[];return a==null?d:l&&a.map===l?a.map(b,c):(x(a,function(a,e,f){d[d.length]=b.call(c,a,e,f)}),d)},w.reduce=w.foldl=w.inject=function(a,b,c,d){var e=c!==void 0;a==null&&(a=[]);if(m&&a.reduce===m)return d&&(b=w.bind(b,d)),e?a.reduce(b,c):a.reduce(b);x(a,function(a,f,g){e?c=b.call(d,c,a,f,g):(c=a,e=!0)});if(!e)throw new TypeError("Reduce of empty array with no initial value");return c},w.reduceRight=w.foldr=function(a,b,c,d){a==null&&(a=[]);if(n&&a.reduceRight===n)return d&&(b=w.bind(b,d)),c!==void 0?a.reduceRight(b,c):a.reduceRight(b);var e=(w.isArray(a)?a.slice():w.toArray(a)).reverse();return w.reduce(e,b,c,d)},w.find=w.detect=function(a,b,c){var d;return y(a,function(a,e,f){if(b.call(c,a,e,f))return d=a,!0}),d},w.filter=w.select=function(a,b,c){var d=[];return a==null?d:o&&a.filter===o?a.filter(b,c):(x(a,function(a,e,f){b.call(c,a,e,f)&&(d[d.length]=a)}),d)},w.reject=function(a,b,c){var d=[];return a==null?d:(x(a,function(a,e,f){b.call(c,a,e,f)||(d[d.length]=a)}),d)},w.every=w.all=function(a,b,d){var e=!0;return a==null?e:p&&a.every===p?a.every(b,d):(x(a,function(a,f,g){if(!(e=e&&b.call(d,a,f,g)))return c}),e)};var y=w.some=w.any=function(a,b,d){b=b||w.identity;var e=!1;return a==null?e:q&&a.some===q?a.some(b,d):(x(a,function(a,f,g){if(e|=b.call(d,a,f,g))return c}),!!e)};w.include=w.contains=function(a,b){var c=!1;return a==null?c:r&&a.indexOf===r?a.indexOf(b)!=-1:(y(a,function(a){if(c=a===b)return!0}),c)},w.invoke=function(a,b){var c=g.call(arguments,2);return w.map(a,function(a){return(b.call?b||a:a[b]).apply(a,c)})},w.pluck=function(a,b){return w.map(a,function(a){return a[b]})},w.max=function(a,b,c){if(!b&&w.isArray(a))return Math.max.apply(Math,a);var d={computed:-Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g>=d.computed&&(d={value:a,computed:g})}),d.value},w.min=function(a,b,c){if(!b&&w.isArray(a))return Math.min.apply(Math,a);var d={computed:Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g<d.computed&&(d={value:a,computed:g})}),d.value},w.sortBy=function(a,b,c){return w.pluck(w.map(a,function(a,d,e){return{value:a,criteria:b.call(c,a,d,e)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")},w.groupBy=function(a,b){var c={};return x(a,function(a,d){var e=b(a,d);(c[e]||(c[e]=[])).push(a)}),c},w.sortedIndex=function(a,b,c){c||(c=w.identity);var d=0,e=a.length;while(d<e){var f=d+e>>1;c(a[f])<c(b)?d=f+1:e=f}return d},w.toArray=function(a){return a?a.toArray?a.toArray():w.isArray(a)?g.call(a):w.isArguments(a)?g.call(a):w.values(a):[]},w.size=function(a){return w.toArray(a).length},w.first=w.head=function(a,b,c){return b!=null&&!c?g.call(a,0,b):a[0]},w.rest=w.tail=function(a,b,c){return g.call(a,b==null||c?1:b)},w.last=function(a){return a[a.length-1]},w.compact=function(a){return w.filter(a,function(a){return!!a})},w.flatten=function(a){return w.reduce(a,function(a,b){return w.isArray(b)?a.concat(w.flatten(b)):(a[a.length]=b,a)},[])},w.without=function(a){return w.difference(a,g.call(arguments,1))},w.uniq=w.unique=function(a,b){return w.reduce(a,function(a,c,d){if(0==d||(b===!0?w.last(a)!=c:!w.include(a,c)))a[a.length]=c;return a},[])},w.union=function(){return w.uniq(w.flatten(arguments))},w.intersection=w.intersect=function(a){var b=g.call(arguments,1);return w.filter(w.uniq(a),function(a){return w.every(b,function(b){return w.indexOf(b,a)>=0})})},w.difference=function(a,b){return w.filter(a,function(a){return!w.include(b,a)})},w.zip=function(){var a=g.call(arguments),b=w.max(w.pluck(a,"length")),c=new Array(b);for(var d=0;d<b;d++)c[d]=w.pluck(a,""+d);return c},w.indexOf=function(a,b,c){if(a==null)return-1;var d,e;if(c)return d=w.sortedIndex(a,b),a[d]===b?d:-1;if(r&&a.indexOf===r)return a.indexOf(b);for(d=0,e=a.length;d<e;d++)if(a[d]===b)return d;return-1},w.lastIndexOf=function(a,b){if(a==null)return-1;if(s&&a.lastIndexOf===s)return a.lastIndexOf(b);var c=a.length;while(c--)if(a[c]===b)return c;return-1},w.range=function(a,b,c){arguments.length<=1&&(b=a||0,a=0),c=arguments[2]||1;var d=Math.max(Math.ceil((b-a)/c),0),e=0,f=new Array(d);while(e<d)f[e++]=a,a+=c;return f},w.bind=function(a,b){if(a.bind===v&&v)return v.apply(a,g.call(arguments,1));var c=g.call(arguments,2);return function(){return a.apply(b,c.concat(g.call(arguments)))}},w.bindAll=function(a){var b=g.call(arguments,1);return b.length==0&&(b=w.functions(a)),x(b,function(b){a[b]=w.bind(a[b],a)}),a},w.memoize=function(a,b){var c={};return b||(b=w.identity),function(){var d=b.apply(this,arguments);return j.call(c,d)?c[d]:c[d]=a.apply(this,arguments)}},w.delay=function(a,b){var c=g.call(arguments,2);return setTimeout(function(){return a.apply(a,c)},b)},w.defer=function(a){return w.delay.apply(w,[a,1].concat(g.call(arguments,1)))};var z=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,a.apply(e,f)};c&&clearTimeout(d);if(c||!d)d=setTimeout(g,b)}};w.throttle=function(a,b){return z(a,b,!1)},w.debounce=function(a,b){return z(a,b,!0)},w.once=function(a){var b=!1,c;return function(){return b?c:(b=!0,c=a.apply(this,arguments))}},w.wrap=function(a,b){return function(){var c=[a].concat(g.call(arguments));return b.apply(this,c)}},w.compose=function(){var a=g.call(arguments);return function(){var b=g.call(arguments);for(var c=a.length-1;c>=0;c--)b=[a[c].apply(this,b)];return b[0]}},w.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}},w.keys=u||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)j.call(a,c)&&(b[b.length]=c);return b},w.values=function(a){return w.map(a,w.identity)},w.functions=w.methods=function(a){var b=[];for(var c in a)w.isFunction(a[c])&&b.push(c);return b.sort()},w.extend=function(a){return x(g.call(arguments,1),function(b){for(var c in b)b[c]!==void 0&&(a[c]=b[c])}),a},w.defaults=function(a){return x(g.call(arguments,1),function(b){for(var c in b)a[c]==null&&(a[c]=b[c])}),a},w.clone=function(a){return w.isArray(a)?a.slice():w.extend({},a)},w.tap=function(a,b){return b(a),a},w.isEqual=function(a,b){if(a===b)return!0;var c=typeof a,d=typeof b;if(c!=d)return!1;if(a==b)return!0;if(!a&&b||a&&!b)return!1;a._chain&&(a=a._wrapped),b._chain&&(b=b._wrapped);if(a.isEqual)return a.isEqual(b);if(b.isEqual)return b.isEqual(a);if(w.isDate(a)&&w.isDate(b))return a.getTime()===b.getTime();if(w.isNaN(a)&&w.isNaN(b))return!1;if(w.isRegExp(a)&&w.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(c!=="object")return!1;if(a.length&&a.length!==b.length)return!1;var e=w.keys(a),f=w.keys(b);if(e.length!=f.length)return!1;for(var g in a)if(!(g in b)||!w.isEqual(a[g],b[g]))return!1;return!0},w.isEmpty=function(a){if(w.isArray(a)||w.isString(a))return a.length===0;for(var b in a)if(j.call(a,b))return!1;return!0},w.isElement=function(a){return!!a&&a.nodeType==1},w.isArray=t||function(a){return i.call(a)==="[object Array]"},w.isObject=function(a){return a===Object(a)},w.isArguments=function(a){return!!a&&!!j.call(a,"callee")},w.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)},w.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)},w.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)},w.isNaN=function(a){return a!==a},w.isBoolean=function(a){return a===!0||a===!1},w.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)},w.isRegExp=function(a){return!(!(a&&a.test&&a.exec)||!a.ignoreCase&&a.ignoreCase!==!1)},w.isNull=function(a){return a===null},w.isUndefined=function(a){return a===void 0},w.noConflict=function(){return a._=b,this},w.identity=function(a){return a},w.times=function(a,b,c){for(var d=0;d<a;d++)b.call(c,d)},w.mixin=function(a){x(w.functions(a),function(b){D(b,w[b]=a[b])})};var A=0;w.uniqueId=function(a){var b=A++;return a?a+b:b},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g},w.template=function(a,b){var c=w.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(c.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(c.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj",d);return b?e(b):e};var B=function(a){this._wrapped=a};w.prototype=B.prototype;var C=function(a,b){return b?w(a).chain():a},D=function(a,b){B.prototype[a]=function(){var a=g.call(arguments);return h.call(a,this._wrapped),C(b.apply(w,a),this._chain)}};w.mixin(w),x(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];B.prototype[a]=function(){return b.apply(this._wrapped,arguments),C(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(a){var b=d[a];B.prototype[a]=function(){return C(b.apply(this._wrapped,arguments),this._chain)}}),B.prototype.chain=function(){return this._chain=!0,this},B.prototype.value=function(){return this._wrapped}})();
/**
 * Flotr2 (c) 2012 Carl Sutherland
 * MIT License
 * Special thanks to:
 * Flotr: http://code.google.com/p/flotr/ (fork)
 * Flot: https://github.com/flot/flot (original fork)
 */
(function(){var a=this,b=this.Flotr,c;c={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(a,b){c.graphTypes[a]=b,c.defaultOptions[a]=b.options||{},c.defaultOptions.defaultType=c.defaultOptions.defaultType||a},addPlugin:function(a,b){c.plugins[a]=b,c.defaultOptions[a]=b.options||{}},draw:function(a,b,d,e){return e=e||c.Graph,new e(a,b,d)},merge:function(a,b){var d,e,f=b||{};for(d in a)e=a[d],e&&typeof e=="object"?e.constructor===Array?f[d]=this._.clone(e):e.constructor!==RegExp&&!this._.isElement(e)?f[d]=c.merge(e,b?b[d]:undefined):f[d]=e:f[d]=e;return f},clone:function(a){return c.merge(a,{})},getTickSize:function(a,b,d,e){var f=(d-b)/a,g=c.getMagnitude(f),h=10,i=f/g;return i<1.5?h=1:i<2.25?h=2:i<3?h=e===0?2:2.5:i<7.5&&(h=5),h*g},defaultTickFormatter:function(a,b){return a+""},defaultTrackFormatter:function(a){return"("+a.x+", "+a.y+")"},engineeringNotation:function(a,b,c){var d=["Y","Z","E","P","T","G","M","k",""],e=["y","z","a","f","p","n","Âµ","m",""],f=d.length;c=c||1e3,b=Math.pow(10,b||2);if(a===0)return 0;if(a>1)while(f--&&a>=c)a/=c;else{d=e,f=d.length;while(f--&&a<1)a*=c}return Math.round(a*b)/b+d[f]},getMagnitude:function(a){return Math.pow(10,Math.floor(Math.log(a)/Math.LN10))},toPixel:function(a){return Math.floor(a)+.5},toRad:function(a){return-a*(Math.PI/180)},floorInBase:function(a,b){return b*Math.floor(a/b)},drawText:function(a,b,d,e,f){if(!a.fillText){a.drawText(b,d,e,f);return}f=this._.extend({size:c.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},f),a.save(),a.translate(d,e),a.rotate(f.angle),a.fillStyle=f.color,a.font=(f.weight>1?"bold ":"")+f.size*1.3+"px sans-serif",a.textAlign=f.textAlign,a.textBaseline=f.textBaseline,a.fillText(b,0,0),a.restore()},getBestTextAlign:function(a,b){return b=b||{textAlign:"center",textBaseline:"middle"},a+=c.getTextAngleFromAlign(b),Math.abs(Math.cos(a))>.01&&(b.textAlign=Math.cos(a)>0?"right":"left"),Math.abs(Math.sin(a))>.01&&(b.textBaseline=Math.sin(a)>0?"top":"bottom"),b},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(a){return c.alignTable[a.textAlign+" "+a.textBaseline]||0},noConflict:function(){return a.Flotr=b,this}},a.Flotr=c})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function b(a,b,c,d){this.rgba=["r","g","b","a"];var e=4;while(-1<--e)this[this.rgba[e]]=arguments[e]||(e==3?1:0);this.normalize()}var a=Flotr._,c={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};b.prototype={scale:function(b,c,d,e){var f=4;while(-1<--f)a.isUndefined(arguments[f])||(this[this.rgba[f]]*=arguments[f]);return this.normalize()},alpha:function(b){return!a.isUndefined(b)&&!a.isNull(b)&&(this.a=b),this.normalize()},clone:function(){return new b(this.r,this.b,this.g,this.a)},limit:function(a,b,c){return Math.max(Math.min(a,c),b)},normalize:function(){var a=this.limit;return this.r=a(parseInt(this.r,10),0,255),this.g=a(parseInt(this.g,10),0,255),this.b=a(parseInt(this.b,10),0,255),this.a=a(this.a,0,1),this},distance:function(a){if(!a)return;a=new b.parse(a);var c=0,d=3;while(-1<--d)c+=Math.abs(this[this.rgba[d]]-a[this.rgba[d]]);return c},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var a=1-(.299*this.r+.587*this.g+.114*this.b)/255;return a<.5?"#000000":"#ffffff"}},a.extend(b,{parse:function(a){if(a instanceof b)return a;var d;if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return new b(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16));if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10));if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return new b(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16));if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]));if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55);if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]));var e=(a+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return e=="transparent"?new b(255,255,255,0):(d=c[e])?new b(d[0],d[1],d[2]):new b(0,0,0,0)},processColor:function(c,d){var e=d.opacity;if(!c)return"rgba(0, 0, 0, 0)";if(c instanceof b)return c.alpha(e).toString();if(a.isString(c))return b.parse(c).alpha(e).toString();var f=c.colors?c:{colors:c};if(!d.ctx)return a.isArray(f.colors)?b.parse(a.isArray(f.colors[0])?f.colors[0][1]:f.colors[0]).alpha(e).toString():"rgba(0, 0, 0, 0)";f=a.extend({start:"top",end:"bottom"},f),/top/i.test(f.start)&&(d.x1=0),/left/i.test(f.start)&&(d.y1=0),/bottom/i.test(f.end)&&(d.x2=0),/right/i.test(f.end)&&(d.y2=0);var g,h,i,j=d.ctx.createLinearGradient(d.x1,d.y1,d.x2,d.y2);for(g=0;g<f.colors.length;g++)h=f.colors[g],a.isArray(h)?(i=h[0],h=h[1]):i=g/(f.colors.length-1),j.addColorStop(i,b.parse(h).alpha(e));return j}}),Flotr.Color=b}(),Flotr.Date={set:function(a,b,c,d){c=c||"UTC",b="set"+(c==="UTC"?"UTC":"")+b,a[b](d)},get:function(a,b,c){return c=c||"UTC",b="get"+(c==="UTC"?"UTC":"")+b,a[b]()},format:function(a,b,c){function f(a){return a+="",a.length==1?"0"+a:a}if(!a)return;var d=this.get,e={h:d(a,"Hours",c).toString(),H:f(d(a,"Hours",c)),M:f(d(a,"Minutes",c)),S:f(d(a,"Seconds",c)),s:d(a,"Milliseconds",c),d:d(a,"Date",c).toString(),m:(d(a,"Month")+1).toString(),y:d(a,"FullYear").toString(),b:Flotr.Date.monthNames[d(a,"Month",c)]},g=[],h,i=!1;for(var j=0;j<b.length;++j)h=b.charAt(j),i?(g.push(e[h]||h),i=!1):h=="%"?i=!0:g.push(h);return g.join("")},getFormat:function(a,b){var c=Flotr.Date.timeUnits;return a<c.second?"%h:%M:%S.%s":a<c.minute?"%h:%M:%S":a<c.day?b<2*c.day?"%h:%M":"%b %d %h:%M":a<c.month?"%b %d":a<c.year?b<c.year?"%b":"%b %y":"%y"},formatter:function(a,b){var c=b.options,d=Flotr.Date.timeUnits[c.timeUnit],e=new Date(a*d);if(b.options.timeFormat)return Flotr.Date.format(e,c.timeFormat,c.timeMode);var f=(b.max-b.min)*d,g=b.tickSize*Flotr.Date.timeUnits[b.tickUnit];return Flotr.Date.format(e,Flotr.Date.getFormat(g,f),c.timeMode)},generator:function(a){function s(a){b(q,a,g,Flotr.floorInBase(c(q,a,g),m))}var b=this.set,c=this.get,d=this.timeUnits,e=this.spec,f=a.options,g=f.timeMode,h=d[f.timeUnit],i=a.min*h,j=a.max*h,k=(j-i)/f.noTicks,l=[],m=a.tickSize,n,o,p;o=f.tickFormatter===Flotr.defaultTickFormatter?this.formatter:f.tickFormatter;for(p=0;p<e.length-1;++p){var q=e[p][0]*d[e[p][1]];if(k<(q+e[p+1][0]*d[e[p+1][1]])/2&&q>=m)break}m=e[p][0],n=e[p][1],n=="year"&&(m=Flotr.getTickSize(f.noTicks*d.year,i,j,0),m==.5&&(n="month",m=6)),a.tickUnit=n,a.tickSize=m;var q=new Date(i),r=m*d[n];switch(n){case"millisecond":s("Milliseconds");break;case"second":s("Seconds");break;case"minute":s("Minutes");break;case"hour":s("Hours");break;case"month":s("Month");break;case"year":s("FullYear")}r>=d.second&&b(q,"Milliseconds",g,0),r>=d.minute&&b(q,"Seconds",g,0),r>=d.hour&&b(q,"Minutes",g,0),r>=d.day&&b(q,"Hours",g,0),r>=d.day*4&&b(q,"Date",g,1),r>=d.year&&b(q,"Month",g,0);var t=0,u=NaN,v;do{v=u,u=q.getTime(),l.push({v:u/h,label:o(u/h,a)});if(n=="month")if(m<1){b(q,"Date",g,1);var w=q.getTime();b(q,"Month",g,c(q,"Month",g)+1);var x=q.getTime();q.setTime(u+t*d.hour+(x-w)*m),t=c(q,"Hours",g),b(q,"Hours",g,0)}else b(q,"Month",g,c(q,"Month",g)+m);else n=="year"?b(q,"FullYear",g,c(q,"FullYear",g)+m):q.setTime(u+r)}while(u<j&&u!=v);return l},timeUnits:{millisecond:1,second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,year:31556952e3},spec:[[1,"millisecond"],[20,"millisecond"],[50,"millisecond"],[100,"millisecond"],[200,"millisecond"],[500,"millisecond"],[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},function(){var a=Flotr._;Flotr.DOM={addClass:function(b,c){var d=b.className?b.className:"";if(a.include(d.split(/\s+/g),c))return;b.className=(d?d+" ":"")+c},create:function(a){return document.createElement(a)},node:function(a){var b=Flotr.DOM.create("div"),c;return b.innerHTML=a,c=b.children[0],b.innerHTML="",c},empty:function(a){a.innerHTML=""},hide:function(a){Flotr.DOM.setStyles(a,{display:"none"})},insert:function(b,c){a.isString(c)?b.innerHTML+=c:a.isElement(c)&&b.appendChild(c)},opacity:function(a,b){a.style.opacity=b},position:function(a,b){return a.offsetParent?(b=this.position(a.offsetParent),b.left+=a.offsetLeft,b.top+=a.offsetTop,b):{left:a.offsetLeft||0,top:a.offsetTop||0}},removeClass:function(b,c){var d=b.className?b.className:"";b.className=a.filter(d.split(/\s+/g),function(a){if(a!=c)return!0}).join(" ")},setStyles:function(b,c){a.each(c,function(a,c){b.style[c]=a})},show:function(a){Flotr.DOM.setStyles(a,{display:""})},size:function(a){return{height:a.offsetHeight,width:a.offsetWidth}}}}(),function(){var a=Flotr,b=a.bean;a.EventAdapter={observe:function(a,c,d){return b.add(a,c,d),this},fire:function(a,c,d){return b.fire(a,c,d),typeof Prototype!="undefined"&&Event.fire(a,c,d),this},stopObserving:function(a,c,d){return b.remove(a,c,d),this},eventPointer:function(b){if(!a._.isUndefined(b.touches)&&b.touches.length>0)return{x:b.touches[0].pageX,y:b.touches[0].pageY};if(!a._.isUndefined(b.changedTouches)&&b.changedTouches.length>0)return{x:b.changedTouches[0].pageX,y:b.changedTouches[0].pageY};if(b.pageX||b.pageY)return{x:b.pageX,y:b.pageY};if(b.clientX||b.clientY){var c=document,d=c.body,e=c.documentElement;return{x:b.clientX+d.scrollLeft+e.scrollLeft,y:b.clientY+d.scrollTop+e.scrollTop}}}}}(),function(){var a=Flotr,b=a.DOM,c=a._,d=function(a){this.o=a};d.prototype={dimensions:function(a,b,c,d){return a?this.o.html?this.html(a,this.o.element,c,d):this.canvas(a,b):{width:0,height:0}},canvas:function(b,c){if(!this.o.textEnabled)return;c=c||{};var d=this.measureText(b,c),e=d.width,f=c.size||a.defaultOptions.fontSize,g=c.angle||0,h=Math.cos(g),i=Math.sin(g),j=2,k=6,l;return l={width:Math.abs(h*e)+Math.abs(i*f)+j,height:Math.abs(i*e)+Math.abs(h*f)+k},l},html:function(a,c,d,e){var f=b.create("div");return b.setStyles(f,{position:"absolute",top:"-10000px"}),b.insert(f,'<div style="'+d+'" class="'+e+' flotr-dummy-div">'+a+"</div>"),b.insert(this.o.element,f),b.size(f)},measureText:function(b,d){var e=this.o.ctx,f;return!e.fillText||a.isIphone&&e.measure?{width:e.measure(b,d)}:(d=c.extend({size:a.defaultOptions.fontSize,weight:1,angle:0},d),e.save(),e.font=(d.weight>1?"bold ":"")+d.size*1.3+"px sans-serif",f=e.measureText(b),e.restore(),f)}},Flotr.Text=d}(),function(){function e(a,c,d){return b.observe.apply(this,arguments),this._handles.push(arguments),this}var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d=Flotr;Graph=function(a,e,f){this._setEl(a),this._initMembers(),this._initPlugins(),b.fire(this.el,"flotr:beforeinit",[this]),this.data=e,this.series=d.Series.getSeries(e),this._initOptions(f),this._initGraphTypes(),this._initCanvas(),this._text=new d.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),b.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(c.bind(function(){b.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){b.fire(this.el,"flotr:destroy"),c.each(this._handles,function(a){b.stopObserving.apply(this,a)}),this._handles=[],this.el.graph=null},observe:e,_observe:e,processColor:function(a,b){var e={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return c.extend(e,b),d.Color.processColor(a,e)},findDataRanges:function(){var a=this.axes,b,e,f;c.each(this.series,function(a){f=a.getRange(),f&&(b=a.xaxis,e=a.yaxis,b.datamin=Math.min(f.xmin,b.datamin),b.datamax=Math.max(f.xmax,b.datamax),e.datamin=Math.min(f.ymin,e.datamin),e.datamax=Math.max(f.ymax,e.datamax),b.used=b.used||f.xused,e.used=e.used||f.yused)},this),!a.x.used&&!a.x2.used&&(a.x.used=!0),!a.y.used&&!a.y2.used&&(a.y.used=!0),c.each(a,function(a){a.calculateRange()});var g=c.keys(d.graphTypes),h=!1;c.each(this.series,function(a){if(a.hide)return;c.each(g,function(b){a[b]&&a[b].show&&(this.extendRange(b,a),h=!0)},this),h||this.extendRange(this.options.defaultType,a)},this)},extendRange:function(a,b){this[a].extendRange&&this[a].extendRange(b,b.data,b[a],this[a]),this[a].extendYRange&&this[a].extendYRange(b.yaxis,b.data,b[a],this[a]),this[a].extendXRange&&this[a].extendXRange(b.xaxis,b.data,b[a],this[a])},calculateSpacing:function(){var a=this.axes,b=this.options,d=this.series,e=b.grid.labelMargin,f=this._text,g=a.x,h=a.x2,i=a.y,j=a.y2,k=b.grid.outlineWidth,l,m,n,o;c.each(a,function(a){a.calculateTicks(),a.calculateTextDimensions(f,b)}),o=f.dimensions(b.title,{size:b.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=o.height,o=f.dimensions(b.subtitle,{size:b.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=o.height;for(m=0;m<b.length;++m)d[m].points.show&&(k=Math.max(k,d[m].points.radius+d[m].points.lineWidth/2));var p=this.plotOffset;g.options.margin===!1?(p.bottom=0,p.top=0):(p.bottom+=(b.grid.circular?0:g.used&&g.options.showLabels?g.maxLabel.height+e:0)+(g.used&&g.options.title?g.titleSize.height+e:0)+k,p.top+=(b.grid.circular?0:h.used&&h.options.showLabels?h.maxLabel.height+e:0)+(h.used&&h.options.title?h.titleSize.height+e:0)+this.subtitleHeight+this.titleHeight+k),i.options.margin===!1?(p.left=0,p.right=0):(p.left+=(b.grid.circular?0:i.used&&i.options.showLabels?i.maxLabel.width+e:0)+(i.used&&i.options.title?i.titleSize.width+e:0)+k,p.right+=(b.grid.circular?0:j.used&&j.options.showLabels?j.maxLabel.width+e:0)+(j.used&&j.options.title?j.titleSize.width+e:0)+k),p.top=Math.floor(p.top),this.plotWidth=this.canvasWidth-p.left-p.right,this.plotHeight=this.canvasHeight-p.bottom-p.top,g.length=h.length=this.plotWidth,i.length=j.length=this.plotHeight,i.offset=j.offset=this.plotHeight,g.setScale(),h.setScale(),i.setScale(),j.setScale()},draw:function(a){var c=this.ctx,d;b.fire(this.el,"flotr:beforedraw",[this.series,this]);if(this.series.length){c.save(),c.translate(this.plotOffset.left,this.plotOffset.top);for(d=0;d<this.series.length;d++)this.series[d].hide||this.drawSeries(this.series[d]);c.restore(),this.clip()}b.fire(this.el,"flotr:afterdraw",[this.series,this]),a&&a()},drawSeries:function(a){function b(a,b){var c=this.getOptions(a,b);this[b].draw(c)}var e=!1;a=a||this.series,c.each(d.graphTypes,function(c,d){a[d]&&a[d].show&&this[d]&&(e=!0,b.call(this,a,d))},this),e||b.call(this,a,this.options.defaultType)},getOptions:function(a,b){var e=a[b],f=this[b],g={context:this.ctx,width:this.plotWidth,height:this.plotHeight,fontSize:this.options.fontSize,fontColor:this.options.fontColor,textEnabled:this.textEnabled,htmlText:this.options.HtmlText,text:this._text,element:this.el,data:a.data,color:a.color,shadowSize:a.shadowSize,xScale:c.bind(a.xaxis.d2p,a.xaxis),yScale:c.bind(a.yaxis.d2p,a.yaxis)};return g=d.merge(e,g),g.fillStyle=this.processColor(e.fillColor||a.color,{opacity:e.fillOpacity}),g},getEventPosition:function(c){var d=document,e=d.body,f=d.documentElement,g=this.axes,h=this.plotOffset,i=this.lastMousePos,j=b.eventPointer(c),k=j.x-i.pageX,l=j.y-i.pageY,m,n,o;return"ontouchstart"in this.el?(m=a.position(this.overlay),n=j.x-m.left-h.left,o=j.y-m.top-h.top):(m=this.overlay.getBoundingClientRect(),n=c.clientX-m.left-h.left-e.scrollLeft-f.scrollLeft,o=c.clientY-m.top-h.top-e.scrollTop-f.scrollTop),{x:g.x.p2d(n),x2:g.x2.p2d(n),y:g.y.p2d(o),y2:g.y2.p2d(o),relX:n,relY:o,dX:k,dY:l,absX:j.x,absY:j.y,pageX:j.x,pageY:j.y}},clickHandler:function(a){if(this.ignoreClick)return this.ignoreClick=!1,this.ignoreClick;b.fire(this.el,"flotr:click",[this.getEventPosition(a),this])},mouseMoveHandler:function(a){if(this.mouseDownMoveHandler)return;var c=this.getEventPosition(a);b.fire(this.el,"flotr:mousemove",[a,c,this]),this.lastMousePos=c},mouseDownHandler:function(a){if(this.mouseUpHandler)return;this.mouseUpHandler=c.bind(function(a){b.stopObserving(document,"mouseup",this.mouseUpHandler),b.stopObserving(document,"mousemove",this.mouseDownMoveHandler),this.mouseDownMoveHandler=null,this.mouseUpHandler=null,b.fire(this.el,"flotr:mouseup",[a,this])},this),this.mouseDownMoveHandler=c.bind(function(c){var d=this.getEventPosition(c);b.fire(this.el,"flotr:mousemove",[a,d,this]),this.lastMousePos=d},this),b.observe(document,"mouseup",this.mouseUpHandler),b.observe(document,"mousemove",this.mouseDownMoveHandler),b.fire(this.el,"flotr:mousedown",[a,this]),this.ignoreClick=!1},drawTooltip:function(b,c,d,e){var f=this.getMouseTrack(),g="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;",h=e.position,i=e.margin,j=this.plotOffset;c!==null&&d!==null?(e.relative?(h.charAt(0)=="n"?g+="bottom:"+(i-j.top-d+this.canvasHeight)+"px;top:auto;":h.charAt(0)=="s"&&(g+="top:"+(i+j.top+d)+"px;bottom:auto;"),h.charAt(1)=="e"?g+="left:"+(i+j.left+c)+"px;right:auto;":h.charAt(1)=="w"&&(g+="right:"+(i-j.left-c+this.canvasWidth)+"px;left:auto;")):(h.charAt(0)=="n"?g+="top:"+(i+j.top)+"px;bottom:auto;":h.charAt(0)=="s"&&(g+="bottom:"+(i+j.bottom)+"px;top:auto;"),h.charAt(1)=="e"?g+="right:"+(i+j.right)+"px;left:auto;":h.charAt(1)=="w"&&(g+="left:"+(i+j.left)+"px;right:auto;")),f.style.cssText=g,a.empty(f),a.insert(f,b),a.show(f)):a.hide(f)},clip:function(){var a=this.ctx,b=this.plotOffset,c=this.canvasWidth,e=this.canvasHeight;d.isIE&&d.isIE<9?(a.save(),a.fillStyle=this.processColor(this.options.ieBackgroundColor),a.fillRect(0,0,c,b.top),a.fillRect(0,0,b.left,e),a.fillRect(0,e-b.bottom,c,b.bottom),a.fillRect(c-b.right,0,b.right,e),a.restore()):(a.clearRect(0,0,c,b.top),a.clearRect(0,0,b.left,e),a.clearRect(0,e-b.bottom,c,b.bottom),a.clearRect(c-b.right,0,b.right,e))},_initMembers:function(){this._handles=[],this.lastMousePos={pageX:null,pageY:null},this.plotOffset={left:0,right:0,top:0,bottom:0},this.ignoreClick=!0,this.prevHit=null},_initGraphTypes:function(){c.each(d.graphTypes,function(a,b){this[b]=d.clone(a)},this)},_initEvents:function(){var a=this.el,d,e,f;"ontouchstart"in a?(d=c.bind(function(c){f=!0,b.stopObserving(document,"touchend",d),b.fire(a,"flotr:mouseup",[event,this]),this.multitouches=null,e||this.clickHandler(c)},this),this.observe(this.overlay,"touchstart",c.bind(function(c){e=!1,f=!1,this.ignoreClick=!1,c.touches&&c.touches.length>1&&(this.multitouches=c.touches),b.fire(a,"flotr:mousedown",[event,this]),this.observe(document,"touchend",d)},this)),this.observe(this.overlay,"touchmove",c.bind(function(c){var d=this.getEventPosition(c);c.preventDefault(),e=!0,this.multitouches||c.touches&&c.touches.length>1?this.multitouches=c.touches:f||b.fire(a,"flotr:mousemove",[event,d,this]),this.lastMousePos=d},this))):this.observe(this.overlay,"mousedown",c.bind(this.mouseDownHandler,this)).observe(a,"mousemove",c.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",c.bind(this.clickHandler,this)).observe(a,"mouseout",function(){b.fire(a,"flotr:mouseout")})},_initCanvas:function(){function k(e,f){return e||(e=a.create("canvas"),typeof FlashCanvas!="undefined"&&typeof e.getContext=="function"&&FlashCanvas.initElement(e),e.className="flotr-"+f,e.style.cssText="position:absolute;left:0px;top:0px;",a.insert(b,e)),c.each(i,function(b,c){a.show(e);if(f=="canvas"&&e.getAttribute(c)===b)return;e.setAttribute(c,b*d.resolution),e.style[c]=b+"px"}),e.context_=null,e}function l(a){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(a);var b=a.getContext("2d");return window.G_vmlCanvasManager||b.scale(d.resolution,d.resolution),b}var b=this.el,d=this.options,e=b.children,f=[],g,h,i,j;for(h=e.length;h--;)g=e[h],!this.canvas&&g.className==="flotr-canvas"?this.canvas=g:!this.overlay&&g.className==="flotr-overlay"?this.overlay=g:f.push(g);for(h=f.length;h--;)b.removeChild(f[h]);a.setStyles(b,{position:"relative"}),i={},i.width=b.clientWidth,i.height=b.clientHeight;if(i.width<=0||i.height<=0||d.resolution<=0)throw"Invalid dimensions for plot, width = "+i.width+", height = "+i.height+", resolution = "+d.resolution;this.canvas=k(this.canvas,"canvas"),this.overlay=k(this.overlay,"overlay"),this.ctx=l(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=l(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=i.height,this.canvasWidth=i.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){c.each(d.plugins,function(a,b){c.each(a.callbacks,function(a,b){this.observe(this.el,b,c.bind(a,this))},this),this[b]=d.clone(a),c.each(this[b],function(a,d){c.isFunction(a)&&(this[b][d]=c.bind(a,this))},this)},this)},_initOptions:function(a){var e=d.clone(d.defaultOptions);e.x2axis=c.extend(c.clone(e.xaxis),e.x2axis),e.y2axis=c.extend(c.clone(e.yaxis),e.y2axis),this.options=d.merge(a||{},e),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),b.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=d.Axis.getAxes(this.options);var f=[],g=[],h=this.series.length,i=this.series.length,j=this.options.colors,k=[],l=0,m,n,o,p;for(n=i-1;n>-1;--n)m=this.series[n].color,m&&(--i,c.isNumber(m)?f.push(m):k.push(d.Color.parse(m)));for(n=f.length-1;n>-1;--n)i=Math.max(i,f[n]+1);for(n=0;g.length<i;){m=j.length==n?new d.Color(100,100,100):d.Color.parse(j[n]);var q=l%2==1?-1:1,r=1+q*Math.ceil(l/2)*.2;m.scale(r,r,r),g.push(m),++n>=j.length&&(n=0,++l)}for(n=0,o=0;n<h;++n){p=this.series[n],p.color?c.isNumber(p.color)&&(p.color=g[p.color].toString()):p.color=g[o++].toString(),p.xaxis||(p.xaxis=this.axes.x),p.xaxis==1?p.xaxis=this.axes.x:p.xaxis==2&&(p.xaxis=this.axes.x2),p.yaxis||(p.yaxis=this.axes.y),p.yaxis==1?p.yaxis=this.axes.y:p.yaxis==2&&(p.yaxis=this.axes.y2);for(var s in d.graphTypes)p[s]=c.extend(c.clone(this.options[s]),p[s]);p.mouse=c.extend(c.clone(this.options.mouse),p.mouse),c.isUndefined(p.shadowSize)&&(p.shadowSize=this.options.shadowSize)}},_setEl:function(a){if(!a)throw"The target container doesn't exist";if(a.graph instanceof Graph)a.graph.destroy();else if(!a.clientWidth)throw"The target container must be visible";a.graph=this,this.el=a}},Flotr.Graph=Graph}(),function(){function c(b){this.orientation=1,this.offset=0,this.datamin=Number.MAX_VALUE,this.datamax=-Number.MAX_VALUE,a.extend(this,b),this._setTranslations()}function d(a){return this.offset+this.orientation*(a-this.min)*this.scale}function e(a){return(this.offset+this.orientation*a)/this.scale+this.min}function f(a){return this.offset+this.orientation*(h(a,this.options.base)-h(this.min,this.options.base))*this.scale}function g(a){return j((this.offset+this.orientation*a)/this.scale+h(this.min,this.options.base),this.options.base)}function h(a,b){return a=Math.log(Math.max(a,Number.MIN_VALUE)),b!==Math.E&&(a/=Math.log(b)),a}function j(a,b){return b===Math.E?Math.exp(a):Math.pow(b,a)}var a=Flotr._,b="logarithmic";c.prototype={setScale:function(){var a=this.length;this.options.scaling==b?this.scale=a/(h(this.max,this.options.base)-h(this.min,this.options.base)):this.scale=a/(this.max-this.min)},calculateTicks:function(){var a=this.options;this.ticks=[],this.minorTicks=[],a.ticks?(this._cleanUserTicks(a.ticks,this.ticks),this._cleanUserTicks(a.minorTicks||[],this.minorTicks)):a.mode=="time"?this._calculateTimeTicks():a.scaling==="logarithmic"?this._calculateLogTicks():this._calculateTicks()},calculateRange:function(){if(!this.used)return;var a=this,b=a.options,c=b.min!==null?b.min:a.datamin,d=b.max!==null?b.max:a.datamax,e=b.autoscaleMargin;b.scaling=="logarithmic"&&(c<=0&&(c=a.datamin),d<=0&&(d=c));if(d==c){var f=d?.01:1;b.min===null&&(c-=f),b.max===null&&(d+=f)}if(b.scaling==="logarithmic"){c<0&&(c=d/b.base);var g=Math.log(d);b.base!=Math.E&&(g/=Math.log(b.base)),g=Math.ceil(g);var h=Math.log(c);b.base!=Math.E&&(h/=Math.log(b.base)),h=Math.ceil(h),a.tickSize=Flotr.getTickSize(b.noTicks,h,g,b.tickDecimals===null?0:b.tickDecimals),b.minorTickFreq===null&&(g-h>10?b.minorTickFreq=0:g-h>5?b.minorTickFreq=2:b.minorTickFreq=5)}else a.tickSize=Flotr.getTickSize(b.noTicks,c,d,b.tickDecimals);a.min=c,a.max=d,b.min===null&&b.autoscale&&(a.min-=a.tickSize*e,a.min<0&&a.datamin>=0&&(a.min=0),a.min=a.tickSize*Math.floor(a.min/a.tickSize)),b.max===null&&b.autoscale&&(a.max+=a.tickSize*e,a.max>0&&a.datamax<=0&&a.datamax!=a.datamin&&(a.max=0),a.max=a.tickSize*Math.ceil(a.max/a.tickSize)),a.min==a.max&&(a.max=a.min+1)},calculateTextDimensions:function(a,b){var c="",d,e;if(this.options.showLabels)for(e=0;e<this.ticks.length;++e)d=this.ticks[e].label.length,d>c.length&&(c=this.ticks[e].label);this.maxLabel=a.dimensions(c,{size:b.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=a.dimensions(this.options.title,{size:b.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(b,c){var d=this,e=this.options,f,g,h,i;a.isFunction(b)&&(b=b({min:d.min,max:d.max}));for(g=0;g<b.length;++g)i=b[g],typeof i=="object"?(f=i[0],h=i.length>1?i[1]:e.tickFormatter(f,{min:d.min,max:d.max})):(f=i,h=e.tickFormatter(f,{min:this.min,max:this.max})),c[g]={v:f,label:h}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var a=this,b=a.options,c,d,e=Math.log(a.max);b.base!=Math.E&&(e/=Math.log(b.base)),e=Math.ceil(e);var f=Math.log(a.min);b.base!=Math.E&&(f/=Math.log(b.base)),f=Math.ceil(f);for(i=f;i<e;i+=a.tickSize){d=b.base==Math.E?Math.exp(i):Math.pow(b.base,i);var g=d*(b.base==Math.E?Math.exp(a.tickSize):Math.pow(b.base,a.tickSize)),h=(g-d)/b.minorTickFreq;a.ticks.push({v:d,label:b.tickFormatter(d,{min:a.min,max:a.max})});for(c=d+h;c<g;c+=h)a.minorTicks.push({v:c,label:b.tickFormatter(c,{min:a.min,max:a.max})})}d=b.base==Math.E?Math.exp(i):Math.pow(b.base,i),a.ticks.push({v:d,label:b.tickFormatter(d,{min:a.min,max:a.max})})},_calculateTicks:function(){var a=this,b=a.options,c=a.tickSize,d=a.min,e=a.max,f=c*Math.ceil(d/c),g,h,i,j,k,l;b.minorTickFreq&&(h=c/b.minorTickFreq);for(k=0;(i=j=f+k*c)<=e;++k){g=b.tickDecimals,g===null&&(g=1-Math.floor(Math.log(c)/Math.LN10)),g<0&&(g=0),i=i.toFixed(g),a.ticks.push({v:i,label:b.tickFormatter(i,{min:a.min,max:a.max})});if(b.minorTickFreq)for(l=0;l<b.minorTickFreq&&k*c+l*h<e;++l)i=j+l*h,a.minorTicks.push({v:i,label:b.tickFormatter(i,{min:a.min,max:a.max})})}},_setTranslations:function(a){this.d2p=a?f:d,this.p2d=a?g:e}},a.extend(c,{getAxes:function(a){return{x:new c({options:a.xaxis,n:1,length:this.plotWidth}),x2:new c({options:a.x2axis,n:2,length:this.plotWidth}),y:new c({options:a.yaxis,n:1,length:this.plotHeight,offset:this.plotHeight,orientation:-1}),y2:new c({options:a.y2axis,n:2,length:this.plotHeight,offset:this.plotHeight,orientation:-1})}}}),Flotr.Axis=c}(),function(){function b(b){a.extend(this,b)}var a=Flotr._;b.prototype={getRange:function(){var a=this.data,b=a.length,c=Number.MAX_VALUE,d=Number.MAX_VALUE,e=-Number.MAX_VALUE,f=-Number.MAX_VALUE,g=!1,h=!1,i,j,k;if(b<0||this.hide)return!1;for(k=0;k<b;k++)i=a[k][0],j=a[k][1],i<c&&(c=i,g=!0),i>e&&(e=i,g=!0),j<d&&(d=j,h=!0),j>f&&(f=j,h=!0);return{xmin:c,xmax:e,ymin:d,ymax:f,xused:g,yused:h}}},a.extend(b,{getSeries:function(c){return a.map(c,function(c){var d;return c.data?(d=new b,a.extend(d,c)):d=new b({data:c}),d})}}),Flotr.Series=b}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize,e;b.save(),b.lineJoin="round",d&&(b.lineWidth=d/2,e=c/2+b.lineWidth/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,e+d/2,!1),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,e,!1)),b.lineWidth=c,b.strokeStyle=a.color,this.plot(a,0,!0),b.restore()},plot:function(a,b,c){var d=a.context,e=a.width,f=a.height,g=a.xScale,h=a.yScale,i=a.data,j=a.stacked?this.stack:!1,k=i.length-1,l=null,m=null,n=h(0),o,p,q,r,s,t,u;if(k<1)return;d.beginPath();for(u=0;u<k;++u){if(i[u][1]===null||i[u+1][1]===null)continue;o=g(i[u][0]),p=g(i[u+1][0]),j?(s=j.values[i[u][0]]||0,t=j.values[i[u+1][0]]||j.values[i[u][0]]||0,q=h(i[u][1]+s),r=h(i[u+1][1]+t),c&&(j.values[i[u][0]]=i[u][1]+s,u==k-1&&(j.values[i[u+1][0]]=i[u+1][1]+t))):(q=h(i[u][1]),r=h(i[u+1][1]));if(q>f&&r>f||q<0&&r<0||o<0&&p<0||o>e&&p>e)continue;(l!=o||m!=q+b)&&d.moveTo(o,q+b),l=p,m=r+b,a.steps?(d.lineTo(l+b/2,q+b),d.lineTo(l+b/2,m)):d.lineTo(l,m)}(!a.fill||a.fill&&!a.fillBorder)&&d.stroke(),!b&&a.fill&&(o=g(i[0][0]),d.fillStyle=a.fillStyle,d.lineTo(p,n),d.lineTo(o,n),d.lineTo(o,h(i[0][1])),d.fill(),a.fillBorder&&d.stroke()),d.closePath()},extendYRange:function(a,b,c,d){var e=a.options;if(c.stacked&&(!e.max&&e.max!==0||!e.min&&e.min!==0)){var f=a.max,g=a.min,h=d.positiveSums||{},i=d.negativeSums||{},j,k;for(k=0;k<b.length;k++)j=b[k][0]+"",b[k][1]>0?(h[j]=(h[j]||0)+b[k][1],f=Math.max(f,h[j])):(i[j]=(i[j]||0)+b[k][1],g=Math.min(g,i[j]));d.negativeSums=i,d.positiveSums=h,a.max=f,a.min=g}c.steps&&(this.hit=function(a){var b=a.data,c=a.args,d=a.yScale,e=c[0],f=b.length,g=c[1],h=e.x,i=e.relY,j;for(j=0;j<f-1;j++)if(h>=b[j][0]&&h<=b[j+1][0]){Math.abs(d(b[j][1])-i)<8&&(g.x=b[j][0],g.y=b[j][1],g.index=j,g.seriesIndex=a.index);break}},this.drawHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=c.index,g=e(c.x),h=a.yScale(c.y),i;d.length-1>f&&(i=a.xScale(d[f+1][0]),b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,b.beginPath(),b.moveTo(g,h),b.lineTo(i,h),b.stroke(),b.closePath(),b.restore())},this.clearHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=a.lineWidth,g=c.index,h=e(c.x),i=a.yScale(c.y),j;d.length-1>g&&(j=a.xScale(d[g+1][0]),b.clearRect(h-f,i-f,j-h+2*f,2*f))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,a.fill&&(b.fillStyle=a.fillStyle),this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.shadowSize,e,f,g,h,i,j;if(b.length<1)return;this.translate(c,a.horizontal);for(e=0;e<b.length;e++){f=this.getBarGeometry(b[e][0],b[e][1],a);if(f===null)continue;g=f.left,h=f.top,i=f.width,j=f.height,a.fill&&c.fillRect(g,h,i,j),d&&(c.save(),c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(g+d,h+d,i,j),c.restore()),a.lineWidth&&c.strokeRect(g,h,i,j)}},translate:function(a,b){b&&(a.rotate(-Math.PI/2),a.scale(-1,1))},getBarGeometry:function(a,b,c){var d=c.horizontal,e=c.barWidth,f=c.centered,g=c.stacked?this.stack:!1,h=c.lineWidth,i=f?e/2:0,j=d?c.yScale:c.xScale,k=d?c.xScale:c.yScale,l=d?b:a,m=d?a:b,n=0,o,p,q,r,s;return g&&(o=m>0?g.positive:g.negative,n=o[l]||n,o[l]=n+m),p=j(l-i),q=j(l+e-i),r=k(m+n),s=k(n),s<0&&(s=0),a===null||b===null?null:{x:l,y:m,xScale:j,yScale:k,top:r,left:Math.min(p,q)-h/2,width:Math.abs(q-p)-h,height:s-r}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h=this.getBarGeometry(f,g,a),i=h.width/2,j=h.left,k,l;for(l=b.length;l--;)k=this.getBarGeometry(b[l][0],b[l][1],a),k.y>h.y&&Math.abs(j-k.left)<i&&(e.x=b[l][0],e.y=b[l][1],e.index=l,e.seriesIndex=a.index)},drawHit:function(a){var b=a.context,c=a.args,d=this.getBarGeometry(c.x,c.y,a),e=d.left,f=d.top,g=d.width,h=d.height;b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,this.translate(b,a.horizontal),b.beginPath(),b.moveTo(e,f+h),b.lineTo(e,f),b.lineTo(e+g,f),b.lineTo(e+g,f+h),a.fill&&(b.fillStyle=a.fillStyle,b.fill()),b.stroke(),b.closePath(),b.restore()},clearHit:function(a){var b=a.context,c=a.args,d=this.getBarGeometry(c.x,c.y,a),e=d.left,f=d.width,g=d.top,h=d.height,i=2*a.lineWidth;b.save(),this.translate(b,a.horizontal),b.clearRect(e-i,Math.min(g,g+h)-i,f+2*i,Math.abs(h)+2*i),b.restore()},extendXRange:function(a,b,c,d){this._extendRange(a,b,c,d)},extendYRange:function(a,b,c,d){this._extendRange(a,b,c,d)},_extendRange:function(a,b,c,d){var e=a.options.max;if(_.isNumber(e)||_.isString(e))return;var f=a.min,g=a.max,h=c.horizontal,i=a.orientation,j=this.positiveSums||{},k=this.negativeSums||{},l,m,n,o;(i==1&&!h||i==-1&&h)&&c.centered&&(g=Math.max(a.datamax+c.barWidth,g),f=Math.min(a.datamin-c.barWidth,f));if(c.stacked&&(i==1&&h||i==-1&&!h))for(o=b.length;o--;)l=b[o][i==1?1:0]+"",m=b[o][i==1?0:1],m>0?(j[l]=(j[l]||0)+m,g=Math.max(g,j[l])):(k[l]=(k[l]||0)+m,f=Math.min(f,k[l]));(i==1&&h||i==-1&&!h)&&c.topPadding&&(a.max===a.datamax||c.stacked&&this.stackMax!==g)&&(g+=c.topPadding*(g-f)),this.stackMin=f,this.stackMax=g,this.negativeSums=k,this.positiveSums=j,a.max=g,a.min=f}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e,f,g,h,i;b=b||0;for(f=0;f<c.length;++f)e=this.getGeometry(c[f],a),d.beginPath(),d.arc(e.x+b,e.y+b,e.z,0,2*Math.PI,!0),d.stroke(),a.fill&&d.fill(),d.closePath()},getGeometry:function(a,b){return{x:b.xScale(a[0]),y:b.yScale(a[1]),z:a[2]*b.baseRadius}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h,j,k;for(i=b.length;i--;)h=this.getGeometry(b[i],a),j=h.x-a.xScale(f),k=h.y-a.yScale(g),Math.sqrt(j*j+k*k)<h.z&&(e.x=b[i][0],e.y=b[i][1],e.index=i,e.seriesIndex=a.index)},drawHit:function(a){var b=a.context,c=this.getGeometry(a.data[a.args.index],a);b.save(),b.lineWidth=a.lineWidth,b.fillStyle=a.fillStyle,b.strokeStyle=a.color,b.beginPath(),b.arc(c.x,c.y,c.z,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath(),b.restore()},clearHit:function(a){var b=a.context,c=this.getGeometry(a.data[a.args.index],a),d=c.z+a.lineWidth;b.save(),b.clearRect(c.x-d,c.y-d,2*d,2*d),b.restore()}}),Flotr.addType("candles",{options:{show:!1,lineWidth:1,wickLineWidth:1,candleWidth:.6,fill:!0,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:.5,barcharts:!1},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineCap="butt",b.lineWidth=a.wickLineWidth||a.lineWidth,this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.xScale,e=a.yScale,f=a.candleWidth/2,g=a.shadowSize,h=a.lineWidth,i=a.wickLineWidth,j=i%2/2,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;if(b.length<1)return;for(y=0;y<b.length;y++){l=b[y],m=l[0],o=l[1],p=l[2],q=l[3],r=l[4],s=d(m-f),t=d(m+f),u=e(q),v=e(p),w=e(Math.min(o,r)),x=e(Math.max(o,r)),k=a[o>r?"downFillColor":"upFillColor"],a.fill&&!a.barcharts&&(c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(s+g,x+g,t-s,w-x),c.save(),c.globalAlpha=a.fillOpacity,c.fillStyle=k,c.fillRect(s,x+h,t-s,w-x),c.restore());if(h||i)m=Math.floor((s+t)/2)+j,c.strokeStyle=k,c.beginPath(),a.barcharts?(c.moveTo(m,Math.floor(v+f)),c.lineTo(m,Math.floor(u+f)),n=Math.floor(o+f)+.5,c.moveTo(Math.floor(s)+j,n),c.lineTo(m,n),n=Math.floor(r+f)+.5,c.moveTo(Math.floor(t)+j,n),c.lineTo(m,n)):(c.strokeRect(s,x+h,t-s,w-x),c.moveTo(m,Math.floor(x+h)),c.lineTo(m,Math.floor(v+h)),c.moveTo(m,Math.floor(w+h)),c.lineTo(m,Math.floor(u+h))),c.closePath(),c.stroke()}},extendXRange:function(a,b,c){a.options.max===null&&(a.max=Math.max(a.datamax+.5,a.max),a.min=Math.min(a.datamin-.5,a.min))}}),Flotr.addType("gantt",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=this.ctx,c=a.gantt.barWidth,d=Math.min(a.gantt.lineWidth,c);b.save(),b.translate(this.plotOffset.left,this.plotOffset.top),b.lineJoin="miter",b.lineWidth=d,b.strokeStyle=a.color,b.save(),this.gantt.plotShadows(a,c,0,a.gantt.fill),b.restore();if(a.gantt.fill){var e=a.gantt.fillColor||a.color;b.fillStyle=this.processColor(e,{opacity:a.gantt.fillOpacity})}this.gantt.plot(a,c,0,a.gantt.fill),b.restore()},plot:function(a,b,c,d){var e=a.data;if(e.length<1)return;var f=a.xaxis,g=a.yaxis,h=this.ctx,i;for(i=0;i<e.length;i++){var j=e[i][0],k=e[i][1],l=e[i][2],m=!0,n=!0,o=!0;if(k===null||l===null)continue;var p=k,q=k+l,r=j-(a.gantt.centered?b/2:0),s=j+b-(a.gantt.centered?b/2:0);if(q<f.min||p>f.max||s<g.min||r>g.max)continue;p<f.min&&(p=f.min,m=!1),q>f.max&&(q=f.max,f.lastSerie!=a&&(n=!1)),r<g.min&&(r=g.min),s>g.max&&(s=g.max,g.lastSerie!=a&&(n=!1)),d&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h.lineTo(f.d2p(p),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(r)+c),h.fill(),h.closePath()),a.gantt.lineWidth&&(m||o||n)&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h[m?"lineTo":"moveTo"](f.d2p(p),g.d2p(s)+c),h[n?"lineTo":"moveTo"](f.d2p(q),g.d2p(s)+c),h[o?"lineTo":"moveTo"](f.d2p(q),g.d2p(r)+c),h.stroke(),h.closePath())}},plotShadows:function(a,b,c){var d=a.data;if(d.length<1)return;var e,f,g,h,i=a.xaxis,j=a.yaxis,k=this.ctx,l=this.options.shadowSize;for(e=0;e<d.length;e++){f=d[e][0],g=d[e][1],h=d[e][2];if(g===null||h===null)continue;var m=g,n=g+h,o=f-(a.gantt.centered?b/2:0),p=f+b-(a.gantt.centered?b/2:0);if(n<i.min||m>i.max||p<j.min||o>j.max)continue;m<i.min&&(m=i.min),n>i.max&&(n=i.max),o<j.min&&(o=j.min),p>j.max&&(p=j.max);var q=i.d2p(n)-i.d2p(m)-(i.d2p(n)+l<=this.plotWidth?0:l),r=j.d2p(o)-j.d2p(p)-(j.d2p(o)+l<=this.plotHeight?0:l);k.fillStyle="rgba(0,0,0,0.05)",k.fillRect(Math.min(i.d2p(m)+l,this.plotWidth),Math.min(j.d2p(p)+l,this.plotHeight),q,r)}},extendXRange:function(a){if(a.options.max===null){var b=a.min,c=a.max,d,e,f,g,h,i={},j={},k=null;for(d=0;d<this.series.length;++d){g=this.series[d],h=g.gantt;if(h.show&&g.xaxis==a){for(e=0;e<g.data.length;e++)h.show&&(y=g.data[e][0]+"",i[y]=Math.max(i[y]||0,g.data[e][1]+g.data[e][2]),k=g);for(e in i)c=Math.max(i[e],c)}}a.lastSerie=k,a.max=c,a.min=b}},extendYRange:function(a){if(a.options.max===null){var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d,e,f,g,h={},i={},j=null;for(d=0;d<this.series.length;++d){f=this.series[d],g=f.gantt;if(g.show&&!f.hide&&f.yaxis==a){var k=Number.MIN_VALUE,l=Number.MAX_VALUE;for(e=0;e<f.data.length;e++)k=Math.max(k,f.data[e][0]),l=Math.min(l,f.data[e][0]);g.centered?(b=Math.max(k+.5,b),c=Math.min(l-.5,c)):(b=Math.max(k+1,b),c=Math.min(l,c)),g.barWidth+k>b&&(b=a.max+g.barWidth)}}a.lastSerie=j,a.max=b,a.min=c,a.tickSize=Flotr.getTickSize(a.options.noTicks,c,b,a.options.tickDecimals)}}}),function(){function a(a){return typeof a=="object"&&a.constructor&&(Image?!0:a.constructor===Image)}Flotr.defaultMarkerFormatter=function(a){return Math.round(a.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(a){function m(a,b){return g=d.negative[a]||0,f=d.positive[a]||0,b>0?(d.positive[a]=g+b,g+b):(d.negative[a]=f+b,f+b)}var b=a.data,c=a.context,d=a.stacked?a.stack:!1,e=a.stackingType,f,g,h,i,j,k,l;c.save(),c.lineJoin="round",c.lineWidth=a.lineWidth,c.strokeStyle="rgba(0,0,0,0.5)",c.fillStyle=a.fillStyle;for(i=0;i<b.length;++i)j=b[i][0],k=b[i][1],d&&(e=="b"?a.horizontal?k=m(k,j):j=m(j,k):e=="a"&&(h=d.values[j]||0,d.values[j]=h+k,k=h+k)),l=a.labelFormatter({x:j,y:k,index:i,data:b}),this.plot(a.xScale(j),a.yScale(k),l,a);c.restore()},plot:function(b,c,d,e){var f=e.context;if(a(d)&&!d.complete)throw"Marker image not loaded.";this._plot(b,c,d,e)},_plot:function(b,c,d,e){var f=e.context,g=2,h=b,i=c,j;a(d)?j={height:d.height,width:d.width}:j=e.text.canvas(d),j.width=Math.floor(j.width+g*2),j.height=Math.floor(j.height+g*2),e.position.indexOf("c")!=-1?h-=j.width/2+g:e.position.indexOf("l")!=-1&&(h-=j.width),e.position.indexOf("m")!=-1?i-=j.height/2+g:e.position.indexOf("t")!=-1?i-=j.height+e.verticalMargin:i+=e.verticalMargin,h=Math.floor(h)+.5,i=Math.floor(i)+.5,e.fill&&f.fillRect(h,i,j.width,j.height),e.stroke&&f.strokeRect(h,i,j.width,j.height),a(d)?f.drawImage(d,h+g,i+g):Flotr.drawText(f,d,h+g,i+g,{textBaseline:"top",textAlign:"left",size:e.fontSize,color:e.color})}})}(),function(){var a=Flotr._;Flotr.defaultPieLabelFormatter=function(a,b){return(100*b/a).toFixed(2)+"%"},Flotr.addType("pie",{options:{show:!1,lineWidth:1,fill:!0,fillColor:null,fillOpacity:.6,explode:6,sizeRatio:.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:!1,pie3DviewAngle:Math.PI/2*.8,pie3DspliceThickness:20},draw:function(a){var b=a.data,c=a.context,d=c.canvas,e=a.lineWidth,f=a.shadowSize,g=a.sizeRatio,h=a.height,i=a.width,j=a.explode,k=a.color,l=a.fill,m=a.fillStyle,n=Math.min(d.width,d.height)*g/2,o=b[0][1],p=[],q=1,r=Math.PI*2*o/this.total,s=this.startAngle||2*Math.PI*a.startAngle,t=s+r,u=s+r/2,v=a.labelFormatter(this.total,o),w=j+n+4,x=Math.cos(u)*w,y=Math.sin(u)*w,z=x<0?"right":"left",A=y>0?"top":"bottom",B,C,D,x,y;c.save(),c.translate(i/2,h/2),c.scale(1,q),C=Math.cos(u)*j,D=Math.sin(u)*j,f>0&&(this.plotSlice(C+f,D+f,n,s,t,c),l&&(c.fillStyle="rgba(0,0,0,0.1)",c.fill())),this.plotSlice(C,D,n,s,t,c),l&&(c.fillStyle=m,c.fill()),c.lineWidth=e,c.strokeStyle=k,c.stroke(),B={size:a.fontSize*1.2,color:a.fontColor,weight:1.5},v&&(a.htmlText||!a.textEnabled?(divStyle="position:absolute;"+A+":"+(h/2+(A==="top"?y:-y))+"px;",divStyle+=z+":"+(i/2+(z==="right"?-x:x))+"px;",p.push('<div style="',divStyle,'" class="flotr-grid-label">',v,"</div>")):(B.textAlign=z,B.textBaseline=A,Flotr.drawText(c,v,x,y,B)));if(a.htmlText||!a.textEnabled){var E=Flotr.DOM.node('<div style="color:'+a.fontColor+'" class="flotr-labels"></div>');Flotr.DOM.insert(E,p.join("")),Flotr.DOM.insert(a.element,E)}c.restore(),this.startAngle=t,this.slices=this.slices||[],this.slices.push({radius:Math.min(d.width,d.height)*g/2,x:C,y:D,explode:j,start:s,end:t})},plotSlice:function(a,b,c,d,e,f){f.beginPath(),f.moveTo(a,b),f.arc(a,b,c,d,e,!1),f.lineTo(a,b),f.closePath()},hit:function(a){var b=a.data[0],c=a.args,d=a.index,e=c[0],f=c[1],g=this.slices[d],h=e.relX-a.width/2,i=e.relY-a.height/2,j=Math.sqrt(h*h+i*i),k=Math.atan(i/h),l=Math.PI*2,m=g.explode||a.explode,n=g.start%l,o=g.end%l;h<0?k+=Math.PI:h>0&&i<0&&(k+=l),j<g.radius+m&&j>m&&(n>o&&(k<o||k>n)||k>n&&k<o)&&(f.x=b[0],f.y=b[1],f.sAngle=n,f.eAngle=o,f.index=0,f.seriesIndex=d,f.fraction=b[1]/this.total)},drawHit:function(a){var b=a.context,c=this.slices[a.args.seriesIndex];b.save(),b.translate(a.width/2,a.height/2),this.plotSlice(c.x,c.y,c.radius,c.start,c.end,b),b.stroke(),b.restore()},clearHit:function(a){var b=a.context,c=this.slices[a.args.seriesIndex],d=2*a.lineWidth,e=c.radius+d;b.save(),b.translate(a.width/2,a.height/2),b.clearRect(c.x-e,c.y-e,2*e+d,2*e+d),b.restore()},extendYRange:function(a,b){this.total=(this.total||0)+b[0][1]}})}(),Flotr.addType("points",{options:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#FFFFFF",fillOpacity:.4},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize;b.save(),d>0&&(b.lineWidth=d/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,d/2+b.lineWidth/2),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,b.lineWidth/2)),b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillColor||a.color,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=a.xScale,f=a.yScale,g,h,i;for(g=c.length-1;g>-1;--g){i=c[g][1];if(i===null)continue;h=e(c[g][0]),i=f(i);if(h<0||h>a.width||i<0||i>a.height)continue;d.beginPath(),b?d.arc(h,i+b,a.radius,0,Math.PI,!1):(d.arc(h,i,a.radius,0,2*Math.PI,!0),a.fill&&d.fill()),d.stroke(),d.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.translate(a.width/2,a.height/2),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=Math.min(a.height,a.width)*a.radiusRatio/2,f=2*Math.PI/c.length,g=-Math.PI/2,h,i;b=b||0,d.beginPath();for(h=0;h<c.length;++h)i=c[h][1]/this.max,d[h===0?"moveTo":"lineTo"](Math.cos(h*f+g)*e*i+b,Math.sin(h*f+g)*e*i+b);d.closePath(),a.fill&&d.fill(),d.stroke()},extendYRange:function(a,b){this.max=Math.max(a.max,this.max||-Number.MAX_VALUE)}}),Flotr.addType("timeline",{options:{show:!1,lineWidth:1,barWidth:.2,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.xScale,e=a.yScale,f=a.barWidth,g=a.lineWidth,h;Flotr._.each(b,function(a){var b=a[0],h=a[1],i=a[2],j=f,k=Math.ceil(d(b)),l=Math.ceil(d(b+i))-k,m=Math.round(e(h)),n=Math.round(e(h-j))-m,o=k-g/2,p=Math.round(m-n/2)-g/2;c.strokeRect(o,p,l,n),c.fillRect(o,p,l,n)})},extendRange:function(a){var b=a.data,c=a.xaxis,d=a.yaxis,e=a.timeline.barWidth;c.options.min===null&&(c.min=c.datamin-e/2);if(c.options.max===null){var f=c.max;Flotr._.each(b,function(a){f=Math.max(f,a[0]+a[2])},this),c.max=f+e/2}d.options.min===null&&(d.min=d.datamin-e),d.options.min===null&&(d.max=d.datamax+e)}}),function(){var a=Flotr.DOM;Flotr.addPlugin("crosshair",{options:{mode:null,color:"#FF0000",hideCursor:!0},callbacks:{"flotr:mousemove":function(a,b){this.options.crosshair.mode&&(this.crosshair.clearCrosshair(),this.crosshair.drawCrosshair(b))}},drawCrosshair:function(b){var c=this.octx,d=this.options.crosshair,e=this.plotOffset,f=e.left+b.relX+.5,g=e.top+b.relY+.5;if(b.relX<0||b.relY<0||b.relX>this.plotWidth||b.relY>this.plotHeight){this.el.style.cursor=null,a.removeClass(this.el,"flotr-crosshair");return}d.hideCursor&&(this.el.style.cursor="none",a.addClass(this.el,"flotr-crosshair")),c.save(),c.strokeStyle=d.color,c.lineWidth=1,c.beginPath(),d.mode.indexOf("x")!=-1&&(c.moveTo(f,e.top),c.lineTo(f,e.top+this.plotHeight)),d.mode.indexOf("y")!=-1&&(c.moveTo(e.left,g),c.lineTo(e.left+this.plotWidth,g)),c.stroke(),c.restore()},clearCrosshair:function(){var a=this.plotOffset,b=this.lastMousePos,c=this.octx;b&&(c.clearRect(b.relX+a.left,a.top,1,this.plotHeight+1),c.clearRect(a.left,b.relY+a.top,this.plotWidth+1,1))}})}(),function(){function c(a,b,c,d){var e="image/"+a,f=b.toDataURL(e),g=new Image;return g.src=f,g}var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("download",{saveImage:function(d,e,f,g){var h=null;if(Flotr.isIE&&Flotr.isIE<9)return h="<html><body>"+this.canvas.firstChild.innerHTML+"</body></html>",window.open().document.write(h);if(d!=="jpeg"&&d!=="png")return;h=c(d,this.canvas,e,f);if(b.isElement(h)&&g)this.download.restoreCanvas(),a.hide(this.canvas),a.hide(this.overlay),a.setStyles({position:"absolute"}),a.insert(this.el,h),this.saveImageElement=h;else return window.open(h.src)},restoreCanvas:function(){a.show(this.canvas),a.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var a=Flotr.EventAdapter,b=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function p(a){for(n=0;n<a.length;++n){var b=a[n].v/l.max;for(o=0;o<=u;++o)c[o===0?"moveTo":"lineTo"](Math.cos(o*v+w)*t*b,Math.sin(o*v+w)*t*b)}}function q(a,d){b.each(b.pluck(a,"v"),function(a){if(a<=l.min||a>=l.max||(a==l.min||a==l.max)&&e.outlineWidth)return;d(Math.floor(l.d2p(a))+c.lineWidth/2)})}function r(a){c.moveTo(a,0),c.lineTo(a,j)}function s(a){c.moveTo(0,a),c.lineTo(k,a)}var c=this.ctx,d=this.options,e=d.grid,f=e.verticalLines,g=e.horizontalLines,h=e.minorVerticalLines,i=e.minorHorizontalLines,j=this.plotHeight,k=this.plotWidth,l,m,n,o;(f||h||g||i)&&a.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,d,this]),c.save(),c.lineWidth=1,c.strokeStyle=e.tickColor;if(e.circular){c.translate(this.plotOffset.left+k/2,this.plotOffset.top+j/2);var t=Math.min(j,k)*d.radar.radiusRatio/2,u=this.axes.x.ticks.length,v=2*(Math.PI/u),w=-Math.PI/2;c.beginPath(),l=this.axes.y,g&&p(l.ticks),i&&p(l.minorTicks),f&&b.times(u,function(a){c.moveTo(0,0),c.lineTo(Math.cos(a*v+w)*t,Math.sin(a*v+w)*t)}),c.stroke()}else c.translate(this.plotOffset.left,this.plotOffset.top),e.backgroundColor&&(c.fillStyle=this.processColor(e.backgroundColor,{x1:0,y1:0,x2:k,y2:j}),c.fillRect(0,0,k,j)),c.beginPath(),l=this.axes.x,f&&q(l.ticks,r),h&&q(l.minorTicks,r),l=this.axes.y,g&&q(l.ticks,s),i&&q(l.minorTicks,s),c.stroke();c.restore(),(f||h||g||i)&&a.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,d,this])},drawOutline:function(){var a=this,b=a.options,c=b.grid,d=c.outline,e=a.ctx,f=c.backgroundImage,g=a.plotOffset,h=g.left,j=g.top,k=a.plotWidth,l=a.plotHeight,m,n,o,p,q,r;if(!c.outlineWidth)return;e.save();if(c.circular){e.translate(h+k/2,j+l/2);var s=Math.min(l,k)*b.radar.radiusRatio/2,t=this.axes.x.ticks.length,u=2*(Math.PI/t),v=-Math.PI/2;e.beginPath(),e.lineWidth=c.outlineWidth,e.strokeStyle=c.color,e.lineJoin="round";for(i=0;i<=t;++i)e[i===0?"moveTo":"lineTo"](Math.cos(i*u+v)*s,Math.sin(i*u+v)*s);e.stroke()}else{e.translate(h,j);var w=c.outlineWidth,x=.5-w+(w+1)%2/2,y="lineTo",z="moveTo";e.lineWidth=w,e.strokeStyle=c.color,e.lineJoin="miter",e.beginPath(),e.moveTo(x,x),k-=w/2%1,l+=w/2,e[d.indexOf("n")!==-1?y:z](k,x),e[d.indexOf("e")!==-1?y:z](k,l),e[d.indexOf("s")!==-1?y:z](x,l),e[d.indexOf("w")!==-1?y:z](x,x),e.stroke(),e.closePath()}e.restore(),f&&(o=f.src||f,p=(parseInt(f.left,10)||0)+g.left,q=(parseInt(f.top,10)||0)+g.top,n=new Image,n.onload=function(){e.save(),f.alpha&&(e.globalAlpha=f.alpha),e.globalCompositeOperation="destination-over",e.drawImage(n,0,0,n.width,n.height,p,q,k,l),e.restore()},n.src=o)}})}(),function(){var a=Flotr.DOM,b=Flotr._,c=Flotr,d="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(a,b){this.hit.track(b)},"flotr:click":function(a){this.hit.track(a)},"flotr:mouseout":function(){this.hit.clearHit()}},track:function(a){(this.options.mouse.track||b.any(this.series,function(a){return a.mouse&&a.mouse.track}))&&this.hit.hit(a)},executeOnType:function(a,d,e){function h(a,h){b.each(b.keys(c.graphTypes),function(b){a[b]&&a[b].show&&this[b][d]&&(g=this.getOptions(a,b),g.fill=!!a.mouse.fillColor,g.fillStyle=this.processColor(a.mouse.fillColor||"#ffffff",{opacity:a.mouse.fillOpacity}),g.color=a.mouse.lineColor,g.context=this.octx,g.index=h,e&&(g.args=e),this[b][d].call(this[b],g),f=!0)},this)}var f=!1,g;return b.isArray(a)||(a=[a]),b.each(a,h,this),f},drawHit:function(a){var b=this.octx,c=a.series;if(c.mouse.lineColor){b.save(),b.lineWidth=c.points?c.points.lineWidth:1,b.strokeStyle=c.mouse.lineColor,b.fillStyle=this.processColor(c.mouse.fillColor||"#ffffff",{opacity:c.mouse.fillOpacity}),b.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(c,"drawHit",a)){var d=a.xaxis,e=a.yaxis;b.beginPath(),b.arc(d.d2p(a.x),e.d2p(a.y),c.points.radius||c.mouse.radius,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath()}b.restore()}this.prevHit=a},clearHit:function(){var b=this.prevHit,c=this.octx,d=this.plotOffset;c.save(),c.translate(d.left,d.top);if(b){if(!this.hit.executeOnType(b.series,"clearHit",this.prevHit)){var e=b.series,f=e.points?e.points.lineWidth:1;offset=(e.points.radius||e.mouse.radius)+f,c.clearRect(b.xaxis.d2p(b.x)-offset,b.yaxis.d2p(b.y)-offset,offset*2,offset*2)}a.hide(this.mouseTrack),this.prevHit=null}c.restore()},hit:function(a){var c=this.options,d=this.prevHit,e,f,g,h,i,j,k,l;if(this.series.length===0)return;n={relX:a.relX,relY:a.relY,absX:a.absX,absY:a.absY};if(c.mouse.trackY&&!c.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[a,n]))b.isUndefined(n.seriesIndex)||(i=this.series[n.seriesIndex],n.series=i,n.mouse=i.mouse,n.xaxis=i.xaxis,n.yaxis=i.yaxis);else{e=this.hit.closest(a);if(e){e=c.mouse.trackY?e.point:e.x,h=e.seriesIndex,i=this.series[h],k=i.xaxis,l=i.yaxis,f=2*i.mouse.sensibility;if(c.mouse.trackAll||e.distanceX<f/k.scale&&(!c.mouse.trackY||e.distanceY<f/l.scale))n.series=i,n.xaxis=i.xaxis,n.yaxis=i.yaxis,n.mouse=i.mouse,n.x=e.x,n.y=e.y,n.dist=e.distance,n.index=e.dataIndex,n.seriesIndex=h}}if(!d||d.index!==n.index||d.seriesIndex!==n.seriesIndex)this.hit.clearHit(),n.series&&n.mouse&&n.mouse.track&&(this.hit.drawMouseTrack(n),this.hit.drawHit(n),Flotr.EventAdapter.fire(this.el,"flotr:hit",[n,this]))},closest:function(a){function t(a){a.distance=m,a.distanceX=n,a.distanceY=o,a.seriesIndex=r,a.dataIndex=s,a.x=p,a.y=q}var b=this.series,c=this.options,d=a.x,e=a.y,f=Number.MAX_VALUE,g=Number.MAX_VALUE,h={},i={},j=!1,k,l,m,n,o,p,q,r,s;for(r=0;r<b.length;r++){k=b[r],l=k.data,l.length&&(j=!0);for(s=l.length;s--;){p=l[s][0],q=l[s][1];if(p===null||q===null)continue;if(p<k.xaxis.min||p>k.xaxis.max)continue;n=Math.abs(p-d),o=Math.abs(q-e),m=n*n+o*o,m<f&&(f=m,t(h)),n<g&&(g=n,t(i))}}return j?{point:h,x:i}:!1},drawMouseTrack:function(b){var c="",e=b.series,f=b.mouse.position,g=b.mouse.margin,h=d,i=this.mouseTrack,j=this.plotOffset,k=j.left,l=j.right,m=j.bottom,n=j.top,o=b.mouse.trackDecimals,p=this.options;i||(i=a.node('<div class="flotr-mouse-value"></div>'),this.mouseTrack=i,a.insert(this.el,i));if(!b.mouse.relative)f.charAt(0)=="n"?c+="top:"+(g+n)+"px;bottom:auto;":f.charAt(0)=="s"&&(c+="bottom:"+(g+m)+"px;top:auto;"),f.charAt(1)=="e"?c+="right:"+(g+l)+"px;left:auto;":f.charAt(1)=="w"&&(c+="left:"+(g+k)+"px;right:auto;");else if(e.bars.show)c+="bottom:"+(g-n-b.yaxis.d2p(b.y/2)+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+b.xaxis.d2p(b.x-p.bars.barWidth/2))+"px;right:auto;";else if(e.pie.show){var q={x:this.plotWidth/2,y:this.plotHeight/2},r=Math.min(this.canvasWidth,this.canvasHeight)*e.pie.sizeRatio/2,s=b.sAngle<b.eAngle?(b.sAngle+b.eAngle)/2:(b.sAngle+b.eAngle+2*Math.PI)/2;c+="bottom:"+(g-n-q.y-Math.sin(s)*r/2+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+q.x+Math.cos(s)*r/2)+"px;right:auto;"}else f.charAt(0)=="n"?c+="bottom:"+(g-n-b.yaxis.d2p(b.y)+this.canvasHeight)+"px;top:auto;":f.charAt(0)=="s"&&(c+="top:"+(g+n+b.yaxis.d2p(b.y))+"px;bottom:auto;"),f.charAt(1)=="e"?c+="left:"+(g+k+b.xaxis.d2p(b.x))+"px;right:auto;":f.charAt(1)=="w"&&(c+="right:"+(g-k-b.xaxis.d2p(b.x)+this.canvasWidth)+"px;left:auto;");h+=c,i.style.cssText=h;if(!o||o<0)o=0;i.innerHTML=b.mouse.trackFormatter({x:b.x.toFixed(o),y:b.y.toFixed(o),series:b.series,index:b.index,nearest:b,fraction:b.fraction}),a.show(i)}})}(),function(){function a(a,b){return a.which?a.which===1:a.button===0||a.button===1}function b(a,b){return Math.min(Math.max(0,a),b.plotWidth-1)}function c(a,b){return Math.min(Math.max(0,a),b.plotHeight)}var d=Flotr.DOM,e=Flotr.EventAdapter,f=Flotr._;Flotr.addPlugin("selection",{options:{pinchOnly:null,mode:null,color:"#B6D9FF",fps:20},callbacks:{"flotr:mouseup":function(a){var b=this.options.selection,c=this.selection,d=this.getEventPosition(a);if(!b||!b.mode)return;c.interval&&clearInterval(c.interval),this.multitouches?c.updateSelection():b.pinchOnly||c.setSelectionPos(c.selection.second,d),c.clearSelection(),c.selecting&&c.selectionIsSane()&&(c.drawSelection(),c.fireSelectEvent(),this.ignoreClick=!0)},"flotr:mousedown":function(b){var c=this.options.selection,d=this.selection,e=this.getEventPosition(b);if(!c||!c.mode)return;if(!c.mode||!a(b)&&f.isUndefined(b.touches))return;c.pinchOnly||d.setSelectionPos(d.selection.first,e),d.interval&&clearInterval(d.interval),this.lastMousePos.pageX=null,d.selecting=!1,d.interval=setInterval(f.bind(d.updateSelection,this),1e3/c.fps)},"flotr:destroy":function(a){clearInterval(this.selection.interval)}},getArea:function(){var a=this.selection.selection,b=a.first,c=a.second;return{x1:Math.min(b.x,c.x),x2:Math.max(b.x,c.x),y1:Math.min(b.y,c.y),y2:Math.max(b.y,c.y)}},selection:{first:{x:-1,y:-1},second:{x:-1,y:-1}},prevSelection:null,interval:null,fireSelectEvent:function(a){var b=this.axes,c=this.selection.selection,d,f,g,h;a=a||"select",d=b.x.p2d(c.first.x),f=b.x.p2d(c.second.x),g=b.y.p2d(c.first.y),h=b.y.p2d(c.second.y),e.fire(this.el,"flotr:"+a,[{x1:Math.min(d,f),y1:Math.min(g,h),x2:Math.max(d,f),y2:Math.max(g,h),xfirst:d,xsecond:f,yfirst:g,ysecond:h},this])},setSelection:function(a,d){var e=this.options,f=this.axes.x,g=this.axes.y,h=g.scale,i=f.scale,j=e.selection.mode.indexOf("x")!=-1,k=e.selection.mode.indexOf("y")!=-1,l=this.selection.selection;this.selection.clearSelection(),l.first.y=c(j&&!k?0:(g.max-a.y1)*h,this),l.second.y=c(j&&!k?this.plotHeight-1:(g.max-a.y2)*h,this),l.first.x=b(k&&!j?0:a.x1,this),l.second.x=b(k&&!j?this.plotWidth:a.x2,this),this.selection.drawSelection(),d||this.selection.fireSelectEvent()},setSelectionPos:function(a,d){var e=this.options.selection.mode,f=this.selection.selection;e.indexOf("x")==-1?a.x=a==f.first?0:this.plotWidth:a.x=b(d.relX,this),e.indexOf("y")==-1?a.y=a==f.first?0:this.plotHeight-1:a.y=c(d.relY,this)},drawSelection:function(){this.selection.fireSelectEvent("selecting");var a=this.selection.selection,b=this.octx,c=this.options,d=this.plotOffset,e=this.selection.prevSelection;if(e&&a.first.x==e.first.x&&a.first.y==e.first.y&&a.second.x==e.second.x&&a.second.y==e.second.y)return;b.save(),b.strokeStyle=this.processColor(c.selection.color,{opacity:.8}),b.lineWidth=1,b.lineJoin="miter",b.fillStyle=this.processColor(c.selection.color,{opacity:.4}),this.selection.prevSelection={first:{x:a.first.x,y:a.first.y},second:{x:a.second.x,y:a.second.y}};var f=Math.min(a.first.x,a.second.x),g=Math.min(a.first.y,a.second.y),h=Math.abs(a.second.x-a.first.x),i=Math.abs(a.second.y-a.first.y);b.fillRect(f+d.left+.5,g+d.top+.5,h,i),b.strokeRect(f+d.left+.5,g+d.top+.5,h,i),b.restore()},updateSelection:function(){if(!this.lastMousePos.pageX)return;this.selection.selecting=!0;if(this.multitouches)this.selection.setSelectionPos(this.selection.selection.first,this.getEventPosition(this.multitouches[0])),this.selection.setSelectionPos(this.selection.selection.second,this.getEventPosition(this.multitouches[1]));else{if(this.options.selection.pinchOnly)return;this.selection.setSelectionPos(this.selection.selection.second,this.lastMousePos)}this.selection.clearSelection(),this.selection.selectionIsSane()&&this.selection.drawSelection()},clearSelection:function(){if(!this.selection.prevSelection)return;var a=this.selection.prevSelection,b=1,c=this.plotOffset,d=Math.min(a.first.x,a.second.x),e=Math.min(a.first.y,a.second.y),f=Math.abs(a.second.x-a.first.x),g=Math.abs(a.second.y-a.first.y);this.octx.clearRect(d+c.left-b+.5,e+c.top-b,f+2*b+.5,g+2*b+.5),this.selection.prevSelection=null},selectionIsSane:function(){var a=this.selection.selection;return Math.abs(a.second.x-a.first.x)>=5||Math.abs(a.second.y-a.first.y)>=5}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function s(a,b,d){var e=d?b.minorTicks:b.ticks,f=b.orientation===1,h=b.n===1,k,m;k={color:b.options.color||o.grid.color,angle:Flotr.toRad(b.options.labelsAngle),textBaseline:"middle"};for(l=0;l<e.length&&(d?b.options.showMinorLabels:b.options.showLabels);++l){c=e[l],c.label+="";if(!c.label||!c.label.length)continue;x=Math.cos(l*i+j)*g,y=Math.sin(l*i+j)*g,k.textAlign=f?Math.abs(x)<.1?"center":x<0?"right":"left":"left",Flotr.drawText(p,c.label,f?x:3,f?y:-(b.ticks[l].v/b.max)*(g-o.fontSize),k)}}function t(a,b,d,e){function j(a){return a.options.showLabels&&a.used}function k(a,b,c,d){return a.plotOffset.left+(b?d:c?-o.grid.labelMargin:o.grid.labelMargin+a.plotWidth)}function m(a,b,c,d){return a.plotOffset.top+(b?o.grid.labelMargin:d)+(b&&c?a.plotHeight:0)}var f=b.orientation===1,g=b.n===1,h,i;h={color:b.options.color||o.grid.color,textAlign:d,textBaseline:e,angle:Flotr.toRad(b.options.labelsAngle)},h=Flotr.getBestTextAlign(h.angle,h);for(l=0;l<b.ticks.length&&j(b);++l){c=b.ticks[l];if(!c.label||!c.label.length)continue;i=b.d2p(c.v);if(i<0||i>(f?a.plotWidth:a.plotHeight))continue;Flotr.drawText(p,c.label,k(a,f,g,i),m(a,f,g,i),h),!f&&!g&&(p.save(),p.strokeStyle=h.color,p.beginPath(),p.moveTo(a.plotOffset.left+a.plotWidth-8,a.plotOffset.top+b.d2p(c.v)),p.lineTo(a.plotOffset.left+a.plotWidth,a.plotOffset.top+b.d2p(c.v)),p.stroke(),p.restore())}}function u(a,b){var d=b.orientation===1,e=b.n===1,g="",h,i,j,k=a.plotOffset;!d&&!e&&(p.save(),p.strokeStyle=b.options.color||o.grid.color,p.beginPath());if(b.options.showLabels&&(e?!0:b.used))for(l=0;l<b.ticks.length;++l){c=b.ticks[l];if(!c.label||!c.label.length||(d?k.left:k.top)+b.d2p(c.v)<0||(d?k.left:k.top)+b.d2p(c.v)>(d?a.canvasWidth:a.canvasHeight))continue;j=k.top+(d?(e?1:-1)*(a.plotHeight+o.grid.labelMargin):b.d2p(c.v)-b.maxLabel.height/2),h=d?k.left+b.d2p(c.v)-f/2:0,g="",l===0?g=" first":l===b.ticks.length-1&&(g=" last"),g+=d?" flotr-grid-label-x":" flotr-grid-label-y",m+=['<div style="position:absolute; text-align:'+(d?"center":"right")+"; ","top:"+j+"px; ",(!d&&!e?"right:":"left:")+h+"px; ","width:"+(d?f:(e?k.left:k.right)-o.grid.labelMargin)+"px; ",b.options.color?"color:"+b.options.color+"; ":" ",'" class="flotr-grid-label'+g+'">'+c.label+"</div>"].join(" "),!d&&!e&&(p.moveTo(k.left+a.plotWidth-8,k.top+b.d2p(c.v)),p.lineTo(k.left+a.plotWidth,k.top+b.d2p(c.v)))}}var b,c,d,e,f,g,h,i,j,k,l,m="",n=0,o=this.options,p=this.ctx,q=this.axes,r={size:o.fontSize};for(l=0;l<q.x.ticks.length;++l)q.x.ticks[l].label&&++n;f=this.plotWidth/n,o.grid.circular&&(p.save(),p.translate(this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+this.plotHeight/2),g=this.plotHeight*o.radar.radiusRatio/2+o.fontSize,h=this.axes.x.ticks.length,i=2*(Math.PI/h),j=-Math.PI/2,s(this,q.x,!1),s(this,q.x,!0),s(this,q.y,!1),s(this,q.y,!0),p.restore()),!o.HtmlText&&this.textEnabled?(t(this,q.x,"center","top"),t(this,q.x2,"center","bottom"),t(this,q.y,"right","middle"),t(this,q.y2,"left","middle")):(q.x.options.showLabels||q.x2.options.showLabels||q.y.options.showLabels||q.y2.options.showLabels)&&!o.grid.circular&&(m="",u(this,q.x),u(this,q.x2),u(this,q.y),u(this,q.y2),p.stroke(),p.restore(),k=a.create("div"),a.setStyles(k,{fontSize:"smaller",color:o.grid.color}),k.className="flotr-labels",a.insert(this.el,k),a.insert(k,m))}})}(),function(){var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("legend",{options:{show:!0,noColumns:1,labelFormatter:function(a){return a},labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,labelBoxOpacity:.4,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:.85},callbacks:{"flotr:afterinit":function(){this.legend.insertLegend()}},insertLegend:function(){if(!this.options.legend.show)return;var c=this.series,d=this.plotOffset,e=this.options,f=e.legend,g=[],h=!1,i=this.ctx,j=b.filter(c,function(a){return a.label&&!a.hide}).length,k=f.position,l=f.margin,m,n,o;if(j)if(!e.HtmlText&&this.textEnabled&&!f.container){var p={size:e.fontSize*1.1,color:e.grid.color},q=f.labelBoxWidth,r=f.labelBoxHeight,s=f.labelBoxMargin,t=d.left+l,u=d.top+l,v=0;for(m=c.length-1;m>-1;--m){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),v=Math.max(v,this._text.measureText(n,p).width)}var w=Math.round(q+s*3+v),x=Math.round(j*(s+r)+s);k.charAt(0)=="s"&&(u=d.top+this.plotHeight-(l+x)),k.charAt(1)=="e"&&(t=d.left+this.plotWidth-(l+w)),o=this.processColor(f.backgroundColor||"rgb(240,240,240)",{opacity:f.backgroundOpacity||.1}),i.fillStyle=o,i.fillRect(t,u,w,x),i.strokeStyle=f.labelBoxBorderColor,i.strokeRect(Flotr.toPixel(t),Flotr.toPixel(u),w,x);var y=t+s,z=u+s;for(m=0;m<c.length;m++){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),i.fillStyle=c[m].color,i.fillRect(y,z,q-1,r-1),i.strokeStyle=f.labelBoxBorderColor,i.lineWidth=1,i.strokeRect(Math.ceil(y)-1.5,Math.ceil(z)-1.5,q+2,r+2),Flotr.drawText(i,n,y+q+s,z+r,p),z+=r+s}}else{for(m=0;m<c.length;++m){if(!c[m].label||c[m].hide)continue;m%f.noColumns===0&&(g.push(h?"</tr><tr>":"<tr>"),h=!0);var A=c[m],B=f.labelBoxWidth,C=f.labelBoxHeight,E=A.bars?A.bars.fillOpacity:f.labelBoxOpacity,F="opacity:"+E+";filter:alpha(opacity="+E*100+");";n=f.labelFormatter(A.label),o="background-color:"+(A.bars&&A.bars.show&&A.bars.fillColor&&A.bars.fill?A.bars.fillColor:A.color)+";",g.push('<td class="flotr-legend-color-box">','<div style="border:1px solid ',f.labelBoxBorderColor,';padding:1px">','<div style="width:',B-1,"px;height:",C-1,"px;border:1px solid ",c[m].color,'">','<div style="width:',B,"px;height:",C,"px;","opacity:.4;",o,'"></div>',"</div>","</div>","</td>",'<td class="flotr-legend-label">',n,"</td>")}h&&g.push("</tr>");if(g.length>0){var G='<table style="font-size:smaller;color:'+e.grid.color+'">'+g.join("")+"</table>";if(f.container)a.insert(f.container,G);else{var H={position:"absolute","z-index":2};k.charAt(0)=="n"?(H.top=l+d.top+"px",H.bottom="auto"):k.charAt(0)=="s"&&(H.bottom=l+d.bottom+"px",H.top="auto"),k.charAt(1)=="e"?(H.right=l+d.right+"px",H.left="auto"):k.charAt(1)=="w"&&(H.left=l+d.left+"px",H.right="auto");var I=a.create("div"),J;I.className="flotr-legend",a.setStyles(I,H),a.insert(I,G),a.insert(this.el,I);if(!f.backgroundOpacity)return;var K=f.backgroundColor||e.grid.backgroundColor||"#ffffff";b.extend(H,a.size(I),{backgroundColor:K,"z-index":1}),H.width+="px",H.height+="px",I=a.create("div"),I.className="flotr-legend-bg",a.setStyles(I,H),a.opacity(I,f.backgroundOpacity),a.insert(I," "),a.insert(this.el,I)}}}}})}(),function(){function a(a){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(a);var b=c.find(this.axes.x.ticks,function(b){return b.v==a});return b?b.label:a}var b=Flotr.DOM,c=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var a=this.spreadsheet,c=b.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),d=b.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+"</div>"),e=b.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+"</div>"),f;a.tabsContainer=c,a.tabs={graph:d,data:e},b.insert(c,d),b.insert(c,e),b.insert(this.el,c),f=b.size(e).height+2,this.plotOffset.bottom+=f,b.setStyles(c,{top:this.canvasHeight-f+"px"}),this.observe(d,"click",function(){a.showTab("graph")}).observe(e,"click",function(){a.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&a.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var a=this.series,b={};return c.each(a,function(a,d){c.each(a.data,function(a){var c=a[0],e=a[1],f=b[c];if(f)f[d+1]=e;else{var g=[];g[0]=c,g[d+1]=e,b[c]=g}})}),this.seriesData=c.sortBy(b,function(a,b){return parseInt(b,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var d=this.series,e=this.spreadsheet.loadDataGrid(),f=["<colgroup><col />"],g,h,i,j=['<table class="flotr-datagrid"><tr class="first-row">'];j.push("<th>&nbsp;</th>"),c.each(d,function(a,b){j.push('<th scope="col">'+(a.label||String.fromCharCode(65+b))+"</th>"),f.push("<col />")}),j.push("</tr>"),c.each(e,function(b){j.push("<tr>"),c.times(d.length+1,function(d){var e="td",f=b[d],g=c.isUndefined(f)?"":Math.round(f*1e5)/1e5;if(d===0){e="th";var h=a.call(this,g);h&&(g=h)}j.push("<"+e+(e=="th"?' scope="row"':"")+">"+g+"</"+e+">")},this),j.push("</tr>")},this),f.push("</colgroup>"),i=b.node(j.join("")),g=b.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarDownload+"</button>"),h=b.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarSelectAll+"</button>"),this.observe(g,"click",c.bind(this.spreadsheet.downloadCSV,this)).observe(h,"click",c.bind(this.spreadsheet.selectAllData,this));var k=b.node('<div class="flotr-datagrid-toolbar"></div>');b.insert(k,g),b.insert(k,h);var l=this.canvasHeight-b.size(this.spreadsheet.tabsContainer).height-2,m=b.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+this.canvasWidth+"px;height:"+l+'px;overflow:auto;z-index:10"></div>');return b.insert(m,k),b.insert(m,i),b.insert(this.el,m),this.spreadsheet.datagrid=i,this.spreadsheet.container=m,i},showTab:function(a){if(this.spreadsheet.activeTab===a)return;switch(a){case"graph":b.hide(this.spreadsheet.container),b.removeClass(this.spreadsheet.tabs.data,"selected"),b.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),b.show(this.spreadsheet.container),b.addClass(this.spreadsheet.tabs.data,"selected"),b.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+a}this.spreadsheet.activeTab=a},selectAllData:function(){if(this.spreadsheet.tabs){var a,b,c,d,e=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(c=e.ownerDocument)&&(d=c.defaultView)&&d.getSelection&&c.createRange&&(a=window.getSelection())&&a.removeAllRanges?(b=c.createRange(),b.selectNode(e),a.removeAllRanges(),a.addRange(b)):document.body&&document.body.createTextRange&&(b=document.body.createTextRange())&&(b.moveToElementText(e),b.select())},0),!0}return!1},downloadCSV:function(){var b="",d=this.series,e=this.options,f=this.spreadsheet.loadDataGrid(),g=encodeURIComponent(e.spreadsheet.csvFileSeparator);if(e.spreadsheet.decimalSeparator===e.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+e.spreadsheet.decimalSeparator+")";c.each(d,function(a,c){b+=g+'"'+(a.label||String.fromCharCode(65+c)).replace(/\"/g,'\\"')+'"'}),b+="%0D%0A",b+=c.reduce(f,function(b,c){var d=a.call(this,c[0])||"";d='"'+(d+"").replace(/\"/g,'\\"')+'"';var f=c.slice(1).join(g);return e.spreadsheet.decimalSeparator!=="."&&(f=f.replace(/\./g,e.spreadsheet.decimalSeparator)),b+d+g+f+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(b=b.replace(new RegExp(g,"g"),decodeURIComponent(g)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(b)):window.open("data:text/csv,"+b)}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var b,c=this.options,d=c.grid.labelMargin,e=this.ctx,f=this.axes;if(!c.HtmlText&&this.textEnabled){var g={size:c.fontSize,color:c.grid.color,textAlign:"center"};c.subtitle&&Flotr.drawText(e,c.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,g),g.weight=1.5,g.size*=1.5,c.title&&Flotr.drawText(e,c.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,g),g.weight=1.8,g.size*=.8,f.x.options.title&&f.x.used&&(g.textAlign=f.x.options.titleAlign||"center",g.textBaseline="top",g.angle=Flotr.toRad(f.x.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+f.x.maxLabel.height+this.plotHeight+2*d,g)),f.x2.options.title&&f.x2.used&&(g.textAlign=f.x2.options.titleAlign||"center",g.textBaseline="bottom",g.angle=Flotr.toRad(f.x2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-f.x2.maxLabel.height-2*d,g)),f.y.options.title&&f.y.used&&(g.textAlign=f.y.options.titleAlign||"right",g.textBaseline="middle",g.angle=Flotr.toRad(f.y.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y.options.title,this.plotOffset.left-f.y.maxLabel.width-2*d,this.plotOffset.top+this.plotHeight/2,g)),f.y2.options.title&&f.y2.used&&(g.textAlign=f.y2.options.titleAlign||"left",g.textBaseline="middle",g.angle=Flotr.toRad(f.y2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y2.options.title,this.plotOffset.left+this.plotWidth+f.y2.maxLabel.width+2*d,this.plotOffset.top+this.plotHeight/2,g))}else{b=[],c.title&&b.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;font-size:1em;font-weight:bold;text-align:center;width:",this.plotWidth,'px;" class="flotr-title">',c.title,"</div>"),c.subtitle&&b.push('<div style="position:absolute;top:',this.titleHeight,"px;left:",this.plotOffset.left,"px;font-size:smaller;text-align:center;width:",this.plotWidth,'px;" class="flotr-subtitle">',c.subtitle,"</div>"),b.push("</div>"),b.push('<div class="flotr-axis-title" style="font-weight:bold;">'),f.x.options.title&&f.x.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight+c.grid.labelMargin+f.x.titleSize.height,"px;left:",this.plotOffset.left,"px;width:",this.plotWidth,'px;text-align:center;" class="flotr-axis-title">',f.x.options.title,"</div>"),f.x2.options.title&&f.x2.used&&b.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;width:",this.plotWidth,'px;text-align:center;" class="flotr-axis-title">',f.x2.options.title,"</div>"),f.y.options.title&&f.y.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-f.y.titleSize.height/2,'px;left:0;text-align:right;" class="flotr-axis-title">',f.y.options.title,"</div>"),f.y2.options.title&&f.y2.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-f.y.titleSize.height/2,'px;right:0;text-align:right;" class="flotr-axis-title">',f.y2.options.title,"</div>"),b=b.join("");var h=a.create("div");a.setStyles({color:c.grid.color}),h.className="flotr-titles",a.insert(this.el,h),a.insert(h,b)}}})}();
</script>

