forked from opengaussexamples/examples
!21 StrongerGS数据库工具箱-全国半决赛提交代码
Merge pull request !21 from yinyuan/master
This commit is contained in:
commit
7eccef5b32
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
LICENSE
|
||||
Makefile.PL
|
||||
MANIFEST
|
||||
META.yml
|
||||
pgbadger
|
||||
README
|
||||
doc/pgBadger.pod
|
||||
ChangeLog
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
name: pgBadger
|
||||
version: 11.1
|
||||
version_from: pgbadger
|
||||
installdirs: site
|
||||
recommends:
|
||||
Text::CSV_XS: 0
|
||||
JSON::XS: 0
|
||||
|
||||
distribution_type: script
|
||||
generated_by: ExtUtils::MakeMaker version 6.17
|
||||
|
|
@ -0,0 +1,840 @@
|
|||
# This Makefile is for the pgBadger extension to perl.
|
||||
#
|
||||
# It was generated automatically by MakeMaker version
|
||||
# 6.68 (Revision: 66800) from the contents of
|
||||
# Makefile.PL. Don't edit this file, edit Makefile.PL instead.
|
||||
#
|
||||
# ANY CHANGES MADE HERE WILL BE LOST!
|
||||
#
|
||||
# MakeMaker ARGV: ()
|
||||
#
|
||||
|
||||
# MakeMaker Parameters:
|
||||
|
||||
# ABSTRACT => q[pgBadger - PostgreSQL log analysis report]
|
||||
# AUTHOR => [q[Gilles Darold (gilles@darold.net)]]
|
||||
# BUILD_REQUIRES => { }
|
||||
# CONFIGURE_REQUIRES => { }
|
||||
# DESTDIR => q[]
|
||||
# DISTNAME => q[pgbadger]
|
||||
# EXE_FILES => [q[pgbadger]]
|
||||
# INSTALLDIRS => q[site]
|
||||
# MAN1PODS => { doc/pgBadger.pod=>q[blib/man1/pgbadger.1p] }
|
||||
# META_MERGE => { resources=>{ repository=>{ web=>q[https://github.com/darold/pgbadger], git=>q[git@github.com:darold/pgbadger.git], type=>q[git] }, homepage=>q[http://pgbadger.darold.net/] } }
|
||||
# NAME => q[pgBadger]
|
||||
# PREREQ_PM => { }
|
||||
# TEST_REQUIRES => { }
|
||||
# VERSION_FROM => q[pgbadger]
|
||||
# clean => { }
|
||||
# dist => { ZIPFLAGS=>q[-rl], ZIP=>q[/usr/bin/zip], COMPRESS=>q[gzip -9f], SUFFIX=>q[gz] }
|
||||
|
||||
# --- MakeMaker post_initialize section:
|
||||
|
||||
|
||||
# --- MakeMaker const_config section:
|
||||
|
||||
# These definitions are from config.sh (via /usr/lib64/perl5/Config.pm).
|
||||
# They may have been overridden via Makefile.PL or on the command line.
|
||||
AR = ar
|
||||
CC = gcc
|
||||
CCCDLFLAGS = -fPIC
|
||||
CCDLFLAGS = -Wl,--enable-new-dtags -Wl,-rpath,/usr/lib64/perl5/CORE
|
||||
DLEXT = so
|
||||
DLSRC = dl_dlopen.xs
|
||||
EXE_EXT =
|
||||
FULL_AR = /usr/bin/ar
|
||||
LD = gcc
|
||||
LDDLFLAGS = -shared -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -Wl,-z,relro
|
||||
LDFLAGS = -fstack-protector
|
||||
LIBC =
|
||||
LIB_EXT = .a
|
||||
OBJ_EXT = .o
|
||||
OSNAME = linux
|
||||
OSVERS = 3.10.0-957.1.3.el7.x86_64
|
||||
RANLIB = :
|
||||
SITELIBEXP = /usr/local/share/perl5
|
||||
SITEARCHEXP = /usr/local/lib64/perl5
|
||||
SO = so
|
||||
VENDORARCHEXP = /usr/lib64/perl5/vendor_perl
|
||||
VENDORLIBEXP = /usr/share/perl5/vendor_perl
|
||||
|
||||
|
||||
# --- MakeMaker constants section:
|
||||
AR_STATIC_ARGS = cr
|
||||
DIRFILESEP = /
|
||||
DFSEP = $(DIRFILESEP)
|
||||
NAME = pgBadger
|
||||
NAME_SYM = pgBadger
|
||||
VERSION = 11.1
|
||||
VERSION_MACRO = VERSION
|
||||
VERSION_SYM = 11_1
|
||||
DEFINE_VERSION = -D$(VERSION_MACRO)=\"$(VERSION)\"
|
||||
XS_VERSION = 11.1
|
||||
XS_VERSION_MACRO = XS_VERSION
|
||||
XS_DEFINE_VERSION = -D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\"
|
||||
INST_ARCHLIB = blib/arch
|
||||
INST_SCRIPT = blib/script
|
||||
INST_BIN = blib/bin
|
||||
INST_LIB = blib/lib
|
||||
INST_MAN1DIR = blib/man1
|
||||
INST_MAN3DIR = blib/man3
|
||||
MAN1EXT = 1
|
||||
MAN3EXT = 3pm
|
||||
INSTALLDIRS = site
|
||||
DESTDIR =
|
||||
PREFIX = $(SITEPREFIX)
|
||||
PERLPREFIX = /usr
|
||||
SITEPREFIX = /usr/local
|
||||
VENDORPREFIX = /usr
|
||||
INSTALLPRIVLIB = /usr/share/perl5
|
||||
DESTINSTALLPRIVLIB = $(DESTDIR)$(INSTALLPRIVLIB)
|
||||
INSTALLSITELIB = /usr/local/share/perl5
|
||||
DESTINSTALLSITELIB = $(DESTDIR)$(INSTALLSITELIB)
|
||||
INSTALLVENDORLIB = /usr/share/perl5/vendor_perl
|
||||
DESTINSTALLVENDORLIB = $(DESTDIR)$(INSTALLVENDORLIB)
|
||||
INSTALLARCHLIB = /usr/lib64/perl5
|
||||
DESTINSTALLARCHLIB = $(DESTDIR)$(INSTALLARCHLIB)
|
||||
INSTALLSITEARCH = /usr/local/lib64/perl5
|
||||
DESTINSTALLSITEARCH = $(DESTDIR)$(INSTALLSITEARCH)
|
||||
INSTALLVENDORARCH = /usr/lib64/perl5/vendor_perl
|
||||
DESTINSTALLVENDORARCH = $(DESTDIR)$(INSTALLVENDORARCH)
|
||||
INSTALLBIN = /usr/bin
|
||||
DESTINSTALLBIN = $(DESTDIR)$(INSTALLBIN)
|
||||
INSTALLSITEBIN = /usr/local/bin
|
||||
DESTINSTALLSITEBIN = $(DESTDIR)$(INSTALLSITEBIN)
|
||||
INSTALLVENDORBIN = /usr/bin
|
||||
DESTINSTALLVENDORBIN = $(DESTDIR)$(INSTALLVENDORBIN)
|
||||
INSTALLSCRIPT = /usr/bin
|
||||
DESTINSTALLSCRIPT = $(DESTDIR)$(INSTALLSCRIPT)
|
||||
INSTALLSITESCRIPT = /usr/local/bin
|
||||
DESTINSTALLSITESCRIPT = $(DESTDIR)$(INSTALLSITESCRIPT)
|
||||
INSTALLVENDORSCRIPT = /usr/bin
|
||||
DESTINSTALLVENDORSCRIPT = $(DESTDIR)$(INSTALLVENDORSCRIPT)
|
||||
INSTALLMAN1DIR = /usr/share/man/man1
|
||||
DESTINSTALLMAN1DIR = $(DESTDIR)$(INSTALLMAN1DIR)
|
||||
INSTALLSITEMAN1DIR = /usr/local/share/man/man1
|
||||
DESTINSTALLSITEMAN1DIR = $(DESTDIR)$(INSTALLSITEMAN1DIR)
|
||||
INSTALLVENDORMAN1DIR = /usr/share/man/man1
|
||||
DESTINSTALLVENDORMAN1DIR = $(DESTDIR)$(INSTALLVENDORMAN1DIR)
|
||||
INSTALLMAN3DIR = /usr/share/man/man3
|
||||
DESTINSTALLMAN3DIR = $(DESTDIR)$(INSTALLMAN3DIR)
|
||||
INSTALLSITEMAN3DIR = /usr/local/share/man/man3
|
||||
DESTINSTALLSITEMAN3DIR = $(DESTDIR)$(INSTALLSITEMAN3DIR)
|
||||
INSTALLVENDORMAN3DIR = /usr/share/man/man3
|
||||
DESTINSTALLVENDORMAN3DIR = $(DESTDIR)$(INSTALLVENDORMAN3DIR)
|
||||
PERL_LIB = /usr/share/perl5
|
||||
PERL_ARCHLIB = /usr/lib64/perl5
|
||||
LIBPERL_A = libperl.a
|
||||
FIRST_MAKEFILE = Makefile
|
||||
MAKEFILE_OLD = Makefile.old
|
||||
MAKE_APERL_FILE = Makefile.aperl
|
||||
PERLMAINCC = $(CC)
|
||||
PERL_INC = /usr/lib64/perl5/CORE
|
||||
PERL = /usr/bin/perl
|
||||
FULLPERL = /usr/bin/perl
|
||||
ABSPERL = $(PERL)
|
||||
PERLRUN = $(PERL)
|
||||
FULLPERLRUN = $(FULLPERL)
|
||||
ABSPERLRUN = $(ABSPERL)
|
||||
PERLRUNINST = $(PERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
|
||||
FULLPERLRUNINST = $(FULLPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
|
||||
ABSPERLRUNINST = $(ABSPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
|
||||
PERL_CORE = 0
|
||||
PERM_DIR = 755
|
||||
PERM_RW = 644
|
||||
PERM_RWX = 755
|
||||
|
||||
MAKEMAKER = /usr/share/perl5/vendor_perl/ExtUtils/MakeMaker.pm
|
||||
MM_VERSION = 6.68
|
||||
MM_REVISION = 66800
|
||||
|
||||
# FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle).
|
||||
# BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle)
|
||||
# PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar)
|
||||
# DLBASE = Basename part of dynamic library. May be just equal BASEEXT.
|
||||
MAKE = make
|
||||
FULLEXT = pgBadger
|
||||
BASEEXT = pgBadger
|
||||
PARENT_NAME =
|
||||
DLBASE = $(BASEEXT)
|
||||
VERSION_FROM = pgbadger
|
||||
OBJECT =
|
||||
LDFROM = $(OBJECT)
|
||||
LINKTYPE = dynamic
|
||||
BOOTDEP =
|
||||
|
||||
# Handy lists of source code files:
|
||||
XS_FILES =
|
||||
C_FILES =
|
||||
O_FILES =
|
||||
H_FILES =
|
||||
MAN1PODS = doc/pgBadger.pod
|
||||
MAN3PODS =
|
||||
|
||||
# Where is the Config information that we are using/depend on
|
||||
CONFIGDEP = $(PERL_ARCHLIB)$(DFSEP)Config.pm $(PERL_INC)$(DFSEP)config.h
|
||||
|
||||
# Where to build things
|
||||
INST_LIBDIR = $(INST_LIB)
|
||||
INST_ARCHLIBDIR = $(INST_ARCHLIB)
|
||||
|
||||
INST_AUTODIR = $(INST_LIB)/auto/$(FULLEXT)
|
||||
INST_ARCHAUTODIR = $(INST_ARCHLIB)/auto/$(FULLEXT)
|
||||
|
||||
INST_STATIC =
|
||||
INST_DYNAMIC =
|
||||
INST_BOOT =
|
||||
|
||||
# Extra linker info
|
||||
EXPORT_LIST =
|
||||
PERL_ARCHIVE =
|
||||
PERL_ARCHIVE_AFTER =
|
||||
|
||||
|
||||
TO_INST_PM =
|
||||
|
||||
PM_TO_BLIB =
|
||||
|
||||
|
||||
# --- MakeMaker platform_constants section:
|
||||
MM_Unix_VERSION = 6.68
|
||||
PERL_MALLOC_DEF = -DPERL_EXTMALLOC_DEF -Dmalloc=Perl_malloc -Dfree=Perl_mfree -Drealloc=Perl_realloc -Dcalloc=Perl_calloc
|
||||
|
||||
|
||||
# --- MakeMaker tool_autosplit section:
|
||||
# Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto
|
||||
AUTOSPLITFILE = $(ABSPERLRUN) -e 'use AutoSplit; autosplit($$$$ARGV[0], $$$$ARGV[1], 0, 1, 1)' --
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker tool_xsubpp section:
|
||||
|
||||
|
||||
# --- MakeMaker tools_other section:
|
||||
SHELL = /bin/sh
|
||||
CHMOD = chmod
|
||||
CP = cp
|
||||
MV = mv
|
||||
NOOP = $(TRUE)
|
||||
NOECHO = @
|
||||
RM_F = rm -f
|
||||
RM_RF = rm -rf
|
||||
TEST_F = test -f
|
||||
TOUCH = touch
|
||||
UMASK_NULL = umask 0
|
||||
DEV_NULL = > /dev/null 2>&1
|
||||
MKPATH = $(ABSPERLRUN) -MExtUtils::Command -e 'mkpath' --
|
||||
EQUALIZE_TIMESTAMP = $(ABSPERLRUN) -MExtUtils::Command -e 'eqtime' --
|
||||
FALSE = false
|
||||
TRUE = true
|
||||
ECHO = echo
|
||||
ECHO_N = echo -n
|
||||
UNINST = 0
|
||||
VERBINST = 0
|
||||
MOD_INSTALL = $(ABSPERLRUN) -MExtUtils::Install -e 'install([ from_to => {@ARGV}, verbose => '\''$(VERBINST)'\'', uninstall_shadows => '\''$(UNINST)'\'', dir_mode => '\''$(PERM_DIR)'\'' ]);' --
|
||||
DOC_INSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'perllocal_install' --
|
||||
UNINSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'uninstall' --
|
||||
WARN_IF_OLD_PACKLIST = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'warn_if_old_packlist' --
|
||||
MACROSTART =
|
||||
MACROEND =
|
||||
USEMAKEFILE = -f
|
||||
FIXIN = $(ABSPERLRUN) -MExtUtils::MY -e 'MY->fixin(shift)' --
|
||||
|
||||
|
||||
# --- MakeMaker makemakerdflt section:
|
||||
makemakerdflt : all
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker dist section:
|
||||
TAR = tar
|
||||
TARFLAGS = cvf
|
||||
ZIP = /usr/bin/zip
|
||||
ZIPFLAGS = -rl
|
||||
COMPRESS = gzip -9f
|
||||
SUFFIX = gz
|
||||
SHAR = shar
|
||||
PREOP = $(NOECHO) $(NOOP)
|
||||
POSTOP = $(NOECHO) $(NOOP)
|
||||
TO_UNIX = $(NOECHO) $(NOOP)
|
||||
CI = ci -u
|
||||
RCS_LABEL = rcs -Nv$(VERSION_SYM): -q
|
||||
DIST_CP = best
|
||||
DIST_DEFAULT = tardist
|
||||
DISTNAME = pgbadger
|
||||
DISTVNAME = pgbadger-11.1
|
||||
|
||||
|
||||
# --- MakeMaker macro section:
|
||||
|
||||
|
||||
# --- MakeMaker depend section:
|
||||
|
||||
|
||||
# --- MakeMaker cflags section:
|
||||
|
||||
|
||||
# --- MakeMaker const_loadlibs section:
|
||||
|
||||
|
||||
# --- MakeMaker const_cccmd section:
|
||||
|
||||
|
||||
# --- MakeMaker post_constants section:
|
||||
|
||||
|
||||
# --- MakeMaker pasthru section:
|
||||
|
||||
PASTHRU = LIBPERL_A="$(LIBPERL_A)"\
|
||||
LINKTYPE="$(LINKTYPE)"\
|
||||
PREFIX="$(PREFIX)"
|
||||
|
||||
|
||||
# --- MakeMaker special_targets section:
|
||||
.SUFFIXES : .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT)
|
||||
|
||||
.PHONY: all config static dynamic test linkext manifest blibdirs clean realclean disttest distdir
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker c_o section:
|
||||
|
||||
|
||||
# --- MakeMaker xs_c section:
|
||||
|
||||
|
||||
# --- MakeMaker xs_o section:
|
||||
|
||||
|
||||
# --- MakeMaker top_targets section:
|
||||
all :: pure_all manifypods
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
pure_all :: config pm_to_blib subdirs linkext
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
subdirs :: $(MYEXTLIB)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
config :: $(FIRST_MAKEFILE) blibdirs
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
help :
|
||||
perldoc ExtUtils::MakeMaker
|
||||
|
||||
|
||||
# --- MakeMaker blibdirs section:
|
||||
blibdirs : $(INST_LIBDIR)$(DFSEP).exists $(INST_ARCHLIB)$(DFSEP).exists $(INST_AUTODIR)$(DFSEP).exists $(INST_ARCHAUTODIR)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists $(INST_SCRIPT)$(DFSEP).exists $(INST_MAN1DIR)$(DFSEP).exists $(INST_MAN3DIR)$(DFSEP).exists
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
# Backwards compat with 6.18 through 6.25
|
||||
blibdirs.ts : blibdirs
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
$(INST_LIBDIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_LIBDIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_LIBDIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_LIBDIR)$(DFSEP).exists
|
||||
|
||||
$(INST_ARCHLIB)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_ARCHLIB)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHLIB)
|
||||
$(NOECHO) $(TOUCH) $(INST_ARCHLIB)$(DFSEP).exists
|
||||
|
||||
$(INST_AUTODIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_AUTODIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_AUTODIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_AUTODIR)$(DFSEP).exists
|
||||
|
||||
$(INST_ARCHAUTODIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_ARCHAUTODIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHAUTODIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_ARCHAUTODIR)$(DFSEP).exists
|
||||
|
||||
$(INST_BIN)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_BIN)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_BIN)
|
||||
$(NOECHO) $(TOUCH) $(INST_BIN)$(DFSEP).exists
|
||||
|
||||
$(INST_SCRIPT)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_SCRIPT)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_SCRIPT)
|
||||
$(NOECHO) $(TOUCH) $(INST_SCRIPT)$(DFSEP).exists
|
||||
|
||||
$(INST_MAN1DIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_MAN1DIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN1DIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_MAN1DIR)$(DFSEP).exists
|
||||
|
||||
$(INST_MAN3DIR)$(DFSEP).exists :: Makefile.PL
|
||||
$(NOECHO) $(MKPATH) $(INST_MAN3DIR)
|
||||
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN3DIR)
|
||||
$(NOECHO) $(TOUCH) $(INST_MAN3DIR)$(DFSEP).exists
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker linkext section:
|
||||
|
||||
linkext :: $(LINKTYPE)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker dlsyms section:
|
||||
|
||||
|
||||
# --- MakeMaker dynamic section:
|
||||
|
||||
dynamic :: $(FIRST_MAKEFILE) $(INST_DYNAMIC) $(INST_BOOT)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker dynamic_bs section:
|
||||
|
||||
BOOTSTRAP =
|
||||
|
||||
|
||||
# --- MakeMaker dynamic_lib section:
|
||||
|
||||
|
||||
# --- MakeMaker static section:
|
||||
|
||||
## $(INST_PM) has been moved to the all: target.
|
||||
## It remains here for awhile to allow for old usage: "make static"
|
||||
static :: $(FIRST_MAKEFILE) $(INST_STATIC)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker static_lib section:
|
||||
|
||||
|
||||
# --- MakeMaker manifypods section:
|
||||
|
||||
POD2MAN_EXE = $(PERLRUN) "-MExtUtils::Command::MM" -e pod2man "--"
|
||||
POD2MAN = $(POD2MAN_EXE)
|
||||
|
||||
|
||||
manifypods : pure_all \
|
||||
doc/pgBadger.pod
|
||||
$(NOECHO) $(POD2MAN) --section=1 --perm_rw=$(PERM_RW) \
|
||||
doc/pgBadger.pod blib/man1/pgbadger.1p
|
||||
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker processPL section:
|
||||
|
||||
|
||||
# --- MakeMaker installbin section:
|
||||
|
||||
EXE_FILES = pgbadger
|
||||
|
||||
pure_all :: $(INST_SCRIPT)/pgbadger
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
realclean ::
|
||||
$(RM_F) \
|
||||
$(INST_SCRIPT)/pgbadger
|
||||
|
||||
$(INST_SCRIPT)/pgbadger : pgbadger $(FIRST_MAKEFILE) $(INST_SCRIPT)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists
|
||||
$(NOECHO) $(RM_F) $(INST_SCRIPT)/pgbadger
|
||||
$(CP) pgbadger $(INST_SCRIPT)/pgbadger
|
||||
$(FIXIN) $(INST_SCRIPT)/pgbadger
|
||||
-$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_SCRIPT)/pgbadger
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker subdirs section:
|
||||
|
||||
# none
|
||||
|
||||
# --- MakeMaker clean_subdirs section:
|
||||
clean_subdirs :
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker clean section:
|
||||
|
||||
# Delete temporary files but do not touch installed files. We don't delete
|
||||
# the Makefile here so a later make realclean still has a makefile to use.
|
||||
|
||||
clean :: clean_subdirs
|
||||
- $(RM_F) \
|
||||
*$(LIB_EXT) core \
|
||||
core.[0-9] $(INST_ARCHAUTODIR)/extralibs.all \
|
||||
core.[0-9][0-9] $(BASEEXT).bso \
|
||||
pm_to_blib.ts MYMETA.json \
|
||||
core.[0-9][0-9][0-9][0-9] MYMETA.yml \
|
||||
$(BASEEXT).x $(BOOTSTRAP) \
|
||||
perl$(EXE_EXT) tmon.out \
|
||||
*$(OBJ_EXT) pm_to_blib \
|
||||
$(INST_ARCHAUTODIR)/extralibs.ld blibdirs.ts \
|
||||
core.[0-9][0-9][0-9][0-9][0-9] *perl.core \
|
||||
core.*perl.*.? $(MAKE_APERL_FILE) \
|
||||
$(BASEEXT).def perl \
|
||||
core.[0-9][0-9][0-9] mon.out \
|
||||
lib$(BASEEXT).def perlmain.c \
|
||||
perl.exe so_locations \
|
||||
$(BASEEXT).exp
|
||||
- $(RM_RF) \
|
||||
blib
|
||||
- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)
|
||||
|
||||
|
||||
# --- MakeMaker realclean_subdirs section:
|
||||
realclean_subdirs :
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker realclean section:
|
||||
# Delete temporary files (via clean) and also delete dist files
|
||||
realclean purge :: clean realclean_subdirs
|
||||
- $(RM_F) \
|
||||
$(MAKEFILE_OLD) $(FIRST_MAKEFILE)
|
||||
- $(RM_RF) \
|
||||
$(DISTVNAME)
|
||||
|
||||
|
||||
# --- MakeMaker metafile section:
|
||||
metafile :
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker signature section:
|
||||
signature :
|
||||
cpansign -s
|
||||
|
||||
|
||||
# --- MakeMaker dist_basics section:
|
||||
distclean :: realclean distcheck
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
distcheck :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck
|
||||
|
||||
skipcheck :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck
|
||||
|
||||
manifest :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest
|
||||
|
||||
veryclean : realclean
|
||||
$(RM_F) *~ */*~ *.orig */*.orig *.bak */*.bak *.old */*.old
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker dist_core section:
|
||||
|
||||
dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE)
|
||||
$(NOECHO) $(ABSPERLRUN) -l -e 'print '\''Warning: Makefile possibly out of date with $(VERSION_FROM)'\''' \
|
||||
-e ' if -e '\''$(VERSION_FROM)'\'' and -M '\''$(VERSION_FROM)'\'' < -M '\''$(FIRST_MAKEFILE)'\'';' --
|
||||
|
||||
tardist : $(DISTVNAME).tar$(SUFFIX)
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
uutardist : $(DISTVNAME).tar$(SUFFIX)
|
||||
uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu
|
||||
|
||||
$(DISTVNAME).tar$(SUFFIX) : distdir
|
||||
$(PREOP)
|
||||
$(TO_UNIX)
|
||||
$(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME)
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(COMPRESS) $(DISTVNAME).tar
|
||||
$(POSTOP)
|
||||
|
||||
zipdist : $(DISTVNAME).zip
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
$(DISTVNAME).zip : distdir
|
||||
$(PREOP)
|
||||
$(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME)
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(POSTOP)
|
||||
|
||||
shdist : distdir
|
||||
$(PREOP)
|
||||
$(SHAR) $(DISTVNAME) > $(DISTVNAME).shar
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(POSTOP)
|
||||
|
||||
|
||||
# --- MakeMaker distdir section:
|
||||
create_distdir :
|
||||
$(RM_RF) $(DISTVNAME)
|
||||
$(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \
|
||||
-e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');"
|
||||
|
||||
distdir : create_distdir distmeta
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker dist_test section:
|
||||
disttest : distdir
|
||||
cd $(DISTVNAME) && $(ABSPERLRUN) Makefile.PL
|
||||
cd $(DISTVNAME) && $(MAKE) $(PASTHRU)
|
||||
cd $(DISTVNAME) && $(MAKE) test $(PASTHRU)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker dist_ci section:
|
||||
|
||||
ci :
|
||||
$(PERLRUN) "-MExtUtils::Manifest=maniread" \
|
||||
-e "@all = keys %{ maniread() };" \
|
||||
-e "print(qq{Executing $(CI) @all\n}); system(qq{$(CI) @all});" \
|
||||
-e "print(qq{Executing $(RCS_LABEL) ...\n}); system(qq{$(RCS_LABEL) @all});"
|
||||
|
||||
|
||||
# --- MakeMaker distmeta section:
|
||||
distmeta : create_distdir metafile
|
||||
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'exit unless -e q{META.yml};' \
|
||||
-e 'eval { maniadd({q{META.yml} => q{Module YAML meta-data (added by MakeMaker)}}) }' \
|
||||
-e ' or print "Could not add META.yml to MANIFEST: $$$${'\''@'\''}\n"' --
|
||||
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'exit unless -f q{META.json};' \
|
||||
-e 'eval { maniadd({q{META.json} => q{Module JSON meta-data (added by MakeMaker)}}) }' \
|
||||
-e ' or print "Could not add META.json to MANIFEST: $$$${'\''@'\''}\n"' --
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker distsignature section:
|
||||
distsignature : create_distdir
|
||||
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'eval { maniadd({q{SIGNATURE} => q{Public-key signature (added by MakeMaker)}}) } ' \
|
||||
-e ' or print "Could not add SIGNATURE to MANIFEST: $$$${'\''@'\''}\n"' --
|
||||
$(NOECHO) cd $(DISTVNAME) && $(TOUCH) SIGNATURE
|
||||
cd $(DISTVNAME) && cpansign -s
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker install section:
|
||||
|
||||
install :: pure_install doc_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
install_perl :: pure_perl_install doc_perl_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
install_site :: pure_site_install doc_site_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
install_vendor :: pure_vendor_install doc_vendor_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
pure_install :: pure_$(INSTALLDIRS)_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
doc_install :: doc_$(INSTALLDIRS)_install
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
pure__install : pure_site_install
|
||||
$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
|
||||
|
||||
doc__install : doc_site_install
|
||||
$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
|
||||
|
||||
pure_perl_install :: all
|
||||
$(NOECHO) $(MOD_INSTALL) \
|
||||
read $(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist \
|
||||
write $(DESTINSTALLARCHLIB)/auto/$(FULLEXT)/.packlist \
|
||||
$(INST_LIB) $(DESTINSTALLPRIVLIB) \
|
||||
$(INST_ARCHLIB) $(DESTINSTALLARCHLIB) \
|
||||
$(INST_BIN) $(DESTINSTALLBIN) \
|
||||
$(INST_SCRIPT) $(DESTINSTALLSCRIPT) \
|
||||
$(INST_MAN1DIR) $(DESTINSTALLMAN1DIR) \
|
||||
$(INST_MAN3DIR) $(DESTINSTALLMAN3DIR)
|
||||
$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
|
||||
$(SITEARCHEXP)/auto/$(FULLEXT)
|
||||
|
||||
|
||||
pure_site_install :: all
|
||||
$(NOECHO) $(MOD_INSTALL) \
|
||||
read $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist \
|
||||
write $(DESTINSTALLSITEARCH)/auto/$(FULLEXT)/.packlist \
|
||||
$(INST_LIB) $(DESTINSTALLSITELIB) \
|
||||
$(INST_ARCHLIB) $(DESTINSTALLSITEARCH) \
|
||||
$(INST_BIN) $(DESTINSTALLSITEBIN) \
|
||||
$(INST_SCRIPT) $(DESTINSTALLSITESCRIPT) \
|
||||
$(INST_MAN1DIR) $(DESTINSTALLSITEMAN1DIR) \
|
||||
$(INST_MAN3DIR) $(DESTINSTALLSITEMAN3DIR)
|
||||
$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
|
||||
$(PERL_ARCHLIB)/auto/$(FULLEXT)
|
||||
|
||||
pure_vendor_install :: all
|
||||
$(NOECHO) $(MOD_INSTALL) \
|
||||
read $(VENDORARCHEXP)/auto/$(FULLEXT)/.packlist \
|
||||
write $(DESTINSTALLVENDORARCH)/auto/$(FULLEXT)/.packlist \
|
||||
$(INST_LIB) $(DESTINSTALLVENDORLIB) \
|
||||
$(INST_ARCHLIB) $(DESTINSTALLVENDORARCH) \
|
||||
$(INST_BIN) $(DESTINSTALLVENDORBIN) \
|
||||
$(INST_SCRIPT) $(DESTINSTALLVENDORSCRIPT) \
|
||||
$(INST_MAN1DIR) $(DESTINSTALLVENDORMAN1DIR) \
|
||||
$(INST_MAN3DIR) $(DESTINSTALLVENDORMAN3DIR)
|
||||
|
||||
doc_perl_install :: all
|
||||
$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod
|
||||
-$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB)
|
||||
-$(NOECHO) $(DOC_INSTALL) \
|
||||
"Module" "$(NAME)" \
|
||||
"installed into" "$(INSTALLPRIVLIB)" \
|
||||
LINKTYPE "$(LINKTYPE)" \
|
||||
VERSION "$(VERSION)" \
|
||||
EXE_FILES "$(EXE_FILES)" \
|
||||
>> $(DESTINSTALLARCHLIB)/perllocal.pod
|
||||
|
||||
doc_site_install :: all
|
||||
$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod
|
||||
-$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB)
|
||||
-$(NOECHO) $(DOC_INSTALL) \
|
||||
"Module" "$(NAME)" \
|
||||
"installed into" "$(INSTALLSITELIB)" \
|
||||
LINKTYPE "$(LINKTYPE)" \
|
||||
VERSION "$(VERSION)" \
|
||||
EXE_FILES "$(EXE_FILES)" \
|
||||
>> $(DESTINSTALLARCHLIB)/perllocal.pod
|
||||
|
||||
doc_vendor_install :: all
|
||||
$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod
|
||||
-$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB)
|
||||
-$(NOECHO) $(DOC_INSTALL) \
|
||||
"Module" "$(NAME)" \
|
||||
"installed into" "$(INSTALLVENDORLIB)" \
|
||||
LINKTYPE "$(LINKTYPE)" \
|
||||
VERSION "$(VERSION)" \
|
||||
EXE_FILES "$(EXE_FILES)" \
|
||||
>> $(DESTINSTALLARCHLIB)/perllocal.pod
|
||||
|
||||
|
||||
uninstall :: uninstall_from_$(INSTALLDIRS)dirs
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
uninstall_from_perldirs ::
|
||||
$(NOECHO) $(UNINSTALL) $(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist
|
||||
|
||||
uninstall_from_sitedirs ::
|
||||
$(NOECHO) $(UNINSTALL) $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist
|
||||
|
||||
uninstall_from_vendordirs ::
|
||||
$(NOECHO) $(UNINSTALL) $(VENDORARCHEXP)/auto/$(FULLEXT)/.packlist
|
||||
|
||||
|
||||
# --- MakeMaker force section:
|
||||
# Phony target to force checking subdirectories.
|
||||
FORCE :
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
# --- MakeMaker perldepend section:
|
||||
|
||||
|
||||
# --- MakeMaker makefile section:
|
||||
# We take a very conservative approach here, but it's worth it.
|
||||
# We move Makefile to Makefile.old here to avoid gnu make looping.
|
||||
$(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP)
|
||||
$(NOECHO) $(ECHO) "Makefile out-of-date with respect to $?"
|
||||
$(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..."
|
||||
-$(NOECHO) $(RM_F) $(MAKEFILE_OLD)
|
||||
-$(NOECHO) $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD)
|
||||
- $(MAKE) $(USEMAKEFILE) $(MAKEFILE_OLD) clean $(DEV_NULL)
|
||||
$(PERLRUN) Makefile.PL
|
||||
$(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <=="
|
||||
$(NOECHO) $(ECHO) "==> Please rerun the $(MAKE) command. <=="
|
||||
$(FALSE)
|
||||
|
||||
|
||||
|
||||
# --- MakeMaker staticmake section:
|
||||
|
||||
# --- MakeMaker makeaperl section ---
|
||||
MAP_TARGET = perl
|
||||
FULLPERL = /usr/bin/perl
|
||||
|
||||
$(MAP_TARGET) :: static $(MAKE_APERL_FILE)
|
||||
$(MAKE) $(USEMAKEFILE) $(MAKE_APERL_FILE) $@
|
||||
|
||||
$(MAKE_APERL_FILE) : $(FIRST_MAKEFILE) pm_to_blib
|
||||
$(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET)
|
||||
$(NOECHO) $(PERLRUNINST) \
|
||||
Makefile.PL DIR= \
|
||||
MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \
|
||||
MAKEAPERL=1 NORECURS=1 CCCDLFLAGS=
|
||||
|
||||
|
||||
# --- MakeMaker test section:
|
||||
|
||||
TEST_VERBOSE=0
|
||||
TEST_TYPE=test_$(LINKTYPE)
|
||||
TEST_FILE = test.pl
|
||||
TEST_FILES = t/*.t
|
||||
TESTDB_SW = -d
|
||||
|
||||
testdb :: testdb_$(LINKTYPE)
|
||||
|
||||
test :: $(TEST_TYPE) subdirs-test
|
||||
|
||||
subdirs-test ::
|
||||
$(NOECHO) $(NOOP)
|
||||
|
||||
|
||||
test_dynamic :: pure_all
|
||||
PERL_DL_NONLAZY=1 $(FULLPERLRUN) "-MExtUtils::Command::MM" "-e" "test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES)
|
||||
|
||||
testdb_dynamic :: pure_all
|
||||
PERL_DL_NONLAZY=1 $(FULLPERLRUN) $(TESTDB_SW) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE)
|
||||
|
||||
test_ : test_dynamic
|
||||
|
||||
test_static :: test_dynamic
|
||||
testdb_static :: testdb_dynamic
|
||||
|
||||
|
||||
# --- MakeMaker ppd section:
|
||||
# Creates a PPD (Perl Package Description) for a binary distribution.
|
||||
ppd :
|
||||
$(NOECHO) $(ECHO) '<SOFTPKG NAME="$(DISTNAME)" VERSION="$(VERSION)">' > $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) ' <ABSTRACT>pgBadger - PostgreSQL log analysis report</ABSTRACT>' >> $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) ' <AUTHOR>Gilles Darold (gilles@darold.net)</AUTHOR>' >> $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) ' <IMPLEMENTATION>' >> $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) ' <ARCHITECTURE NAME="x86_64-linux-thread-multi-5.16" />' >> $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) ' <CODEBASE HREF="" />' >> $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) ' </IMPLEMENTATION>' >> $(DISTNAME).ppd
|
||||
$(NOECHO) $(ECHO) '</SOFTPKG>' >> $(DISTNAME).ppd
|
||||
|
||||
|
||||
# --- MakeMaker pm_to_blib section:
|
||||
|
||||
pm_to_blib : $(FIRST_MAKEFILE) $(TO_INST_PM)
|
||||
$(NOECHO) $(TOUCH) pm_to_blib
|
||||
|
||||
|
||||
# --- MakeMaker selfdocument section:
|
||||
|
||||
|
||||
# --- MakeMaker postamble section:
|
||||
USE_MARKDOWN=$(shell which pod2markdown)
|
||||
|
||||
README: doc/pgBadger.pod
|
||||
pod2text $^ > $@
|
||||
ifneq ("$(USE_MARKDOWN)", "")
|
||||
cat doc/pgBadger.pod | grep "=head1 " | sed 's/^=head1 \(.*\)/- [\1](#\1)/' | sed 's/ /-/g' | sed 's/--/- /' > $@.md
|
||||
sed -i '1s/^/### TABLE OF CONTENTS\n\n/' $@.md
|
||||
echo >> $@.md
|
||||
pod2markdown $^ | sed 's/^## /#### /' | sed 's/^# /### /' >> $@.md
|
||||
endif
|
||||
|
||||
.INTERMEDIATE: doc/synopsis.pod
|
||||
doc/synopsis.pod: Makefile pgbadger
|
||||
echo "=head1 SYNOPSIS" > $@
|
||||
./pgbadger --help >> $@
|
||||
echo "=head1 DESCRIPTION" >> $@
|
||||
sed -i.bak 's/ +$$//g' $@
|
||||
rm $@.bak
|
||||
|
||||
.PHONY: doc/pgBadger.pod
|
||||
doc/pgBadger.pod: doc/synopsis.pod Makefile
|
||||
sed -i.bak '/^=head1 SYNOPSIS/,/^=head1 DESCRIPTION/d' $@
|
||||
sed -i.bak '4r $<' $@
|
||||
rm $@.bak
|
||||
|
||||
|
||||
# End.
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
use ExtUtils::MakeMaker;
|
||||
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
|
||||
# the contents of the Makefile that is written.
|
||||
|
||||
use strict;
|
||||
|
||||
my @ALLOWED_ARGS = ('INSTALLDIRS','DESTDIR');
|
||||
|
||||
# Parse command line arguments and store them as environment variables
|
||||
while ($_ = shift) {
|
||||
my ($k,$v) = split(/=/, $_, 2);
|
||||
if (grep(/^$k$/, @ALLOWED_ARGS)) {
|
||||
$ENV{$k} = $v;
|
||||
}
|
||||
}
|
||||
$ENV{DESTDIR} =~ s/\/$//;
|
||||
|
||||
# Default install path
|
||||
my $DESTDIR = $ENV{DESTDIR} || '';
|
||||
my $INSTALLDIRS = $ENV{INSTALLDIRS} || 'site';
|
||||
my %merge_compat = ();
|
||||
|
||||
if ($ExtUtils::MakeMaker::VERSION >= 6.46) {
|
||||
%merge_compat = (
|
||||
'META_MERGE' => {
|
||||
resources => {
|
||||
homepage => 'http://pgbadger.darold.net/',
|
||||
repository => {
|
||||
type => 'git',
|
||||
git => 'git@github.com:darold/pgbadger.git',
|
||||
web => 'https://github.com/darold/pgbadger',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub MY::postamble {
|
||||
return <<'EOMAKE';
|
||||
USE_MARKDOWN=$(shell which pod2markdown)
|
||||
|
||||
README: doc/pgBadger.pod
|
||||
pod2text $^ > $@
|
||||
ifneq ("$(USE_MARKDOWN)", "")
|
||||
cat doc/pgBadger.pod | grep "=head1 " | sed 's/^=head1 \(.*\)/- [\1](#\1)/' | sed 's/ /-/g' | sed 's/--/- /' > $@.md
|
||||
sed -i '1s/^/### TABLE OF CONTENTS\n\n/' $@.md
|
||||
echo >> $@.md
|
||||
pod2markdown $^ | sed 's/^## /#### /' | sed 's/^# /### /' >> $@.md
|
||||
endif
|
||||
|
||||
.INTERMEDIATE: doc/synopsis.pod
|
||||
doc/synopsis.pod: Makefile pgbadger
|
||||
echo "=head1 SYNOPSIS" > $@
|
||||
./pgbadger --help >> $@
|
||||
echo "=head1 DESCRIPTION" >> $@
|
||||
sed -i.bak 's/ +$$//g' $@
|
||||
rm $@.bak
|
||||
|
||||
.PHONY: doc/pgBadger.pod
|
||||
doc/pgBadger.pod: doc/synopsis.pod Makefile
|
||||
sed -i.bak '/^=head1 SYNOPSIS/,/^=head1 DESCRIPTION/d' $@
|
||||
sed -i.bak '4r $<' $@
|
||||
rm $@.bak
|
||||
EOMAKE
|
||||
}
|
||||
|
||||
WriteMakefile(
|
||||
'DISTNAME' => 'pgbadger',
|
||||
'NAME' => 'pgBadger',
|
||||
'VERSION_FROM' => 'pgbadger',
|
||||
'dist' => {
|
||||
'COMPRESS'=>'gzip -9f', 'SUFFIX' => 'gz',
|
||||
'ZIP'=>'/usr/bin/zip','ZIPFLAGS'=>'-rl'
|
||||
},
|
||||
'AUTHOR' => 'Gilles Darold (gilles@darold.net)',
|
||||
'ABSTRACT' => 'pgBadger - PostgreSQL log analysis report',
|
||||
'EXE_FILES' => [ qw(pgbadger) ],
|
||||
'MAN1PODS' => { 'doc/pgBadger.pod' => 'blib/man1/pgbadger.1p' },
|
||||
'DESTDIR' => $DESTDIR,
|
||||
'INSTALLDIRS' => $INSTALLDIRS,
|
||||
'clean' => {},
|
||||
%merge_compat
|
||||
);
|
||||
|
|
@ -0,0 +1,737 @@
|
|||
### TABLE OF CONTENTS
|
||||
|
||||
- [NAME](#NAME)
|
||||
- [SYNOPSIS](#SYNOPSIS)
|
||||
- [DESCRIPTION](#DESCRIPTION)
|
||||
- [FEATURE](#FEATURE)
|
||||
- [REQUIREMENT](#REQUIREMENT)
|
||||
- [INSTALLATION](#INSTALLATION)
|
||||
- [openGauss-CONFIGURATION](#openGauss-CONFIGURATION)
|
||||
- [LOG-STATEMENTS](#LOG-STATEMENTS)
|
||||
- [PARALLEL-PROCESSING](#PARALLEL-PROCESSING)
|
||||
- [INCREMENTAL-REPORTS](#INCREMENTAL-REPORTS)
|
||||
- [BINARY-FORMAT](#BINARY-FORMAT)
|
||||
- [JSON-FORMAT](#JSON-FORMAT)
|
||||
- [AUTHORS](#AUTHORS)
|
||||
- [LICENSE](#LICENSE)
|
||||
|
||||
### NAME
|
||||
|
||||
gsbadger - a fast openGauss log analysis report
|
||||
|
||||
### SYNOPSIS
|
||||
|
||||
Usage: gsbadger \[options\] logfile \[...\]
|
||||
|
||||
openGauss 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 files. If you want to pass log content from stdin
|
||||
use - as filename. Note that input from stdin will not work with csvlog.
|
||||
|
||||
Options:
|
||||
|
||||
-a | --average minutes : number of minutes to build the average graphs of
|
||||
queries and connections. Default 5 minutes.
|
||||
-A | --histo-average min: number of minutes to build the histogram graphs
|
||||
of queries. Default 60 minutes.
|
||||
-b | --begin datetime : start date/time for the data to be parsed in log
|
||||
(either a timestamp or a time)
|
||||
-c | --dbclient host : only report on entries for the given client host.
|
||||
-C | --nocomment : remove comments like /* ... */ from queries.
|
||||
-d | --dbname database : only report on entries for the given database.
|
||||
-D | --dns-resolv : client ip addresses are replaced by their DNS name.
|
||||
Be warned that this can really slow down gsbadger.
|
||||
-e | --end datetime : end date/time for the data to be parsed in log
|
||||
(either a timestamp or a time)
|
||||
-E | --explode : explode the main report by generating one report
|
||||
per database. Global information not related to a
|
||||
database are added to the postgres database report.
|
||||
-f | --format logtype : possible values: syslog, syslog2, stderr, jsonlog,
|
||||
cvs, pgbouncer, logplex and rds. Use this option
|
||||
when gsbadger is not able to detect the log format.
|
||||
-G | --nograph : disable graphs on HTML output. Enabled by default.
|
||||
-h | --help : show this message and exit.
|
||||
-H | --html-dir path : path to directory where HTML report must be written
|
||||
in incremental mode, binary files stay on directory
|
||||
defined with -O, --outdir option.
|
||||
-i | --ident name : programname used as syslog ident. Default: postgres
|
||||
-I | --incremental : use incremental mode, reports will be generated by
|
||||
days in a separate directory, --outdir must be set.
|
||||
-j | --jobs number : number of jobs to run at same time. Run as single
|
||||
by default or when working with csvlog.
|
||||
-J | --Jobs number : number of log file to parse in parallel. Process one
|
||||
file at a time by default or when csvlog is used.
|
||||
-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.
|
||||
-L | --logfile-list file:file containing a list of log file to parse.
|
||||
-m | --maxlength size : maximum length of a query, it will be restricted to
|
||||
the given size. Default: no truncate
|
||||
-M | --no-multiline : do not collect multiline statement to avoid garbage
|
||||
especially on errors that generate a huge report.
|
||||
-n | --nohighlight : disable SQL code highlighting.
|
||||
-N | --appname name : only report on entries for given application name
|
||||
-o | --outfile filename: define the filename for the output. Default depends
|
||||
on the output format: out.html, out.txt, out.bin,
|
||||
out.json or out.tsung. This option can be used
|
||||
multiple time to output several format. To use json
|
||||
output the Perl module JSON::XS must be installed,
|
||||
To dump output to stdout use - as filename.
|
||||
-O | --outdir path : directory where out file must be saved.
|
||||
-p | --prefix string : the value of your custom log_line_prefix as
|
||||
defined in your openGauss.conf. Only use it if you
|
||||
aren't using one of the standard prefixes specified
|
||||
in the gsbadger documentation, such as if your
|
||||
prefix includes additional variables like client ip
|
||||
or application name. See examples below.
|
||||
-P | --no-prettify : disable SQL queries prettify formatter.
|
||||
-q | --quiet : don't print anything to stdout, not even a progress
|
||||
bar.
|
||||
-r | --remote-host ip : set the host where to execute the cat command on
|
||||
remote logfile to parse locally the file.
|
||||
-R | --retention N : number of weeks to keep in incremental mode. Default
|
||||
to 0, disabled. Used to set the number of weeks to
|
||||
keep in output directory. Older weeks and days
|
||||
directory are automatically removed.
|
||||
-s | --sample number : number of query samples to store. Default: 3.
|
||||
-S | --select-only : only report SELECT queries.
|
||||
-t | --top number : number of queries to store/display. Default: 20.
|
||||
-T | --title string : change title of the HTML page report.
|
||||
-u | --dbuser username : only report on entries for the given user.
|
||||
-U | --exclude-user username : exclude entries for the specified user from
|
||||
report. Can be used multiple time.
|
||||
-v | --verbose : enable verbose or debug mode. Disabled by default.
|
||||
-V | --version : show gsbadger version and exit.
|
||||
-w | --watch-mode : only report errors just like logwatch could do.
|
||||
-x | --extension : output format. Values: text, html, bin, json or
|
||||
tsung. Default: html
|
||||
-X | --extra-files : in incremental mode allow gsbadger to write CSS and
|
||||
JS files in the output directory as separate files.
|
||||
-z | --zcat exec_path : set the full path to the zcat program. Use it if
|
||||
zcat or bzcat or unzip is not in your path.
|
||||
-Z | --timezone +/-XX : Set the number of hours from GMT of the timezone.
|
||||
Use this to adjust date/time in JavaScript graphs.
|
||||
--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 times.
|
||||
--exclude-file filename: path of the file which contains all the regex to
|
||||
use to exclude queries from the report. One regex
|
||||
per line.
|
||||
--include-query regex : any query that does not match the given regex will
|
||||
be excluded from the report. You can use this
|
||||
option multiple times. For example: "(tbl1|tbl2)".
|
||||
--include-file filename: path of the file which contains all the regex of
|
||||
the queries to include from the report. One regex
|
||||
per line.
|
||||
--disable-error : do not generate error report.
|
||||
--disable-hourly : do not generate hourly report.
|
||||
--disable-type : do not generate report of queries by type, database
|
||||
or user.
|
||||
--disable-query : do not generate query reports (slowest, most
|
||||
frequent, queries by users, by database, ...).
|
||||
--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/restartpoint report.
|
||||
--disable-autovacuum : do not generate autovacuum report.
|
||||
--charset : used to set the HTML charset to be used.
|
||||
Default: utf-8.
|
||||
--csv-separator : used to set the CSV field separator, default: ,
|
||||
--exclude-time regex : any timestamp matching the given regex will be
|
||||
excluded from the report. Example: "2013-04-12 .*"
|
||||
You can use this option multiple times.
|
||||
--include-time regex : only timestamps matching the given regex will be
|
||||
included in the report. Example: "2013-04-12 .*"
|
||||
You can use this option multiple times.
|
||||
--exclude-db name : exclude entries for the specified database from
|
||||
report. Example: "gs_dump". Can be used multiple
|
||||
time.
|
||||
--exclude-appname name : exclude entries for the specified application name
|
||||
from report. Example: "gs_dump". Can be used
|
||||
multiple time.
|
||||
--exclude-line regex : gsbadger will start to exclude any log entry that
|
||||
will match the given regex. Can be used multiple
|
||||
time.
|
||||
--exclude-client name : exclude log entries for the specified client ip.
|
||||
Can be used multiple time.
|
||||
--anonymize : obscure all literals in queries, useful to hide
|
||||
confidential data.
|
||||
--noreport : prevent gsbadger to create reports in incremental
|
||||
mode.
|
||||
--log-duration : force gsbadger to associate log entries generated
|
||||
by both log_duration = on and log_statement = 'all'
|
||||
--enable-checksum : used to add a md5 sum under each query report.
|
||||
--journalctl command : command to use to replace openGauss logfile by
|
||||
a call to journalctl. Basically it might be:
|
||||
journalctl -u openGauss-9.5
|
||||
--pid-dir path : set the path where the pid file must be stored.
|
||||
Default /tmp
|
||||
--pid-file file : set the name of the pid file to manage concurrent
|
||||
execution of gsbadger. Default: gsbadger.pid
|
||||
--rebuild : used to rebuild all html reports in incremental
|
||||
output directories where there's binary data files.
|
||||
--pgbouncer-only : only show PgBouncer related menu in the header.
|
||||
--start-monday : in incremental mode, calendar's weeks start on
|
||||
sunday. Use this option to start on monday.
|
||||
--normalized-only : only dump all normalized query to out.txt
|
||||
--log-timezone +/-XX : Set the number of hours from GMT of the timezone
|
||||
that must be used to adjust date/time read from
|
||||
log file before beeing parsed. Using this option
|
||||
make more difficult log search with a date/time.
|
||||
--prettify-json : use it if you want json output to be prettified.
|
||||
--month-report YYYY-MM : create a cumulative HTML report over the specified
|
||||
month. Requires incremental output directories and
|
||||
the presence of all necessary binary data files
|
||||
|
||||
gsbadger is able to parse a remote log file using a passwordless ssh connection.
|
||||
Use the -r or --remote-host to set the host ip address or hostname. There's also
|
||||
some additional options to fully control the ssh connection.
|
||||
|
||||
--ssh-program ssh path to the ssh program to use. Default: ssh.
|
||||
--ssh-user username connection login name. Default to running user.
|
||||
--ssh-identity file path to the identity file to use.
|
||||
--ssh-timeout second timeout to ssh connection failure. Default 10 secs.
|
||||
--ssh-option options list of -o options to use for the ssh connection.
|
||||
Options always used:
|
||||
-o ConnectTimeout=$ssh_timeout
|
||||
-o PreferredAuthentications=hostbased,publickey
|
||||
|
||||
Log file to parse can also be specified using an URI, supported protocol are
|
||||
http\[s\] and \[s\]ftp. The curl command will be used to download the file and the
|
||||
file will be parsed during download. The ssh protocol is also supported and will
|
||||
use the ssh command like with the remote host use. See examples bellow.
|
||||
|
||||
Examples:
|
||||
|
||||
gsbadger /var/log/openGauss.log
|
||||
gsbadger /var/log/postgres.log.2.gz /var/log/postgres.log.1.gz /var/log/postgres.log
|
||||
gsbadger /var/log/openGauss/openGauss-2012-05-*
|
||||
gsbadger --exclude-query="^(COPY|COMMIT)" /var/log/openGauss.log
|
||||
gsbadger -b "2012-06-25 10:56:11" -e "2012-06-25 10:59:11" /var/log/openGauss.log
|
||||
cat /var/log/postgres.log | gsbadger -
|
||||
# Log prefix with stderr log output
|
||||
gsbadger --prefix '%t [%p]: user=%u,db=%d,client=%h' /pglog/openGauss-2012-08-21*
|
||||
gsbadger --prefix '%m %u@%d %p %r %a : ' /pglog/openGauss.log
|
||||
# Log line prefix with syslog log output
|
||||
gsbadger --prefix 'user=%u,db=%d,client=%h,appname=%a' /pglog/openGauss-2012-08-21*
|
||||
# Use my 8 CPUs to parse my 10GB file faster, much faster
|
||||
gsbadger -j 8 /pglog/openGauss-10.1-main.log
|
||||
|
||||
Use URI notation for remote log file:
|
||||
|
||||
gsbadger http://172.12.110.1//var/log/openGauss/openGauss-10.1-main.log
|
||||
gsbadger ftp://username@172.12.110.14/openGauss-10.1-main.log
|
||||
gsbadger ssh://username@172.12.110.14//var/log/openGauss/openGauss-10.1-main.log*
|
||||
|
||||
You can use together a local openGauss log and a remote pgbouncer log file to parse:
|
||||
|
||||
gsbadger /var/log/openGauss/openGauss-10.1-main.log ssh://username@172.12.110.14/pgbouncer.log
|
||||
|
||||
Generate Tsung sessions XML file with select queries only:
|
||||
|
||||
gsbadger -S -o sessions.tsung --prefix '%t [%p]: user=%u,db=%d ' /pglog/openGauss-10.1.log
|
||||
|
||||
Reporting errors every week by cron job:
|
||||
|
||||
30 23 * * 1 /usr/bin/gsbadger -q -w /var/log/openGauss.log -o /var/reports/gs_errors.html
|
||||
|
||||
Generate report every week using incremental behavior:
|
||||
|
||||
0 4 * * 1 /usr/bin/gsbadger -q `find /var/log/ -mtime -7 -name "openGauss.log*"` -o /var/reports/gs_errors-`date +\%F`.html -l /var/reports/gsbadger_incremental_file.dat
|
||||
|
||||
This supposes that your log file and HTML report are also rotated every week.
|
||||
|
||||
Or better, use the auto-generated incremental reports:
|
||||
|
||||
0 4 * * * /usr/bin/gsbadger -I -q /var/log/openGauss/openGauss.log.1 -O /var/www/gs_reports/
|
||||
|
||||
will generate a report per day and per week.
|
||||
|
||||
In incremental mode, you can also specify the number of week to keep in the
|
||||
reports:
|
||||
|
||||
/usr/bin/gsbadger --retention 2 -I -q /var/log/openGauss/openGauss.log.1 -O /var/www/gs_reports/
|
||||
|
||||
If you have a pg\_dump at 23:00 and 13:00 each day during half an hour, you can
|
||||
use gsbadger as follow to exclude these period from the report:
|
||||
|
||||
gsbadger --exclude-time "2013-09-.* (23|13):.*" openGauss.log
|
||||
|
||||
This will help avoid having COPY statements, as generated by pg\_dump, on top of
|
||||
the list of slowest queries. You can also use --exclude-appname "pg\_dump" to
|
||||
solve this problem in a simpler way.
|
||||
|
||||
You can also parse journalctl output just as if it was a log file:
|
||||
|
||||
gsbadger --journalctl 'journalctl -u openGauss-9.5'
|
||||
|
||||
or worst, call it from a remote host:
|
||||
|
||||
gsbadger -r 192.168.1.159 --journalctl 'journalctl -u openGauss-9.5'
|
||||
|
||||
you don't need to specify any log file at command line, but if you have other
|
||||
openGauss log file to parse, you can add them as usual.
|
||||
|
||||
To rebuild all incremental html reports after, proceed as follow:
|
||||
|
||||
rm /path/to/reports/*.js
|
||||
rm /path/to/reports/*.css
|
||||
gsbadger -X -I -O /path/to/reports/ --rebuild
|
||||
|
||||
it will also update all resource files (JS and CSS). Use -E or --explode
|
||||
if the reports were built using this option.
|
||||
|
||||
gsbadger also support Heroku openGauss logs using logplex format:
|
||||
|
||||
heroku logs -p postgres | gsbadger -f logplex -o heroku.html -
|
||||
|
||||
this will stream Heroku openGauss log to gsbadger through stdin.
|
||||
|
||||
gsbadger can auto detect RDS and cloudwatch openGauss logs using
|
||||
rds format:
|
||||
|
||||
gsbadger -f rds -o rds_out.html rds.log
|
||||
|
||||
To create a cumulative report over a month use command:
|
||||
|
||||
gsbadger --month-report 2919-05 /path/to/incremantal/reports/
|
||||
|
||||
this will add a link to the month name into the calendar view in
|
||||
incremental reports to look at report for month 2019 May.
|
||||
Use -E or --explode if the reports were built using this option.
|
||||
|
||||
### DESCRIPTION
|
||||
|
||||
gsbadger is a openGauss log analyzer built for speed with fully reports
|
||||
from your openGauss log file. It's a single and small Perl script that
|
||||
outperforms any other openGauss log analyzer.
|
||||
|
||||
It is written in pure Perl and uses a JavaScript library (flotr2) to draw
|
||||
graphs so that you don't need to install any additional Perl modules or
|
||||
other packages. Furthermore, this library gives us more features such
|
||||
as zooming. gsbadger also uses the Bootstrap JavaScript library and
|
||||
the FontAwesome webfont for better design. Everything is embedded.
|
||||
|
||||
gsbadger is able to autodetect your log file format (syslog, stderr, csvlog
|
||||
or jsonlog) if the file is long enough. It is designed to parse huge log
|
||||
files as well as compressed files. Supported compressed format are gzip,
|
||||
bzip2 and xz. For the xz format you must have an xz version upper than 5.05
|
||||
that supports the --robot option. For the complete list of features see below.
|
||||
|
||||
All charts are zoomable and can be saved as PNG images.
|
||||
|
||||
You can also limit gsbadger to only report errors or remove any part of the
|
||||
report using command line options.
|
||||
|
||||
gsbadger supports any custom format set into the log\_line\_prefix directive of
|
||||
your openGauss.conf file as long as it at least specify the %t and %p patterns.
|
||||
|
||||
gsbadger allows parallel processing of a single log file or multiple
|
||||
files through the use of the -j option specifying the number of CPUs.
|
||||
|
||||
If you want to save system performance you can also use log\_duration instead of
|
||||
log\_min\_duration\_statement to have reports on duration and number of queries only.
|
||||
|
||||
### FEATURE
|
||||
|
||||
gsbadger reports everything about your SQL queries:
|
||||
|
||||
Overall statistics.
|
||||
The most frequent waiting queries.
|
||||
Queries that waited the most.
|
||||
Queries generating the most temporary files.
|
||||
Queries generating the largest temporary files.
|
||||
The slowest queries.
|
||||
Queries that took up the most time.
|
||||
The most frequent queries.
|
||||
The most frequent errors.
|
||||
Histogram of query times.
|
||||
Histogram of sessions times.
|
||||
Users involved in top queries.
|
||||
Applications involved in top queries.
|
||||
Queries generating the most cancellation.
|
||||
Queries most cancelled.
|
||||
The most time consuming prepare/bind queries
|
||||
|
||||
The following reports are also available with hourly charts divided into
|
||||
periods of five minutes:
|
||||
|
||||
SQL queries statistics.
|
||||
Temporary file statistics.
|
||||
Checkpoints statistics.
|
||||
Autovacuum and autoanalyze statistics.
|
||||
Cancelled queries.
|
||||
Error events (panic, fatal, error and warning).
|
||||
Error class distribution.
|
||||
|
||||
There are also some pie charts about distribution of:
|
||||
|
||||
Locks statistics.
|
||||
Queries by type (select/insert/update/delete).
|
||||
Distribution of queries type per database/application
|
||||
Sessions per database/user/client/application.
|
||||
Connections per database/user/client/application.
|
||||
Autovacuum and autoanalyze per table.
|
||||
Queries per user and total duration per user.
|
||||
|
||||
All charts are zoomable and can be saved as PNG images. SQL queries reported are
|
||||
highlighted and beautified automatically.
|
||||
|
||||
gsbadger is also able to parse PgBouncer log files and to create the following
|
||||
reports:
|
||||
|
||||
Request Throughput
|
||||
Bytes I/O Throughput
|
||||
Queries Average duration
|
||||
Simultaneous sessions
|
||||
Histogram of sessions times
|
||||
Sessions per database
|
||||
Sessions per user
|
||||
Sessions per host
|
||||
Established connections
|
||||
Connections per database
|
||||
Connections per user
|
||||
Connections per host
|
||||
Most used reserved pools
|
||||
Most Frequent Errors/Events
|
||||
|
||||
You can also have incremental reports with one report per day and a cumulative
|
||||
report per week. Two multiprocess modes are available to speed up log parsing,
|
||||
one using one core per log file, and the second using multiple cores to parse
|
||||
a single file. These modes can be combined.
|
||||
|
||||
Histogram granularity can be adjusted using the -A command line option. By default
|
||||
they will report the mean of each top queries/errors occurring per hour, but you can
|
||||
specify the granularity down to the minute.
|
||||
|
||||
gsbadger can also be used in a central place to parse remote log files using a
|
||||
passwordless SSH connection. This mode can be used with compressed files and in
|
||||
the multiprocess per file mode (-J) but can not be used with the CSV log format.
|
||||
|
||||
### REQUIREMENT
|
||||
|
||||
gsbadger comes as a single Perl script - you do not need anything other than a modern
|
||||
Perl distribution. Charts are rendered using a JavaScript library so you don't need
|
||||
anything other than a web browser. Your browser will do all the work.
|
||||
|
||||
If you planned to parse openGauss CSV log files you might need some Perl Modules:
|
||||
|
||||
Text::CSV_XS - to parse openGauss CSV log files.
|
||||
|
||||
This module is optional, if you don't have openGauss log in the CSV format you don't
|
||||
need to install it.
|
||||
|
||||
If you want to export statistics as JSON file you need an additional Perl module:
|
||||
|
||||
JSON::XS - JSON serialising/deserialising, done correctly and fast
|
||||
|
||||
This module is optional, if you don't select the json output format you don't
|
||||
need to install it. You can install it on a Debian like system using:
|
||||
|
||||
sudo apt-get install libjson-xs-perl
|
||||
|
||||
and in RPM like system using:
|
||||
|
||||
sudo yum install perl-JSON-XS
|
||||
|
||||
Compressed log file format is autodetected from the file extension. If gsbadger find
|
||||
a gz extension it will use the zcat utility, with a bz2 extension it will use bzcat
|
||||
and if the file extension is zip or xz then the unzip or xz utilities will be used.
|
||||
|
||||
If those utilities are not found in the PATH environment variable then use the --zcat
|
||||
command line option to change this path. For example:
|
||||
|
||||
--zcat="/usr/local/bin/gunzip -c" or --zcat="/usr/local/bin/bzip2 -dc"
|
||||
--zcat="C:\tools\unzip -p"
|
||||
|
||||
By default gsbadger will use the zcat, bzcat and unzip utilities following the
|
||||
file extension. If you use the default autodetection compress format you can
|
||||
mixed gz, bz2, xz or zip files. Specifying a custom value to --zcat option will
|
||||
remove this feature of mixed compressed format.
|
||||
|
||||
Note that multiprocessing can not be used with compressed files or CSV files as
|
||||
well as under Windows platform.
|
||||
|
||||
### INSTALLATION
|
||||
|
||||
Download the tarball from GitHub and unpack the archive as follow:
|
||||
|
||||
tar xzf gsbadger-11.x.tar.gz
|
||||
cd gsbadger-11.x/
|
||||
perl Makefile.PL
|
||||
make && sudo make install
|
||||
|
||||
This will copy the Perl script gsbadger to /usr/local/bin/gsbadger by default and the
|
||||
man page into /usr/local/share/man/man1/gsbadger.1. Those are the default installation
|
||||
directories for 'site' install.
|
||||
|
||||
If you want to install all under /usr/ location, use INSTALLDIRS='perl' as an argument
|
||||
of Makefile.PL. The script will be installed into /usr/bin/gsbadger and the manpage
|
||||
into /usr/share/man/man1/gsbadger.1.
|
||||
|
||||
For example, to install everything just like Debian does, proceed as follows:
|
||||
|
||||
perl Makefile.PL INSTALLDIRS=vendor
|
||||
|
||||
By default INSTALLDIRS is set to site.
|
||||
|
||||
### openGauss CONFIGURATION
|
||||
|
||||
You must enable and set some configuration directives in your openGauss.conf
|
||||
before starting.
|
||||
|
||||
You must first enable SQL query logging to have something to parse:
|
||||
|
||||
log_min_duration_statement = 0
|
||||
|
||||
Here every statement will be logged, on a busy server you may want to increase
|
||||
this value to only log queries with a longer duration. Note that if you have
|
||||
log\_statement set to 'all' nothing will be logged through the log\_min\_duration\_statement
|
||||
directive. See the next chapter for more information.
|
||||
|
||||
gsbadger supports any custom format set into the log\_line\_prefix directive of
|
||||
your openGauss.conf file as long as it at least specify a time escape sequence
|
||||
(%t, %m or %n) and the process related escape sequence (%p or %c).
|
||||
|
||||
For example, with 'stderr' log format, log\_line\_prefix must be at least:
|
||||
|
||||
log_line_prefix = '%t [%p]: '
|
||||
|
||||
Log line prefix could add user, database name, application name and client ip
|
||||
address as follows:
|
||||
|
||||
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '
|
||||
|
||||
or for syslog log file format:
|
||||
|
||||
log_line_prefix = 'user=%u,db=%d,app=%a,client=%h '
|
||||
|
||||
Log line prefix for stderr output could also be:
|
||||
|
||||
log_line_prefix = '%t [%p]: db=%d,user=%u,app=%a,client=%h '
|
||||
|
||||
or for syslog output:
|
||||
|
||||
log_line_prefix = 'db=%d,user=%u,app=%a,client=%h '
|
||||
|
||||
You need to enable other parameters in openGauss.conf to get more information from your log files:
|
||||
|
||||
log_checkpoints = on
|
||||
log_connections = on
|
||||
log_disconnections = on
|
||||
log_lock_waits = on
|
||||
log_temp_files = 0
|
||||
log_autovacuum_min_duration = 0
|
||||
log_error_verbosity = default
|
||||
|
||||
Do not enable log\_statement as its log format will not be parsed by gsbadger.
|
||||
|
||||
Of course your log messages should be in English without locale support:
|
||||
|
||||
lc_messages='C'
|
||||
|
||||
but this is not only recommended by gsbadger.
|
||||
|
||||
### LOG STATEMENTS
|
||||
|
||||
Considerations about log\_min\_duration\_statement, log\_duration and log\_statement
|
||||
configuration directives.
|
||||
|
||||
If you want the query statistics to include the actual query strings, you
|
||||
must set log\_min\_duration\_statement to 0 or more milliseconds.
|
||||
|
||||
If you just want to report duration and number of queries and don't want all
|
||||
details about queries, set log\_min\_duration\_statement to -1 to disable it and
|
||||
enable log\_duration in your openGauss.conf file. If you want to add the most
|
||||
common request report you can either choose to set log\_min\_duration\_statement
|
||||
to a higher value or choose to enable log\_statement.
|
||||
|
||||
Enabling log\_min\_duration\_statement will add reports about slowest queries and
|
||||
queries that took up the most time. Take care that if you have log\_statement
|
||||
set to 'all' nothing will be logged with log\_min\_duration\_statement.
|
||||
|
||||
Warning: Do not enable both log\_min\_duration\_statement, log\_duration and
|
||||
log\_statement all together, this will result in wrong counter values. Note
|
||||
that this will also increase drastically the size of your log.
|
||||
log\_min\_duration\_statement should always be preferred.
|
||||
|
||||
### PARALLEL PROCESSING
|
||||
|
||||
To enable parallel processing you just have to use the -j N option where N is
|
||||
the number of cores you want to use.
|
||||
|
||||
gsbadger will then proceed as follow:
|
||||
|
||||
for each log file
|
||||
chunk size = int(file size / N)
|
||||
look at start/end offsets of these chunks
|
||||
fork N processes and seek to the start offset of each chunk
|
||||
each process will terminate when the parser reach the end offset
|
||||
of its chunk
|
||||
each process write stats into a binary temporary file
|
||||
wait for all children has terminated
|
||||
All binary temporary files generated will then be read and loaded into
|
||||
memory to build the html output.
|
||||
|
||||
With that method, at start/end of chunks gsbadger may truncate or omit a
|
||||
maximum of N queries per log file which is an insignificant gap if you have
|
||||
millions of queries in your log file. The chance that the query that you were
|
||||
looking for is lost is near 0, this is why I think this gap is livable. Most
|
||||
of the time the query is counted twice but truncated.
|
||||
|
||||
When you have many small log files and many CPUs it is speedier to dedicate
|
||||
one core to one log file at a time. To enable this behavior you have to use
|
||||
option -J N instead. With 200 log files of 10MB each the use of the -J option
|
||||
starts being really interesting with 8 Cores. Using this method you will be
|
||||
sure not to lose any queries in the reports.
|
||||
|
||||
He are a benchmark done on a server with 8 CPUs and a single file of 9.5GB.
|
||||
|
||||
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
|
||||
--------+---------+-------+-------+------
|
||||
-j | 1h41m18 | 50m25 | 25m39 | 15m58
|
||||
-J | 1h41m18 | 54m28 | 41m16 | 34m45
|
||||
|
||||
With 200 log files of 10MB each and a total of 2GB the results are slightly
|
||||
different:
|
||||
|
||||
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
|
||||
--------+-------+-------+-------+------
|
||||
-j | 20m15 | 9m56 | 5m20 | 4m20
|
||||
-J | 20m15 | 9m49 | 5m00 | 2m40
|
||||
|
||||
So it is recommended to use -j unless you have hundreds of small log files
|
||||
and can use at least 8 CPUs.
|
||||
|
||||
IMPORTANT: when you are using parallel parsing gsbadger will generate a
|
||||
lot of temporary files in the /tmp directory and will remove them at the
|
||||
end, so do not remove those files unless gsbadger is not running. They are
|
||||
all named with the following template tmp\_gsbadgerXXXX.bin so they can be
|
||||
easily identified.
|
||||
|
||||
### INCREMENTAL REPORTS
|
||||
|
||||
gsbadger includes an automatic incremental report mode using option -I or
|
||||
\--incremental. When running in this mode, gsbadger will generate one report
|
||||
per day and a cumulative report per week. Output is first done in binary
|
||||
format into the mandatory output directory (see option -O or --outdir),
|
||||
then in HTML format for daily and weekly reports with a main index file.
|
||||
|
||||
The main index file will show a dropdown menu per week with a link to each
|
||||
week report and links to daily reports of each week.
|
||||
|
||||
For example, if you run gsbadger as follows based on a daily rotated file:
|
||||
|
||||
0 4 * * * /usr/bin/gsbadger -I -q /var/log/openGauss/openGauss.log.1 -O /var/www/gs_reports/
|
||||
|
||||
you will have all daily and weekly reports for the full running period.
|
||||
|
||||
In this mode gsbadger will create an automatic incremental file in the
|
||||
output directory, so you don't have to use the -l option unless you want
|
||||
to change the path of that file. This means that you can run gsbadger in
|
||||
this mode each day on a log file rotated each week, and it will not count
|
||||
the log entries twice.
|
||||
|
||||
To save disk space you may want to use the -X or --extra-files command line
|
||||
option to force gsbadger to write JavaScript and CSS to separate files in
|
||||
the output directory. The resources will then be loaded using script and
|
||||
link tags.
|
||||
|
||||
#### Rebuilding reports
|
||||
|
||||
Incremental reports can be rebuilt after a gsbadger report fix or a new
|
||||
feature to update all HTML reports. To rebuild all reports where a binary
|
||||
file is still present proceed as follow:
|
||||
|
||||
rm /path/to/reports/*.js
|
||||
rm /path/to/reports/*.css
|
||||
gsbadger -X -I -O /path/to/reports/ --rebuild
|
||||
|
||||
it will also update all resource files (JS and CSS). Use -E or --explode
|
||||
if the reports were built using this option.
|
||||
|
||||
#### Monthly reports
|
||||
|
||||
By default gsbadger in incremental mode only compute daily and weekly reports.
|
||||
If you want monthly cumulative reports you will have to use a separate command
|
||||
to specify the report to build. For example to build a report for August 2019:
|
||||
|
||||
gsbadger -X --month-report 2919-08 /var/www/gs_reports/
|
||||
|
||||
this will add a link to the month name into the calendar view of incremental
|
||||
reports to look at monthly report. The report for a current month can be run
|
||||
every day it is entirely rebuilt each time. The monthly report is not built by
|
||||
default because it could take lot of time following the amount of data.
|
||||
|
||||
If reports were built with the per database option ( -E | --explode ) it must
|
||||
be used too when calling gsbadger to build monthly report:
|
||||
|
||||
gsbadger -E -X --month-report 2919-08 /var/www/gs_reports/
|
||||
|
||||
This is the same when using the rebuild option ( -R | --rebuild ).
|
||||
|
||||
### BINARY FORMAT
|
||||
|
||||
Using the binary format it is possible to create custom incremental and
|
||||
cumulative reports. For example, if you want to refresh a gsbadger
|
||||
report each hour from a daily openGauss log file, you can proceed by
|
||||
running each hour the following commands:
|
||||
|
||||
gsbadger --last-parsed .gsbadger_last_state_file -o sunday/hourX.bin /var/log/pgsql/openGauss-Sun.log
|
||||
|
||||
to generate the incremental data files in binary format. And to generate the fresh HTML
|
||||
report from that binary file:
|
||||
|
||||
gsbadger sunday/*.bin
|
||||
|
||||
Or as another example, if you generate one log file per hour and you want
|
||||
reports to be rebuilt each time the log file is rotated, proceed as
|
||||
follows:
|
||||
|
||||
gsbadger -o day1/hour01.bin /var/log/pgsql/pglog/openGauss-2012-03-23_10.log
|
||||
gsbadger -o day1/hour02.bin /var/log/pgsql/pglog/openGauss-2012-03-23_11.log
|
||||
gsbadger -o day1/hour03.bin /var/log/pgsql/pglog/openGauss-2012-03-23_12.log
|
||||
...
|
||||
|
||||
When you want to refresh the HTML report, for example each time after a new binary file
|
||||
is generated, just do the following:
|
||||
|
||||
gsbadger -o day1_report.html day1/*.bin
|
||||
|
||||
Adjust the commands to suit your particular needs.
|
||||
|
||||
### JSON FORMAT
|
||||
|
||||
JSON format is good for sharing data with other languages, which makes it
|
||||
easy to integrate gsbadger result into other monitoring tools like Cacti
|
||||
or Graphite.
|
||||
|
||||
### AUTHORS
|
||||
|
||||
gsbadger is an original work from Gilles Darold.
|
||||
|
||||
The gsbadger logo is an original creation of Damien Cazeils.
|
||||
|
||||
The gsbadger v4.x design comes from the "Art is code" company.
|
||||
|
||||
This web site is a work of Gilles Darold.
|
||||
|
||||
gsbadger is maintained by Gilles Darold and every one who wants to contribute.
|
||||
|
||||
Many people have contributed to gsbadger, they are all quoted in the Changelog file.
|
||||
|
||||
### LICENSE
|
||||
|
||||
gsbadger is free software distributed under the openGauss Licence.
|
||||
|
||||
Copyright (c) 2012-2019, Gilles Darold
|
||||
|
||||
A modified version of the SQL::Beautify Perl Module is embedded in gsbadger
|
||||
with copyright (C) 2009 by Jonas Kramer and is published under the terms of
|
||||
the Artistic License 2.0.
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,942 @@
|
|||
.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
|
||||
.\"
|
||||
.\" Standard preamble:
|
||||
.\" ========================================================================
|
||||
.de Sp \" Vertical space (when we can't use .PP)
|
||||
.if t .sp .5v
|
||||
.if n .sp
|
||||
..
|
||||
.de Vb \" Begin verbatim text
|
||||
.ft CW
|
||||
.nf
|
||||
.ne \\$1
|
||||
..
|
||||
.de Ve \" End verbatim text
|
||||
.ft R
|
||||
.fi
|
||||
..
|
||||
.\" Set up some character translations and predefined strings. \*(-- will
|
||||
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
|
||||
.\" double quote, and \*(R" will give a right double quote. \*(C+ will
|
||||
.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
|
||||
.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
|
||||
.\" nothing in troff, for use with C<>.
|
||||
.tr \(*W-
|
||||
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
|
||||
.ie n \{\
|
||||
. ds -- \(*W-
|
||||
. ds PI pi
|
||||
. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
|
||||
. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
|
||||
. ds L" ""
|
||||
. ds R" ""
|
||||
. ds C` ""
|
||||
. ds C' ""
|
||||
'br\}
|
||||
.el\{\
|
||||
. ds -- \|\(em\|
|
||||
. ds PI \(*p
|
||||
. ds L" ``
|
||||
. ds R" ''
|
||||
. ds C`
|
||||
. ds C'
|
||||
'br\}
|
||||
.\"
|
||||
.\" Escape single quotes in literal strings from groff's Unicode transform.
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\"
|
||||
.\" If the F register is turned on, we'll generate index entries on stderr for
|
||||
.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
|
||||
.\" entries marked with X<> in POD. Of course, you'll have to process the
|
||||
.\" output yourself in some meaningful fashion.
|
||||
.\"
|
||||
.\" Avoid warning from groff about undefined register 'F'.
|
||||
.de IX
|
||||
..
|
||||
.nr rF 0
|
||||
.if \n(.g .if rF .nr rF 1
|
||||
.if (\n(rF:(\n(.g==0)) \{
|
||||
. if \nF \{
|
||||
. de IX
|
||||
. tm Index:\\$1\t\\n%\t"\\$2"
|
||||
..
|
||||
. if !\nF==2 \{
|
||||
. nr % 0
|
||||
. nr F 2
|
||||
. \}
|
||||
. \}
|
||||
.\}
|
||||
.rr rF
|
||||
.\"
|
||||
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
|
||||
.\" Fear. Run. Save yourself. No user-serviceable parts.
|
||||
. \" fudge factors for nroff and troff
|
||||
.if n \{\
|
||||
. ds #H 0
|
||||
. ds #V .8m
|
||||
. ds #F .3m
|
||||
. ds #[ \f1
|
||||
. ds #] \fP
|
||||
.\}
|
||||
.if t \{\
|
||||
. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
|
||||
. ds #V .6m
|
||||
. ds #F 0
|
||||
. ds #[ \&
|
||||
. ds #] \&
|
||||
.\}
|
||||
. \" simple accents for nroff and troff
|
||||
.if n \{\
|
||||
. ds ' \&
|
||||
. ds ` \&
|
||||
. ds ^ \&
|
||||
. ds , \&
|
||||
. ds ~ ~
|
||||
. ds /
|
||||
.\}
|
||||
.if t \{\
|
||||
. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
|
||||
. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
|
||||
. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
|
||||
. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
|
||||
. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
|
||||
. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
|
||||
.\}
|
||||
. \" troff and (daisy-wheel) nroff accents
|
||||
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
|
||||
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
|
||||
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
|
||||
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
|
||||
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
|
||||
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
|
||||
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
|
||||
.ds ae a\h'-(\w'a'u*4/10)'e
|
||||
.ds Ae A\h'-(\w'A'u*4/10)'E
|
||||
. \" corrections for vroff
|
||||
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
|
||||
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
|
||||
. \" for low resolution devices (crt and lpr)
|
||||
.if \n(.H>23 .if \n(.V>19 \
|
||||
\{\
|
||||
. ds : e
|
||||
. ds 8 ss
|
||||
. ds o a
|
||||
. ds d- d\h'-1'\(ga
|
||||
. ds D- D\h'-1'\(hy
|
||||
. ds th \o'bp'
|
||||
. ds Th \o'LP'
|
||||
. ds ae ae
|
||||
. ds Ae AE
|
||||
.\}
|
||||
.rm #[ #] #H #V #F C
|
||||
.\" ========================================================================
|
||||
.\"
|
||||
.IX Title "PGBADGER 1"
|
||||
.TH PGBADGER 1 "2022-08-08" "perl v5.16.3" "User Contributed Perl Documentation"
|
||||
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
||||
.\" way too many mistakes in technical documents.
|
||||
.if n .ad l
|
||||
.nh
|
||||
.SH "NAME"
|
||||
pgBadger \- a fast PostgreSQL log analysis report
|
||||
.SH "SYNOPSIS"
|
||||
.IX Header "SYNOPSIS"
|
||||
Usage: pgbadger [options] logfile [...]
|
||||
.PP
|
||||
.Vb 1
|
||||
\& PostgreSQL log analyzer with fully detailed reports and graphs.
|
||||
.Ve
|
||||
.PP
|
||||
Arguments:
|
||||
.PP
|
||||
.Vb 3
|
||||
\& logfile can be a single log file, a list of files, or a shell command
|
||||
\& returning a list of files. If you want to pass log content from stdin
|
||||
\& use \- as filename. Note that input from stdin will not work with csvlog.
|
||||
.Ve
|
||||
.PP
|
||||
Options:
|
||||
.PP
|
||||
.Vb 10
|
||||
\& \-a | \-\-average minutes : number of minutes to build the average graphs of
|
||||
\& queries and connections. Default 5 minutes.
|
||||
\& \-A | \-\-histo\-average min: number of minutes to build the histogram graphs
|
||||
\& of queries. Default 60 minutes.
|
||||
\& \-b | \-\-begin datetime : start date/time for the data to be parsed in log
|
||||
\& (either a timestamp or a time)
|
||||
\& \-c | \-\-dbclient host : only report on entries for the given client host.
|
||||
\& \-C | \-\-nocomment : remove comments like /* ... */ from queries.
|
||||
\& \-d | \-\-dbname database : only report on entries for the given database.
|
||||
\& \-D | \-\-dns\-resolv : client ip addresses are replaced by their DNS name.
|
||||
\& Be warned that this can really slow down pgBadger.
|
||||
\& \-e | \-\-end datetime : end date/time for the data to be parsed in log
|
||||
\& (either a timestamp or a time)
|
||||
\& \-E | \-\-explode : explode the main report by generating one report
|
||||
\& per database. Global information not related to a
|
||||
\& database are added to the postgres database report.
|
||||
\& \-f | \-\-format logtype : possible values: syslog, syslog2, stderr, jsonlog,
|
||||
\& cvs, pgbouncer, logplex and rds. Use this option
|
||||
\& when pgBadger is not able to detect the log format.
|
||||
\& \-G | \-\-nograph : disable graphs on HTML output. Enabled by default.
|
||||
\& \-h | \-\-help : show this message and exit.
|
||||
\& \-H | \-\-html\-dir path : path to directory where HTML report must be written
|
||||
\& in incremental mode, binary files stay on directory
|
||||
\& defined with \-O, \-\-outdir option.
|
||||
\& \-i | \-\-ident name : programname used as syslog ident. Default: postgres
|
||||
\& \-I | \-\-incremental : use incremental mode, reports will be generated by
|
||||
\& days in a separate directory, \-\-outdir must be set.
|
||||
\& \-j | \-\-jobs number : number of jobs to run at same time. Run as single
|
||||
\& by default or when working with csvlog.
|
||||
\& \-J | \-\-Jobs number : number of log file to parse in parallel. Process one
|
||||
\& file at a time by default or when csvlog is used.
|
||||
\& \-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.
|
||||
\& \-L | \-\-logfile\-list file:file containing a list of log file to parse.
|
||||
\& \-m | \-\-maxlength size : maximum length of a query, it will be restricted to
|
||||
\& the given size. Default: no truncate
|
||||
\& \-M | \-\-no\-multiline : do not collect multiline statement to avoid garbage
|
||||
\& especially on errors that generate a huge report.
|
||||
\& \-n | \-\-nohighlight : disable SQL code highlighting.
|
||||
\& \-N | \-\-appname name : only report on entries for given application name
|
||||
\& \-o | \-\-outfile filename: define the filename for the output. Default depends
|
||||
\& on the output format: out.html, out.txt, out.bin,
|
||||
\& out.json or out.tsung. This option can be used
|
||||
\& multiple time to output several format. To use json
|
||||
\& output the Perl module JSON::XS must be installed,
|
||||
\& To dump output to stdout use \- as filename.
|
||||
\& \-O | \-\-outdir path : directory where out file must be saved.
|
||||
\& \-p | \-\-prefix string : the value of your custom log_line_prefix as
|
||||
\& defined in your postgresql.conf. Only use it if you
|
||||
\& aren\*(Aqt using one of the standard prefixes specified
|
||||
\& in the pgBadger documentation, such as if your
|
||||
\& prefix includes additional variables like client ip
|
||||
\& or application name. See examples below.
|
||||
\& \-P | \-\-no\-prettify : disable SQL queries prettify formatter.
|
||||
\& \-q | \-\-quiet : don\*(Aqt print anything to stdout, not even a progress
|
||||
\& bar.
|
||||
\& \-r | \-\-remote\-host ip : set the host where to execute the cat command on
|
||||
\& remote logfile to parse locally the file.
|
||||
\& \-R | \-\-retention N : number of weeks to keep in incremental mode. Default
|
||||
\& to 0, disabled. Used to set the number of weeks to
|
||||
\& keep in output directory. Older weeks and days
|
||||
\& directory are automatically removed.
|
||||
\& \-s | \-\-sample number : number of query samples to store. Default: 3.
|
||||
\& \-S | \-\-select\-only : only report SELECT queries.
|
||||
\& \-t | \-\-top number : number of queries to store/display. Default: 20.
|
||||
\& \-T | \-\-title string : change title of the HTML page report.
|
||||
\& \-u | \-\-dbuser username : only report on entries for the given user.
|
||||
\& \-U | \-\-exclude\-user username : exclude entries for the specified user from
|
||||
\& report. Can be used multiple time.
|
||||
\& \-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, html, bin, json or
|
||||
\& tsung. Default: html
|
||||
\& \-X | \-\-extra\-files : in incremental mode allow pgBadger to write CSS and
|
||||
\& JS files in the output directory as separate files.
|
||||
\& \-z | \-\-zcat exec_path : set the full path to the zcat program. Use it if
|
||||
\& zcat or bzcat or unzip is not in your path.
|
||||
\& \-Z | \-\-timezone +/\-XX : Set the number of hours from GMT of the timezone.
|
||||
\& Use this to adjust date/time in JavaScript graphs.
|
||||
\& \-\-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 times.
|
||||
\& \-\-exclude\-file filename: path of the file which contains all the regex to
|
||||
\& use to exclude queries from the report. One regex
|
||||
\& per line.
|
||||
\& \-\-include\-query regex : any query that does not match the given regex will
|
||||
\& be excluded from the report. You can use this
|
||||
\& option multiple times. For example: "(tbl1|tbl2)".
|
||||
\& \-\-include\-file filename: path of the file which contains all the regex of
|
||||
\& the queries to include from the report. One regex
|
||||
\& per line.
|
||||
\& \-\-disable\-error : do not generate error report.
|
||||
\& \-\-disable\-hourly : do not generate hourly report.
|
||||
\& \-\-disable\-type : do not generate report of queries by type, database
|
||||
\& or user.
|
||||
\& \-\-disable\-query : do not generate query reports (slowest, most
|
||||
\& frequent, queries by users, by database, ...).
|
||||
\& \-\-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/restartpoint report.
|
||||
\& \-\-disable\-autovacuum : do not generate autovacuum report.
|
||||
\& \-\-charset : used to set the HTML charset to be used.
|
||||
\& Default: utf\-8.
|
||||
\& \-\-csv\-separator : used to set the CSV field separator, default: ,
|
||||
\& \-\-exclude\-time regex : any timestamp matching the given regex will be
|
||||
\& excluded from the report. Example: "2013\-04\-12 .*"
|
||||
\& You can use this option multiple times.
|
||||
\& \-\-include\-time regex : only timestamps matching the given regex will be
|
||||
\& included in the report. Example: "2013\-04\-12 .*"
|
||||
\& You can use this option multiple times.
|
||||
\& \-\-exclude\-db name : exclude entries for the specified database from
|
||||
\& report. Example: "pg_dump". Can be used multiple
|
||||
\& time.
|
||||
\& \-\-exclude\-appname name : exclude entries for the specified application name
|
||||
\& from report. Example: "pg_dump". Can be used
|
||||
\& multiple time.
|
||||
\& \-\-exclude\-line regex : pgBadger will start to exclude any log entry that
|
||||
\& will match the given regex. Can be used multiple
|
||||
\& time.
|
||||
\& \-\-exclude\-client name : exclude log entries for the specified client ip.
|
||||
\& Can be used multiple time.
|
||||
\& \-\-anonymize : obscure all literals in queries, useful to hide
|
||||
\& confidential data.
|
||||
\& \-\-noreport : prevent pgBadger to create reports in incremental
|
||||
\& mode.
|
||||
\& \-\-log\-duration : force pgBadger to associate log entries generated
|
||||
\& by both log_duration = on and log_statement = \*(Aqall\*(Aq
|
||||
\& \-\-enable\-checksum : used to add a md5 sum under each query report.
|
||||
\& \-\-journalctl command : command to use to replace PostgreSQL logfile by
|
||||
\& a call to journalctl. Basically it might be:
|
||||
\& journalctl \-u postgresql\-9.5
|
||||
\& \-\-pid\-dir path : set the path where the pid file must be stored.
|
||||
\& Default /tmp
|
||||
\& \-\-pid\-file file : set the name of the pid file to manage concurrent
|
||||
\& execution of pgBadger. Default: pgbadger.pid
|
||||
\& \-\-rebuild : used to rebuild all html reports in incremental
|
||||
\& output directories where there\*(Aqs binary data files.
|
||||
\& \-\-pgbouncer\-only : only show PgBouncer related menu in the header.
|
||||
\& \-\-start\-monday : in incremental mode, calendar\*(Aqs weeks start on
|
||||
\& sunday. Use this option to start on monday.
|
||||
\& \-\-normalized\-only : only dump all normalized query to out.txt
|
||||
\& \-\-log\-timezone +/\-XX : Set the number of hours from GMT of the timezone
|
||||
\& that must be used to adjust date/time read from
|
||||
\& log file before beeing parsed. Using this option
|
||||
\& make more difficult log search with a date/time.
|
||||
\& \-\-prettify\-json : use it if you want json output to be prettified.
|
||||
\& \-\-month\-report YYYY\-MM : create a cumulative HTML report over the specified
|
||||
\& month. Requires incremental output directories and
|
||||
\& the presence of all necessary binary data files
|
||||
.Ve
|
||||
.PP
|
||||
pgBadger is able to parse a remote log file using a passwordless ssh connection.
|
||||
Use the \-r or \-\-remote\-host to set the host ip address or hostname. There's also
|
||||
some additional options to fully control the ssh connection.
|
||||
.PP
|
||||
.Vb 8
|
||||
\& \-\-ssh\-program ssh path to the ssh program to use. Default: ssh.
|
||||
\& \-\-ssh\-user username connection login name. Default to running user.
|
||||
\& \-\-ssh\-identity file path to the identity file to use.
|
||||
\& \-\-ssh\-timeout second timeout to ssh connection failure. Default 10 secs.
|
||||
\& \-\-ssh\-option options list of \-o options to use for the ssh connection.
|
||||
\& Options always used:
|
||||
\& \-o ConnectTimeout=$ssh_timeout
|
||||
\& \-o PreferredAuthentications=hostbased,publickey
|
||||
.Ve
|
||||
.PP
|
||||
Log file to parse can also be specified using an \s-1URI,\s0 supported protocol are
|
||||
http[s] and [s]ftp. The curl command will be used to download the file and the
|
||||
file will be parsed during download. The ssh protocol is also supported and will
|
||||
use the ssh command like with the remote host use. See examples bellow.
|
||||
.PP
|
||||
Examples:
|
||||
.PP
|
||||
.Vb 10
|
||||
\& 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
|
||||
\& pgbadger \-b "2012\-06\-25 10:56:11" \-e "2012\-06\-25 10:59:11" /var/log/postgresql.log
|
||||
\& cat /var/log/postgres.log | pgbadger \-
|
||||
\& # Log prefix with stderr log output
|
||||
\& pgbadger \-\-prefix \*(Aq%t [%p]: user=%u,db=%d,client=%h\*(Aq /pglog/postgresql\-2012\-08\-21*
|
||||
\& pgbadger \-\-prefix \*(Aq%m %u@%d %p %r %a : \*(Aq /pglog/postgresql.log
|
||||
\& # Log line prefix with syslog log output
|
||||
\& pgbadger \-\-prefix \*(Aquser=%u,db=%d,client=%h,appname=%a\*(Aq /pglog/postgresql\-2012\-08\-21*
|
||||
\& # Use my 8 CPUs to parse my 10GB file faster, much faster
|
||||
\& pgbadger \-j 8 /pglog/postgresql\-10.1\-main.log
|
||||
.Ve
|
||||
.PP
|
||||
Use \s-1URI\s0 notation for remote log file:
|
||||
.PP
|
||||
.Vb 3
|
||||
\& pgbadger http://172.12.110.1//var/log/postgresql/postgresql\-10.1\-main.log
|
||||
\& pgbadger ftp://username@172.12.110.14/postgresql\-10.1\-main.log
|
||||
\& pgbadger ssh://username@172.12.110.14//var/log/postgresql/postgresql\-10.1\-main.log*
|
||||
.Ve
|
||||
.PP
|
||||
You can use together a local PostgreSQL log and a remote pgbouncer log file to parse:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger /var/log/postgresql/postgresql\-10.1\-main.log ssh://username@172.12.110.14/pgbouncer.log
|
||||
.Ve
|
||||
.PP
|
||||
Generate Tsung sessions \s-1XML\s0 file with select queries only:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-S \-o sessions.tsung \-\-prefix \*(Aq%t [%p]: user=%u,db=%d \*(Aq /pglog/postgresql\-10.1.log
|
||||
.Ve
|
||||
.PP
|
||||
Reporting errors every week by cron job:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& 30 23 * * 1 /usr/bin/pgbadger \-q \-w /var/log/postgresql.log \-o /var/reports/pg_errors.html
|
||||
.Ve
|
||||
.PP
|
||||
Generate report every week using incremental behavior:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& 0 4 * * 1 /usr/bin/pgbadger \-q \`find /var/log/ \-mtime \-7 \-name "postgresql.log*"\` \-o /var/reports/pg_errors\-\`date +\e%F\`.html \-l /var/reports/pgbadger_incremental_file.dat
|
||||
.Ve
|
||||
.PP
|
||||
This supposes that your log file and \s-1HTML\s0 report are also rotated every week.
|
||||
.PP
|
||||
Or better, use the auto-generated incremental reports:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& 0 4 * * * /usr/bin/pgbadger \-I \-q /var/log/postgresql/postgresql.log.1 \-O /var/www/pg_reports/
|
||||
.Ve
|
||||
.PP
|
||||
will generate a report per day and per week.
|
||||
.PP
|
||||
In incremental mode, you can also specify the number of week to keep in the
|
||||
reports:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& /usr/bin/pgbadger \-\-retention 2 \-I \-q /var/log/postgresql/postgresql.log.1 \-O /var/www/pg_reports/
|
||||
.Ve
|
||||
.PP
|
||||
If you have a pg_dump at 23:00 and 13:00 each day during half an hour, you can
|
||||
use pgBadger as follow to exclude these period from the report:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-\-exclude\-time "2013\-09\-.* (23|13):.*" postgresql.log
|
||||
.Ve
|
||||
.PP
|
||||
This will help avoid having \s-1COPY\s0 statements, as generated by pg_dump, on top of
|
||||
the list of slowest queries. You can also use \-\-exclude\-appname \*(L"pg_dump\*(R" to
|
||||
solve this problem in a simpler way.
|
||||
.PP
|
||||
You can also parse journalctl output just as if it was a log file:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-\-journalctl \*(Aqjournalctl \-u postgresql\-9.5\*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
or worst, call it from a remote host:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-r 192.168.1.159 \-\-journalctl \*(Aqjournalctl \-u postgresql\-9.5\*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
you don't need to specify any log file at command line, but if you have other
|
||||
PostgreSQL log file to parse, you can add them as usual.
|
||||
.PP
|
||||
To rebuild all incremental html reports after, proceed as follow:
|
||||
.PP
|
||||
.Vb 3
|
||||
\& rm /path/to/reports/*.js
|
||||
\& rm /path/to/reports/*.css
|
||||
\& pgbadger \-X \-I \-O /path/to/reports/ \-\-rebuild
|
||||
.Ve
|
||||
.PP
|
||||
it will also update all resource files (\s-1JS\s0 and \s-1CSS\s0). Use \-E or \-\-explode
|
||||
if the reports were built using this option.
|
||||
.PP
|
||||
pgBadger also support Heroku PostgreSQL logs using logplex format:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& heroku logs \-p postgres | pgbadger \-f logplex \-o heroku.html \-
|
||||
.Ve
|
||||
.PP
|
||||
this will stream Heroku PostgreSQL log to pgbadger through stdin.
|
||||
.PP
|
||||
pgBadger can auto detect \s-1RDS\s0 and cloudwatch PostgreSQL logs using
|
||||
rds format:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-f rds \-o rds_out.html rds.log
|
||||
.Ve
|
||||
.PP
|
||||
To create a cumulative report over a month use command:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-\-month\-report 2919\-05 /path/to/incremantal/reports/
|
||||
.Ve
|
||||
.PP
|
||||
this will add a link to the month name into the calendar view in
|
||||
incremental reports to look at report for month 2019 May.
|
||||
Use \-E or \-\-explode if the reports were built using this option.
|
||||
.SH "DESCRIPTION"
|
||||
.IX Header "DESCRIPTION"
|
||||
pgBadger is a PostgreSQL log analyzer built for speed with fully reports
|
||||
from your PostgreSQL log file. It's a single and small Perl script that
|
||||
outperforms any other PostgreSQL log analyzer.
|
||||
.PP
|
||||
It is written in pure Perl and uses a JavaScript library (flotr2) to draw
|
||||
graphs so that you don't need to install any additional Perl modules or
|
||||
other packages. Furthermore, this library gives us more features such
|
||||
as zooming. pgBadger also uses the Bootstrap JavaScript library and
|
||||
the FontAwesome webfont for better design. Everything is embedded.
|
||||
.PP
|
||||
pgBadger is able to autodetect your log file format (syslog, stderr, csvlog
|
||||
or jsonlog) if the file is long enough. It is designed to parse huge log
|
||||
files as well as compressed files. Supported compressed format are gzip,
|
||||
bzip2 and xz. For the xz format you must have an xz version upper than 5.05
|
||||
that supports the \-\-robot option. For the complete list of features see below.
|
||||
.PP
|
||||
All charts are zoomable and can be saved as \s-1PNG\s0 images.
|
||||
.PP
|
||||
You can also limit pgBadger to only report errors or remove any part of the
|
||||
report using command line options.
|
||||
.PP
|
||||
pgBadger supports any custom format set into the log_line_prefix directive of
|
||||
your postgresql.conf file as long as it at least specify the \f(CW%t\fR and \f(CW%p\fR patterns.
|
||||
.PP
|
||||
pgBadger allows parallel processing of a single log file or multiple
|
||||
files through the use of the \-j option specifying the number of CPUs.
|
||||
.PP
|
||||
If you want to save system performance you can also use log_duration instead of
|
||||
log_min_duration_statement to have reports on duration and number of queries only.
|
||||
.SH "FEATURE"
|
||||
.IX Header "FEATURE"
|
||||
pgBadger reports everything about your \s-1SQL\s0 queries:
|
||||
.PP
|
||||
.Vb 10
|
||||
\& Overall statistics.
|
||||
\& The most frequent waiting queries.
|
||||
\& Queries that waited the most.
|
||||
\& Queries generating the most temporary files.
|
||||
\& Queries generating the largest temporary files.
|
||||
\& The slowest queries.
|
||||
\& Queries that took up the most time.
|
||||
\& The most frequent queries.
|
||||
\& The most frequent errors.
|
||||
\& Histogram of query times.
|
||||
\& Histogram of sessions times.
|
||||
\& Users involved in top queries.
|
||||
\& Applications involved in top queries.
|
||||
\& Queries generating the most cancellation.
|
||||
\& Queries most cancelled.
|
||||
\& The most time consuming prepare/bind queries
|
||||
.Ve
|
||||
.PP
|
||||
The following reports are also available with hourly charts divided into
|
||||
periods of five minutes:
|
||||
.PP
|
||||
.Vb 7
|
||||
\& SQL queries statistics.
|
||||
\& Temporary file statistics.
|
||||
\& Checkpoints statistics.
|
||||
\& Autovacuum and autoanalyze statistics.
|
||||
\& Cancelled queries.
|
||||
\& Error events (panic, fatal, error and warning).
|
||||
\& Error class distribution.
|
||||
.Ve
|
||||
.PP
|
||||
There are also some pie charts about distribution of:
|
||||
.PP
|
||||
.Vb 7
|
||||
\& Locks statistics.
|
||||
\& Queries by type (select/insert/update/delete).
|
||||
\& Distribution of queries type per database/application
|
||||
\& Sessions per database/user/client/application.
|
||||
\& Connections per database/user/client/application.
|
||||
\& Autovacuum and autoanalyze per table.
|
||||
\& Queries per user and total duration per user.
|
||||
.Ve
|
||||
.PP
|
||||
All charts are zoomable and can be saved as \s-1PNG\s0 images. \s-1SQL\s0 queries reported are
|
||||
highlighted and beautified automatically.
|
||||
.PP
|
||||
pgBadger is also able to parse PgBouncer log files and to create the following
|
||||
reports:
|
||||
.PP
|
||||
.Vb 10
|
||||
\& Request Throughput
|
||||
\& Bytes I/O Throughput
|
||||
\& Queries Average duration
|
||||
\& Simultaneous sessions
|
||||
\& Histogram of sessions times
|
||||
\& Sessions per database
|
||||
\& Sessions per user
|
||||
\& Sessions per host
|
||||
\& Established connections
|
||||
\& Connections per database
|
||||
\& Connections per user
|
||||
\& Connections per host
|
||||
\& Most used reserved pools
|
||||
\& Most Frequent Errors/Events
|
||||
.Ve
|
||||
.PP
|
||||
You can also have incremental reports with one report per day and a cumulative
|
||||
report per week. Two multiprocess modes are available to speed up log parsing,
|
||||
one using one core per log file, and the second using multiple cores to parse
|
||||
a single file. These modes can be combined.
|
||||
.PP
|
||||
Histogram granularity can be adjusted using the \-A command line option. By default
|
||||
they will report the mean of each top queries/errors occurring per hour, but you can
|
||||
specify the granularity down to the minute.
|
||||
.PP
|
||||
pgBadger can also be used in a central place to parse remote log files using a
|
||||
passwordless \s-1SSH\s0 connection. This mode can be used with compressed files and in
|
||||
the multiprocess per file mode (\-J) but can not be used with the \s-1CSV\s0 log format.
|
||||
.SH "REQUIREMENT"
|
||||
.IX Header "REQUIREMENT"
|
||||
pgBadger comes as a single Perl script \- you do not need anything other than a modern
|
||||
Perl distribution. Charts are rendered using a JavaScript library so you don't need
|
||||
anything other than a web browser. Your browser will do all the work.
|
||||
.PP
|
||||
If you planned to parse PostgreSQL \s-1CSV\s0 log files you might need some Perl Modules:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& Text::CSV_XS \- to parse PostgreSQL CSV log files.
|
||||
.Ve
|
||||
.PP
|
||||
This module is optional, if you don't have PostgreSQL log in the \s-1CSV\s0 format you don't
|
||||
need to install it.
|
||||
.PP
|
||||
If you want to export statistics as \s-1JSON\s0 file you need an additional Perl module:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& JSON::XS \- JSON serialising/deserialising, done correctly and fast
|
||||
.Ve
|
||||
.PP
|
||||
This module is optional, if you don't select the json output format you don't
|
||||
need to install it. You can install it on a Debian like system using:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& sudo apt\-get install libjson\-xs\-perl
|
||||
.Ve
|
||||
.PP
|
||||
and in \s-1RPM\s0 like system using:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& sudo yum install perl\-JSON\-XS
|
||||
.Ve
|
||||
.PP
|
||||
Compressed log file format is autodetected from the file extension. If pgBadger find
|
||||
a gz extension it will use the zcat utility, with a bz2 extension it will use bzcat
|
||||
and if the file extension is zip or xz then the unzip or xz utilities will be used.
|
||||
.PP
|
||||
If those utilities are not found in the \s-1PATH\s0 environment variable then use the \-\-zcat
|
||||
command line option to change this path. For example:
|
||||
.PP
|
||||
.Vb 2
|
||||
\& \-\-zcat="/usr/local/bin/gunzip \-c" or \-\-zcat="/usr/local/bin/bzip2 \-dc"
|
||||
\& \-\-zcat="C:\etools\eunzip \-p"
|
||||
.Ve
|
||||
.PP
|
||||
By default pgBadger will use the zcat, bzcat and unzip utilities following the
|
||||
file extension. If you use the default autodetection compress format you can
|
||||
mixed gz, bz2, xz or zip files. Specifying a custom value to \-\-zcat option will
|
||||
remove this feature of mixed compressed format.
|
||||
.PP
|
||||
Note that multiprocessing can not be used with compressed files or \s-1CSV\s0 files as
|
||||
well as under Windows platform.
|
||||
.SH "INSTALLATION"
|
||||
.IX Header "INSTALLATION"
|
||||
Download the tarball from GitHub and unpack the archive as follow:
|
||||
.PP
|
||||
.Vb 4
|
||||
\& tar xzf pgbadger\-11.x.tar.gz
|
||||
\& cd pgbadger\-11.x/
|
||||
\& perl Makefile.PL
|
||||
\& make && sudo make install
|
||||
.Ve
|
||||
.PP
|
||||
This will copy the Perl script pgbadger to /usr/local/bin/pgbadger by default and the
|
||||
man page into /usr/local/share/man/man1/pgbadger.1. Those are the default installation
|
||||
directories for 'site' install.
|
||||
.PP
|
||||
If you want to install all under /usr/ location, use INSTALLDIRS='perl' as an argument
|
||||
of Makefile.PL. The script will be installed into /usr/bin/pgbadger and the manpage
|
||||
into /usr/share/man/man1/pgbadger.1.
|
||||
.PP
|
||||
For example, to install everything just like Debian does, proceed as follows:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& perl Makefile.PL INSTALLDIRS=vendor
|
||||
.Ve
|
||||
.PP
|
||||
By default \s-1INSTALLDIRS\s0 is set to site.
|
||||
.SH "POSTGRESQL CONFIGURATION"
|
||||
.IX Header "POSTGRESQL CONFIGURATION"
|
||||
You must enable and set some configuration directives in your postgresql.conf
|
||||
before starting.
|
||||
.PP
|
||||
You must first enable \s-1SQL\s0 query logging to have something to parse:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& log_min_duration_statement = 0
|
||||
.Ve
|
||||
.PP
|
||||
Here every statement will be logged, on a busy server you may want to increase
|
||||
this value to only log queries with a longer duration. Note that if you have
|
||||
log_statement set to 'all' nothing will be logged through the log_min_duration_statement
|
||||
directive. See the next chapter for more information.
|
||||
.PP
|
||||
pgBadger supports any custom format set into the log_line_prefix directive of
|
||||
your postgresql.conf file as long as it at least specify a time escape sequence
|
||||
(%t, \f(CW%m\fR or \f(CW%n\fR) and the process related escape sequence (%p or \f(CW%c\fR).
|
||||
.PP
|
||||
For example, with 'stderr' log format, log_line_prefix must be at least:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& log_line_prefix = \*(Aq%t [%p]: \*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
Log line prefix could add user, database name, application name and client ip
|
||||
address as follows:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& log_line_prefix = \*(Aq%t [%p]: user=%u,db=%d,app=%a,client=%h \*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
or for syslog log file format:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& log_line_prefix = \*(Aquser=%u,db=%d,app=%a,client=%h \*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
Log line prefix for stderr output could also be:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& log_line_prefix = \*(Aq%t [%p]: db=%d,user=%u,app=%a,client=%h \*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
or for syslog output:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& log_line_prefix = \*(Aqdb=%d,user=%u,app=%a,client=%h \*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
You need to enable other parameters in postgresql.conf to get more information from your log files:
|
||||
.PP
|
||||
.Vb 7
|
||||
\& log_checkpoints = on
|
||||
\& log_connections = on
|
||||
\& log_disconnections = on
|
||||
\& log_lock_waits = on
|
||||
\& log_temp_files = 0
|
||||
\& log_autovacuum_min_duration = 0
|
||||
\& log_error_verbosity = default
|
||||
.Ve
|
||||
.PP
|
||||
Do not enable log_statement as its log format will not be parsed by pgBadger.
|
||||
.PP
|
||||
Of course your log messages should be in English without locale support:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& lc_messages=\*(AqC\*(Aq
|
||||
.Ve
|
||||
.PP
|
||||
but this is not only recommended by pgBadger.
|
||||
.SH "LOG STATEMENTS"
|
||||
.IX Header "LOG STATEMENTS"
|
||||
Considerations about log_min_duration_statement, log_duration and log_statement
|
||||
configuration directives.
|
||||
.PP
|
||||
If you want the query statistics to include the actual query strings, you
|
||||
must set log_min_duration_statement to 0 or more milliseconds.
|
||||
.PP
|
||||
If you just want to report duration and number of queries and don't want all
|
||||
details about queries, set log_min_duration_statement to \-1 to disable it and
|
||||
enable log_duration in your postgresql.conf file. If you want to add the most
|
||||
common request report you can either choose to set log_min_duration_statement
|
||||
to a higher value or choose to enable log_statement.
|
||||
.PP
|
||||
Enabling log_min_duration_statement will add reports about slowest queries and
|
||||
queries that took up the most time. Take care that if you have log_statement
|
||||
set to 'all' nothing will be logged with log_min_duration_statement.
|
||||
.PP
|
||||
Warning: Do not enable both log_min_duration_statement, log_duration and
|
||||
log_statement all together, this will result in wrong counter values. Note
|
||||
that this will also increase drastically the size of your log.
|
||||
log_min_duration_statement should always be preferred.
|
||||
.SH "PARALLEL PROCESSING"
|
||||
.IX Header "PARALLEL PROCESSING"
|
||||
To enable parallel processing you just have to use the \-j N option where N is
|
||||
the number of cores you want to use.
|
||||
.PP
|
||||
pgBadger will then proceed as follow:
|
||||
.PP
|
||||
.Vb 10
|
||||
\& for each log file
|
||||
\& chunk size = int(file size / N)
|
||||
\& look at start/end offsets of these chunks
|
||||
\& fork N processes and seek to the start offset of each chunk
|
||||
\& each process will terminate when the parser reach the end offset
|
||||
\& of its chunk
|
||||
\& each process write stats into a binary temporary file
|
||||
\& wait for all children has terminated
|
||||
\& All binary temporary files generated will then be read and loaded into
|
||||
\& memory to build the html output.
|
||||
.Ve
|
||||
.PP
|
||||
With that method, at start/end of chunks pgBadger may truncate or omit a
|
||||
maximum of N queries per log file which is an insignificant gap if you have
|
||||
millions of queries in your log file. The chance that the query that you were
|
||||
looking for is lost is near 0, this is why I think this gap is livable. Most
|
||||
of the time the query is counted twice but truncated.
|
||||
.PP
|
||||
When you have many small log files and many CPUs it is speedier to dedicate
|
||||
one core to one log file at a time. To enable this behavior you have to use
|
||||
option \-J N instead. With 200 log files of 10MB each the use of the \-J option
|
||||
starts being really interesting with 8 Cores. Using this method you will be
|
||||
sure not to lose any queries in the reports.
|
||||
.PP
|
||||
He are a benchmark done on a server with 8 CPUs and a single file of 9.5GB.
|
||||
.PP
|
||||
.Vb 4
|
||||
\& Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
|
||||
\& \-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-
|
||||
\& \-j | 1h41m18 | 50m25 | 25m39 | 15m58
|
||||
\& \-J | 1h41m18 | 54m28 | 41m16 | 34m45
|
||||
.Ve
|
||||
.PP
|
||||
With 200 log files of 10MB each and a total of 2GB the results are slightly
|
||||
different:
|
||||
.PP
|
||||
.Vb 4
|
||||
\& Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
|
||||
\& \-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-
|
||||
\& \-j | 20m15 | 9m56 | 5m20 | 4m20
|
||||
\& \-J | 20m15 | 9m49 | 5m00 | 2m40
|
||||
.Ve
|
||||
.PP
|
||||
So it is recommended to use \-j unless you have hundreds of small log files
|
||||
and can use at least 8 CPUs.
|
||||
.PP
|
||||
\&\s-1IMPORTANT:\s0 when you are using parallel parsing pgBadger will generate a
|
||||
lot of temporary files in the /tmp directory and will remove them at the
|
||||
end, so do not remove those files unless pgBadger is not running. They are
|
||||
all named with the following template tmp_pgbadgerXXXX.bin so they can be
|
||||
easily identified.
|
||||
.SH "INCREMENTAL REPORTS"
|
||||
.IX Header "INCREMENTAL REPORTS"
|
||||
pgBadger includes an automatic incremental report mode using option \-I or
|
||||
\&\-\-incremental. When running in this mode, pgBadger will generate one report
|
||||
per day and a cumulative report per week. Output is first done in binary
|
||||
format into the mandatory output directory (see option \-O or \-\-outdir),
|
||||
then in \s-1HTML\s0 format for daily and weekly reports with a main index file.
|
||||
.PP
|
||||
The main index file will show a dropdown menu per week with a link to each
|
||||
week report and links to daily reports of each week.
|
||||
.PP
|
||||
For example, if you run pgBadger as follows based on a daily rotated file:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& 0 4 * * * /usr/bin/pgbadger \-I \-q /var/log/postgresql/postgresql.log.1 \-O /var/www/pg_reports/
|
||||
.Ve
|
||||
.PP
|
||||
you will have all daily and weekly reports for the full running period.
|
||||
.PP
|
||||
In this mode pgBadger will create an automatic incremental file in the
|
||||
output directory, so you don't have to use the \-l option unless you want
|
||||
to change the path of that file. This means that you can run pgBadger in
|
||||
this mode each day on a log file rotated each week, and it will not count
|
||||
the log entries twice.
|
||||
.PP
|
||||
To save disk space you may want to use the \-X or \-\-extra\-files command line
|
||||
option to force pgBadger to write JavaScript and \s-1CSS\s0 to separate files in
|
||||
the output directory. The resources will then be loaded using script and
|
||||
link tags.
|
||||
.SS "Rebuilding reports"
|
||||
.IX Subsection "Rebuilding reports"
|
||||
Incremental reports can be rebuilt after a pgbadger report fix or a new
|
||||
feature to update all \s-1HTML\s0 reports. To rebuild all reports where a binary
|
||||
file is still present proceed as follow:
|
||||
.PP
|
||||
.Vb 3
|
||||
\& rm /path/to/reports/*.js
|
||||
\& rm /path/to/reports/*.css
|
||||
\& pgbadger \-X \-I \-O /path/to/reports/ \-\-rebuild
|
||||
.Ve
|
||||
.PP
|
||||
it will also update all resource files (\s-1JS\s0 and \s-1CSS\s0). Use \-E or \-\-explode
|
||||
if the reports were built using this option.
|
||||
.SS "Monthly reports"
|
||||
.IX Subsection "Monthly reports"
|
||||
By default pgBadger in incremental mode only compute daily and weekly reports.
|
||||
If you want monthly cumulative reports you will have to use a separate command
|
||||
to specify the report to build. For example to build a report for August 2019:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-X \-\-month\-report 2919\-08 /var/www/pg_reports/
|
||||
.Ve
|
||||
.PP
|
||||
this will add a link to the month name into the calendar view of incremental
|
||||
reports to look at monthly report. The report for a current month can be run
|
||||
every day it is entirely rebuilt each time. The monthly report is not built by
|
||||
default because it could take lot of time following the amount of data.
|
||||
.PP
|
||||
If reports were built with the per database option ( \-E | \-\-explode ) it must
|
||||
be used too when calling pgbadger to build monthly report:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-E \-X \-\-month\-report 2919\-08 /var/www/pg_reports/
|
||||
.Ve
|
||||
.PP
|
||||
This is the same when using the rebuild option ( \-R | \-\-rebuild ).
|
||||
.SH "BINARY FORMAT"
|
||||
.IX Header "BINARY FORMAT"
|
||||
Using the binary format it is possible to create custom incremental and
|
||||
cumulative reports. For example, if you want to refresh a pgBadger
|
||||
report each hour from a daily PostgreSQL log file, you can proceed by
|
||||
running each hour the following commands:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-\-last\-parsed .pgbadger_last_state_file \-o sunday/hourX.bin /var/log/pgsql/postgresql\-Sun.log
|
||||
.Ve
|
||||
.PP
|
||||
to generate the incremental data files in binary format. And to generate the fresh \s-1HTML\s0
|
||||
report from that binary file:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger sunday/*.bin
|
||||
.Ve
|
||||
.PP
|
||||
Or as another example, if you generate one log file per hour and you want
|
||||
reports to be rebuilt each time the log file is rotated, proceed as
|
||||
follows:
|
||||
.PP
|
||||
.Vb 4
|
||||
\& pgbadger \-o day1/hour01.bin /var/log/pgsql/pglog/postgresql\-2012\-03\-23_10.log
|
||||
\& pgbadger \-o day1/hour02.bin /var/log/pgsql/pglog/postgresql\-2012\-03\-23_11.log
|
||||
\& pgbadger \-o day1/hour03.bin /var/log/pgsql/pglog/postgresql\-2012\-03\-23_12.log
|
||||
\& ...
|
||||
.Ve
|
||||
.PP
|
||||
When you want to refresh the \s-1HTML\s0 report, for example each time after a new binary file
|
||||
is generated, just do the following:
|
||||
.PP
|
||||
.Vb 1
|
||||
\& pgbadger \-o day1_report.html day1/*.bin
|
||||
.Ve
|
||||
.PP
|
||||
Adjust the commands to suit your particular needs.
|
||||
.SH "JSON FORMAT"
|
||||
.IX Header "JSON FORMAT"
|
||||
\&\s-1JSON\s0 format is good for sharing data with other languages, which makes it
|
||||
easy to integrate pgBadger result into other monitoring tools like Cacti
|
||||
or Graphite.
|
||||
.SH "AUTHORS"
|
||||
.IX Header "AUTHORS"
|
||||
pgBadger is an original work from Gilles Darold.
|
||||
.PP
|
||||
The pgBadger logo is an original creation of Damien Cazeils.
|
||||
.PP
|
||||
The pgBadger v4.x design comes from the \*(L"Art is code\*(R" company.
|
||||
.PP
|
||||
This web site is a work of Gilles Darold.
|
||||
.PP
|
||||
pgBadger is maintained by Gilles Darold and every one who wants to contribute.
|
||||
.PP
|
||||
Many people have contributed to pgBadger, they are all quoted in the Changelog file.
|
||||
.SH "LICENSE"
|
||||
.IX Header "LICENSE"
|
||||
pgBadger is free software distributed under the PostgreSQL Licence.
|
||||
.PP
|
||||
Copyright (c) 2012\-2019, Gilles Darold
|
||||
.PP
|
||||
A modified version of the SQL::Beautify Perl Module is embedded in pgBadger
|
||||
with copyright (C) 2009 by Jonas Kramer and is published under the terms of
|
||||
the Artistic License 2.0.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,721 @@
|
|||
=head1 NAME
|
||||
|
||||
pgBadger - a fast PostgreSQL log analysis report
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
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 files. If you want to pass log content from stdin
|
||||
use - as filename. Note that input from stdin will not work with csvlog.
|
||||
|
||||
Options:
|
||||
|
||||
-a | --average minutes : number of minutes to build the average graphs of
|
||||
queries and connections. Default 5 minutes.
|
||||
-A | --histo-average min: number of minutes to build the histogram graphs
|
||||
of queries. Default 60 minutes.
|
||||
-b | --begin datetime : start date/time for the data to be parsed in log
|
||||
(either a timestamp or a time)
|
||||
-c | --dbclient host : only report on entries for the given client host.
|
||||
-C | --nocomment : remove comments like /* ... */ from queries.
|
||||
-d | --dbname database : only report on entries for the given database.
|
||||
-D | --dns-resolv : client ip addresses are replaced by their DNS name.
|
||||
Be warned that this can really slow down pgBadger.
|
||||
-e | --end datetime : end date/time for the data to be parsed in log
|
||||
(either a timestamp or a time)
|
||||
-E | --explode : explode the main report by generating one report
|
||||
per database. Global information not related to a
|
||||
database are added to the postgres database report.
|
||||
-f | --format logtype : possible values: syslog, syslog2, stderr, jsonlog,
|
||||
cvs, pgbouncer, logplex and rds. Use this option
|
||||
when pgBadger is not able to detect the log format.
|
||||
-G | --nograph : disable graphs on HTML output. Enabled by default.
|
||||
-h | --help : show this message and exit.
|
||||
-H | --html-dir path : path to directory where HTML report must be written
|
||||
in incremental mode, binary files stay on directory
|
||||
defined with -O, --outdir option.
|
||||
-i | --ident name : programname used as syslog ident. Default: postgres
|
||||
-I | --incremental : use incremental mode, reports will be generated by
|
||||
days in a separate directory, --outdir must be set.
|
||||
-j | --jobs number : number of jobs to run at same time. Run as single
|
||||
by default or when working with csvlog.
|
||||
-J | --Jobs number : number of log file to parse in parallel. Process one
|
||||
file at a time by default or when csvlog is used.
|
||||
-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.
|
||||
-L | --logfile-list file:file containing a list of log file to parse.
|
||||
-m | --maxlength size : maximum length of a query, it will be restricted to
|
||||
the given size. Default: no truncate
|
||||
-M | --no-multiline : do not collect multiline statement to avoid garbage
|
||||
especially on errors that generate a huge report.
|
||||
-n | --nohighlight : disable SQL code highlighting.
|
||||
-N | --appname name : only report on entries for given application name
|
||||
-o | --outfile filename: define the filename for the output. Default depends
|
||||
on the output format: out.html, out.txt, out.bin,
|
||||
out.json or out.tsung. This option can be used
|
||||
multiple time to output several format. To use json
|
||||
output the Perl module JSON::XS must be installed,
|
||||
To dump output to stdout use - as filename.
|
||||
-O | --outdir path : directory where out file must be saved.
|
||||
-p | --prefix string : the value of your custom log_line_prefix as
|
||||
defined in your postgresql.conf. Only use it if you
|
||||
aren't using one of the standard prefixes specified
|
||||
in the pgBadger documentation, such as if your
|
||||
prefix includes additional variables like client ip
|
||||
or application name. See examples below.
|
||||
-P | --no-prettify : disable SQL queries prettify formatter.
|
||||
-q | --quiet : don't print anything to stdout, not even a progress
|
||||
bar.
|
||||
-r | --remote-host ip : set the host where to execute the cat command on
|
||||
remote logfile to parse locally the file.
|
||||
-R | --retention N : number of weeks to keep in incremental mode. Default
|
||||
to 0, disabled. Used to set the number of weeks to
|
||||
keep in output directory. Older weeks and days
|
||||
directory are automatically removed.
|
||||
-s | --sample number : number of query samples to store. Default: 3.
|
||||
-S | --select-only : only report SELECT queries.
|
||||
-t | --top number : number of queries to store/display. Default: 20.
|
||||
-T | --title string : change title of the HTML page report.
|
||||
-u | --dbuser username : only report on entries for the given user.
|
||||
-U | --exclude-user username : exclude entries for the specified user from
|
||||
report. Can be used multiple time.
|
||||
-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, html, bin, json or
|
||||
tsung. Default: html
|
||||
-X | --extra-files : in incremental mode allow pgBadger to write CSS and
|
||||
JS files in the output directory as separate files.
|
||||
-z | --zcat exec_path : set the full path to the zcat program. Use it if
|
||||
zcat or bzcat or unzip is not in your path.
|
||||
-Z | --timezone +/-XX : Set the number of hours from GMT of the timezone.
|
||||
Use this to adjust date/time in JavaScript graphs.
|
||||
--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 times.
|
||||
--exclude-file filename: path of the file which contains all the regex to
|
||||
use to exclude queries from the report. One regex
|
||||
per line.
|
||||
--include-query regex : any query that does not match the given regex will
|
||||
be excluded from the report. You can use this
|
||||
option multiple times. For example: "(tbl1|tbl2)".
|
||||
--include-file filename: path of the file which contains all the regex of
|
||||
the queries to include from the report. One regex
|
||||
per line.
|
||||
--disable-error : do not generate error report.
|
||||
--disable-hourly : do not generate hourly report.
|
||||
--disable-type : do not generate report of queries by type, database
|
||||
or user.
|
||||
--disable-query : do not generate query reports (slowest, most
|
||||
frequent, queries by users, by database, ...).
|
||||
--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/restartpoint report.
|
||||
--disable-autovacuum : do not generate autovacuum report.
|
||||
--charset : used to set the HTML charset to be used.
|
||||
Default: utf-8.
|
||||
--csv-separator : used to set the CSV field separator, default: ,
|
||||
--exclude-time regex : any timestamp matching the given regex will be
|
||||
excluded from the report. Example: "2013-04-12 .*"
|
||||
You can use this option multiple times.
|
||||
--include-time regex : only timestamps matching the given regex will be
|
||||
included in the report. Example: "2013-04-12 .*"
|
||||
You can use this option multiple times.
|
||||
--exclude-db name : exclude entries for the specified database from
|
||||
report. Example: "pg_dump". Can be used multiple
|
||||
time.
|
||||
--exclude-appname name : exclude entries for the specified application name
|
||||
from report. Example: "pg_dump". Can be used
|
||||
multiple time.
|
||||
--exclude-line regex : pgBadger will start to exclude any log entry that
|
||||
will match the given regex. Can be used multiple
|
||||
time.
|
||||
--exclude-client name : exclude log entries for the specified client ip.
|
||||
Can be used multiple time.
|
||||
--anonymize : obscure all literals in queries, useful to hide
|
||||
confidential data.
|
||||
--noreport : prevent pgBadger to create reports in incremental
|
||||
mode.
|
||||
--log-duration : force pgBadger to associate log entries generated
|
||||
by both log_duration = on and log_statement = 'all'
|
||||
--enable-checksum : used to add a md5 sum under each query report.
|
||||
--journalctl command : command to use to replace PostgreSQL logfile by
|
||||
a call to journalctl. Basically it might be:
|
||||
journalctl -u postgresql-9.5
|
||||
--pid-dir path : set the path where the pid file must be stored.
|
||||
Default /tmp
|
||||
--pid-file file : set the name of the pid file to manage concurrent
|
||||
execution of pgBadger. Default: pgbadger.pid
|
||||
--rebuild : used to rebuild all html reports in incremental
|
||||
output directories where there's binary data files.
|
||||
--pgbouncer-only : only show PgBouncer related menu in the header.
|
||||
--start-monday : in incremental mode, calendar's weeks start on
|
||||
sunday. Use this option to start on monday.
|
||||
--normalized-only : only dump all normalized query to out.txt
|
||||
--log-timezone +/-XX : Set the number of hours from GMT of the timezone
|
||||
that must be used to adjust date/time read from
|
||||
log file before beeing parsed. Using this option
|
||||
make more difficult log search with a date/time.
|
||||
--prettify-json : use it if you want json output to be prettified.
|
||||
--month-report YYYY-MM : create a cumulative HTML report over the specified
|
||||
month. Requires incremental output directories and
|
||||
the presence of all necessary binary data files
|
||||
|
||||
pgBadger is able to parse a remote log file using a passwordless ssh connection.
|
||||
Use the -r or --remote-host to set the host ip address or hostname. There's also
|
||||
some additional options to fully control the ssh connection.
|
||||
|
||||
--ssh-program ssh path to the ssh program to use. Default: ssh.
|
||||
--ssh-user username connection login name. Default to running user.
|
||||
--ssh-identity file path to the identity file to use.
|
||||
--ssh-timeout second timeout to ssh connection failure. Default 10 secs.
|
||||
--ssh-option options list of -o options to use for the ssh connection.
|
||||
Options always used:
|
||||
-o ConnectTimeout=$ssh_timeout
|
||||
-o PreferredAuthentications=hostbased,publickey
|
||||
|
||||
Log file to parse can also be specified using an URI, supported protocol are
|
||||
http[s] and [s]ftp. The curl command will be used to download the file and the
|
||||
file will be parsed during download. The ssh protocol is also supported and will
|
||||
use the ssh command like with the remote host use. See examples bellow.
|
||||
|
||||
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
|
||||
pgbadger -b "2012-06-25 10:56:11" -e "2012-06-25 10:59:11" /var/log/postgresql.log
|
||||
cat /var/log/postgres.log | pgbadger -
|
||||
# Log prefix with stderr log output
|
||||
pgbadger --prefix '%t [%p]: user=%u,db=%d,client=%h' /pglog/postgresql-2012-08-21*
|
||||
pgbadger --prefix '%m %u@%d %p %r %a : ' /pglog/postgresql.log
|
||||
# Log line prefix with syslog log output
|
||||
pgbadger --prefix 'user=%u,db=%d,client=%h,appname=%a' /pglog/postgresql-2012-08-21*
|
||||
# Use my 8 CPUs to parse my 10GB file faster, much faster
|
||||
pgbadger -j 8 /pglog/postgresql-10.1-main.log
|
||||
|
||||
Use URI notation for remote log file:
|
||||
|
||||
pgbadger http://172.12.110.1//var/log/postgresql/postgresql-10.1-main.log
|
||||
pgbadger ftp://username@172.12.110.14/postgresql-10.1-main.log
|
||||
pgbadger ssh://username@172.12.110.14//var/log/postgresql/postgresql-10.1-main.log*
|
||||
|
||||
You can use together a local PostgreSQL log and a remote pgbouncer log file to parse:
|
||||
|
||||
pgbadger /var/log/postgresql/postgresql-10.1-main.log ssh://username@172.12.110.14/pgbouncer.log
|
||||
|
||||
Generate Tsung sessions XML file with select queries only:
|
||||
|
||||
pgbadger -S -o sessions.tsung --prefix '%t [%p]: user=%u,db=%d ' /pglog/postgresql-10.1.log
|
||||
|
||||
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
|
||||
|
||||
Generate report every week using incremental behavior:
|
||||
|
||||
0 4 * * 1 /usr/bin/pgbadger -q `find /var/log/ -mtime -7 -name "postgresql.log*"` -o /var/reports/pg_errors-`date +\%F`.html -l /var/reports/pgbadger_incremental_file.dat
|
||||
|
||||
This supposes that your log file and HTML report are also rotated every week.
|
||||
|
||||
Or better, use the auto-generated incremental reports:
|
||||
|
||||
0 4 * * * /usr/bin/pgbadger -I -q /var/log/postgresql/postgresql.log.1 -O /var/www/pg_reports/
|
||||
|
||||
will generate a report per day and per week.
|
||||
|
||||
In incremental mode, you can also specify the number of week to keep in the
|
||||
reports:
|
||||
|
||||
/usr/bin/pgbadger --retention 2 -I -q /var/log/postgresql/postgresql.log.1 -O /var/www/pg_reports/
|
||||
|
||||
If you have a pg_dump at 23:00 and 13:00 each day during half an hour, you can
|
||||
use pgBadger as follow to exclude these period from the report:
|
||||
|
||||
pgbadger --exclude-time "2013-09-.* (23|13):.*" postgresql.log
|
||||
|
||||
This will help avoid having COPY statements, as generated by pg_dump, on top of
|
||||
the list of slowest queries. You can also use --exclude-appname "pg_dump" to
|
||||
solve this problem in a simpler way.
|
||||
|
||||
You can also parse journalctl output just as if it was a log file:
|
||||
|
||||
pgbadger --journalctl 'journalctl -u postgresql-9.5'
|
||||
|
||||
or worst, call it from a remote host:
|
||||
|
||||
pgbadger -r 192.168.1.159 --journalctl 'journalctl -u postgresql-9.5'
|
||||
|
||||
you don't need to specify any log file at command line, but if you have other
|
||||
PostgreSQL log file to parse, you can add them as usual.
|
||||
|
||||
To rebuild all incremental html reports after, proceed as follow:
|
||||
|
||||
rm /path/to/reports/*.js
|
||||
rm /path/to/reports/*.css
|
||||
pgbadger -X -I -O /path/to/reports/ --rebuild
|
||||
|
||||
it will also update all resource files (JS and CSS). Use -E or --explode
|
||||
if the reports were built using this option.
|
||||
|
||||
pgBadger also support Heroku PostgreSQL logs using logplex format:
|
||||
|
||||
heroku logs -p postgres | pgbadger -f logplex -o heroku.html -
|
||||
|
||||
this will stream Heroku PostgreSQL log to pgbadger through stdin.
|
||||
|
||||
pgBadger can auto detect RDS and cloudwatch PostgreSQL logs using
|
||||
rds format:
|
||||
|
||||
pgbadger -f rds -o rds_out.html rds.log
|
||||
|
||||
To create a cumulative report over a month use command:
|
||||
|
||||
pgbadger --month-report 2919-05 /path/to/incremantal/reports/
|
||||
|
||||
this will add a link to the month name into the calendar view in
|
||||
incremental reports to look at report for month 2019 May.
|
||||
Use -E or --explode if the reports were built using this option.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
pgBadger is a PostgreSQL log analyzer built for speed with fully reports
|
||||
from your PostgreSQL log file. It's a single and small Perl script that
|
||||
outperforms any other PostgreSQL log analyzer.
|
||||
|
||||
It is written in pure Perl and uses a JavaScript library (flotr2) to draw
|
||||
graphs so that you don't need to install any additional Perl modules or
|
||||
other packages. Furthermore, this library gives us more features such
|
||||
as zooming. pgBadger also uses the Bootstrap JavaScript library and
|
||||
the FontAwesome webfont for better design. Everything is embedded.
|
||||
|
||||
pgBadger is able to autodetect your log file format (syslog, stderr, csvlog
|
||||
or jsonlog) if the file is long enough. It is designed to parse huge log
|
||||
files as well as compressed files. Supported compressed format are gzip,
|
||||
bzip2 and xz. For the xz format you must have an xz version upper than 5.05
|
||||
that supports the --robot option. For the complete list of features see below.
|
||||
|
||||
All charts are zoomable and can be saved as PNG images.
|
||||
|
||||
You can also limit pgBadger to only report errors or remove any part of the
|
||||
report using command line options.
|
||||
|
||||
pgBadger supports any custom format set into the log_line_prefix directive of
|
||||
your postgresql.conf file as long as it at least specify the %t and %p patterns.
|
||||
|
||||
pgBadger allows parallel processing of a single log file or multiple
|
||||
files through the use of the -j option specifying the number of CPUs.
|
||||
|
||||
If you want to save system performance you can also use log_duration instead of
|
||||
log_min_duration_statement to have reports on duration and number of queries only.
|
||||
|
||||
=head1 FEATURE
|
||||
|
||||
pgBadger reports everything about your SQL queries:
|
||||
|
||||
Overall statistics.
|
||||
The most frequent waiting queries.
|
||||
Queries that waited the most.
|
||||
Queries generating the most temporary files.
|
||||
Queries generating the largest temporary files.
|
||||
The slowest queries.
|
||||
Queries that took up the most time.
|
||||
The most frequent queries.
|
||||
The most frequent errors.
|
||||
Histogram of query times.
|
||||
Histogram of sessions times.
|
||||
Users involved in top queries.
|
||||
Applications involved in top queries.
|
||||
Queries generating the most cancellation.
|
||||
Queries most cancelled.
|
||||
The most time consuming prepare/bind queries
|
||||
|
||||
The following reports are also available with hourly charts divided into
|
||||
periods of five minutes:
|
||||
|
||||
SQL queries statistics.
|
||||
Temporary file statistics.
|
||||
Checkpoints statistics.
|
||||
Autovacuum and autoanalyze statistics.
|
||||
Cancelled queries.
|
||||
Error events (panic, fatal, error and warning).
|
||||
Error class distribution.
|
||||
|
||||
There are also some pie charts about distribution of:
|
||||
|
||||
Locks statistics.
|
||||
Queries by type (select/insert/update/delete).
|
||||
Distribution of queries type per database/application
|
||||
Sessions per database/user/client/application.
|
||||
Connections per database/user/client/application.
|
||||
Autovacuum and autoanalyze per table.
|
||||
Queries per user and total duration per user.
|
||||
|
||||
All charts are zoomable and can be saved as PNG images. SQL queries reported are
|
||||
highlighted and beautified automatically.
|
||||
|
||||
pgBadger is also able to parse PgBouncer log files and to create the following
|
||||
reports:
|
||||
|
||||
Request Throughput
|
||||
Bytes I/O Throughput
|
||||
Queries Average duration
|
||||
Simultaneous sessions
|
||||
Histogram of sessions times
|
||||
Sessions per database
|
||||
Sessions per user
|
||||
Sessions per host
|
||||
Established connections
|
||||
Connections per database
|
||||
Connections per user
|
||||
Connections per host
|
||||
Most used reserved pools
|
||||
Most Frequent Errors/Events
|
||||
|
||||
You can also have incremental reports with one report per day and a cumulative
|
||||
report per week. Two multiprocess modes are available to speed up log parsing,
|
||||
one using one core per log file, and the second using multiple cores to parse
|
||||
a single file. These modes can be combined.
|
||||
|
||||
Histogram granularity can be adjusted using the -A command line option. By default
|
||||
they will report the mean of each top queries/errors occurring per hour, but you can
|
||||
specify the granularity down to the minute.
|
||||
|
||||
pgBadger can also be used in a central place to parse remote log files using a
|
||||
passwordless SSH connection. This mode can be used with compressed files and in
|
||||
the multiprocess per file mode (-J) but can not be used with the CSV log format.
|
||||
|
||||
|
||||
=head1 REQUIREMENT
|
||||
|
||||
pgBadger comes as a single Perl script - you do not need anything other than a modern
|
||||
Perl distribution. Charts are rendered using a JavaScript library so you don't need
|
||||
anything other than a web browser. Your browser will do all the work.
|
||||
|
||||
If you planned to parse PostgreSQL CSV log files you might need some Perl Modules:
|
||||
|
||||
Text::CSV_XS - to parse PostgreSQL CSV log files.
|
||||
|
||||
This module is optional, if you don't have PostgreSQL log in the CSV format you don't
|
||||
need to install it.
|
||||
|
||||
If you want to export statistics as JSON file you need an additional Perl module:
|
||||
|
||||
JSON::XS - JSON serialising/deserialising, done correctly and fast
|
||||
|
||||
This module is optional, if you don't select the json output format you don't
|
||||
need to install it. You can install it on a Debian like system using:
|
||||
|
||||
sudo apt-get install libjson-xs-perl
|
||||
|
||||
and in RPM like system using:
|
||||
|
||||
sudo yum install perl-JSON-XS
|
||||
|
||||
Compressed log file format is autodetected from the file extension. If pgBadger find
|
||||
a gz extension it will use the zcat utility, with a bz2 extension it will use bzcat
|
||||
and if the file extension is zip or xz then the unzip or xz utilities will be used.
|
||||
|
||||
If those utilities are not found in the PATH environment variable then use the --zcat
|
||||
command line option to change this path. For example:
|
||||
|
||||
--zcat="/usr/local/bin/gunzip -c" or --zcat="/usr/local/bin/bzip2 -dc"
|
||||
--zcat="C:\tools\unzip -p"
|
||||
|
||||
By default pgBadger will use the zcat, bzcat and unzip utilities following the
|
||||
file extension. If you use the default autodetection compress format you can
|
||||
mixed gz, bz2, xz or zip files. Specifying a custom value to --zcat option will
|
||||
remove this feature of mixed compressed format.
|
||||
|
||||
Note that multiprocessing can not be used with compressed files or CSV files as
|
||||
well as under Windows platform.
|
||||
|
||||
=head1 INSTALLATION
|
||||
|
||||
Download the tarball from GitHub and unpack the archive as follow:
|
||||
|
||||
tar xzf pgbadger-11.x.tar.gz
|
||||
cd pgbadger-11.x/
|
||||
perl Makefile.PL
|
||||
make && sudo make install
|
||||
|
||||
This will copy the Perl script pgbadger to /usr/local/bin/pgbadger by default and the
|
||||
man page into /usr/local/share/man/man1/pgbadger.1. Those are the default installation
|
||||
directories for 'site' install.
|
||||
|
||||
If you want to install all under /usr/ location, use INSTALLDIRS='perl' as an argument
|
||||
of Makefile.PL. The script will be installed into /usr/bin/pgbadger and the manpage
|
||||
into /usr/share/man/man1/pgbadger.1.
|
||||
|
||||
For example, to install everything just like Debian does, proceed as follows:
|
||||
|
||||
perl Makefile.PL INSTALLDIRS=vendor
|
||||
|
||||
By default INSTALLDIRS is set to site.
|
||||
|
||||
=head1 POSTGRESQL CONFIGURATION
|
||||
|
||||
You must enable and set some configuration directives in your postgresql.conf
|
||||
before starting.
|
||||
|
||||
You must first enable SQL query logging to have something to parse:
|
||||
|
||||
log_min_duration_statement = 0
|
||||
|
||||
Here every statement will be logged, on a busy server you may want to increase
|
||||
this value to only log queries with a longer duration. Note that if you have
|
||||
log_statement set to 'all' nothing will be logged through the log_min_duration_statement
|
||||
directive. See the next chapter for more information.
|
||||
|
||||
pgBadger supports any custom format set into the log_line_prefix directive of
|
||||
your postgresql.conf file as long as it at least specify a time escape sequence
|
||||
(%t, %m or %n) and the process related escape sequence (%p or %c).
|
||||
|
||||
For example, with 'stderr' log format, log_line_prefix must be at least:
|
||||
|
||||
log_line_prefix = '%t [%p]: '
|
||||
|
||||
Log line prefix could add user, database name, application name and client ip
|
||||
address as follows:
|
||||
|
||||
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '
|
||||
|
||||
or for syslog log file format:
|
||||
|
||||
log_line_prefix = 'user=%u,db=%d,app=%a,client=%h '
|
||||
|
||||
Log line prefix for stderr output could also be:
|
||||
|
||||
log_line_prefix = '%t [%p]: db=%d,user=%u,app=%a,client=%h '
|
||||
|
||||
or for syslog output:
|
||||
|
||||
log_line_prefix = 'db=%d,user=%u,app=%a,client=%h '
|
||||
|
||||
You need to enable other parameters in postgresql.conf to get more information from your log files:
|
||||
|
||||
log_checkpoints = on
|
||||
log_connections = on
|
||||
log_disconnections = on
|
||||
log_lock_waits = on
|
||||
log_temp_files = 0
|
||||
log_autovacuum_min_duration = 0
|
||||
log_error_verbosity = default
|
||||
|
||||
Do not enable log_statement as its log format will not be parsed by pgBadger.
|
||||
|
||||
Of course your log messages should be in English without locale support:
|
||||
|
||||
lc_messages='C'
|
||||
|
||||
but this is not only recommended by pgBadger.
|
||||
|
||||
=head1 LOG STATEMENTS
|
||||
|
||||
Considerations about log_min_duration_statement, log_duration and log_statement
|
||||
configuration directives.
|
||||
|
||||
If you want the query statistics to include the actual query strings, you
|
||||
must set log_min_duration_statement to 0 or more milliseconds.
|
||||
|
||||
If you just want to report duration and number of queries and don't want all
|
||||
details about queries, set log_min_duration_statement to -1 to disable it and
|
||||
enable log_duration in your postgresql.conf file. If you want to add the most
|
||||
common request report you can either choose to set log_min_duration_statement
|
||||
to a higher value or choose to enable log_statement.
|
||||
|
||||
Enabling log_min_duration_statement will add reports about slowest queries and
|
||||
queries that took up the most time. Take care that if you have log_statement
|
||||
set to 'all' nothing will be logged with log_min_duration_statement.
|
||||
|
||||
Warning: Do not enable both log_min_duration_statement, log_duration and
|
||||
log_statement all together, this will result in wrong counter values. Note
|
||||
that this will also increase drastically the size of your log.
|
||||
log_min_duration_statement should always be preferred.
|
||||
|
||||
=head1 PARALLEL PROCESSING
|
||||
|
||||
To enable parallel processing you just have to use the -j N option where N is
|
||||
the number of cores you want to use.
|
||||
|
||||
pgBadger will then proceed as follow:
|
||||
|
||||
for each log file
|
||||
chunk size = int(file size / N)
|
||||
look at start/end offsets of these chunks
|
||||
fork N processes and seek to the start offset of each chunk
|
||||
each process will terminate when the parser reach the end offset
|
||||
of its chunk
|
||||
each process write stats into a binary temporary file
|
||||
wait for all children has terminated
|
||||
All binary temporary files generated will then be read and loaded into
|
||||
memory to build the html output.
|
||||
|
||||
With that method, at start/end of chunks pgBadger may truncate or omit a
|
||||
maximum of N queries per log file which is an insignificant gap if you have
|
||||
millions of queries in your log file. The chance that the query that you were
|
||||
looking for is lost is near 0, this is why I think this gap is livable. Most
|
||||
of the time the query is counted twice but truncated.
|
||||
|
||||
When you have many small log files and many CPUs it is speedier to dedicate
|
||||
one core to one log file at a time. To enable this behavior you have to use
|
||||
option -J N instead. With 200 log files of 10MB each the use of the -J option
|
||||
starts being really interesting with 8 Cores. Using this method you will be
|
||||
sure not to lose any queries in the reports.
|
||||
|
||||
He are a benchmark done on a server with 8 CPUs and a single file of 9.5GB.
|
||||
|
||||
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
|
||||
--------+---------+-------+-------+------
|
||||
-j | 1h41m18 | 50m25 | 25m39 | 15m58
|
||||
-J | 1h41m18 | 54m28 | 41m16 | 34m45
|
||||
|
||||
With 200 log files of 10MB each and a total of 2GB the results are slightly
|
||||
different:
|
||||
|
||||
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
|
||||
--------+-------+-------+-------+------
|
||||
-j | 20m15 | 9m56 | 5m20 | 4m20
|
||||
-J | 20m15 | 9m49 | 5m00 | 2m40
|
||||
|
||||
So it is recommended to use -j unless you have hundreds of small log files
|
||||
and can use at least 8 CPUs.
|
||||
|
||||
IMPORTANT: when you are using parallel parsing pgBadger will generate a
|
||||
lot of temporary files in the /tmp directory and will remove them at the
|
||||
end, so do not remove those files unless pgBadger is not running. They are
|
||||
all named with the following template tmp_pgbadgerXXXX.bin so they can be
|
||||
easily identified.
|
||||
|
||||
=head1 INCREMENTAL REPORTS
|
||||
|
||||
pgBadger includes an automatic incremental report mode using option -I or
|
||||
--incremental. When running in this mode, pgBadger will generate one report
|
||||
per day and a cumulative report per week. Output is first done in binary
|
||||
format into the mandatory output directory (see option -O or --outdir),
|
||||
then in HTML format for daily and weekly reports with a main index file.
|
||||
|
||||
The main index file will show a dropdown menu per week with a link to each
|
||||
week report and links to daily reports of each week.
|
||||
|
||||
For example, if you run pgBadger as follows based on a daily rotated file:
|
||||
|
||||
0 4 * * * /usr/bin/pgbadger -I -q /var/log/postgresql/postgresql.log.1 -O /var/www/pg_reports/
|
||||
|
||||
you will have all daily and weekly reports for the full running period.
|
||||
|
||||
In this mode pgBadger will create an automatic incremental file in the
|
||||
output directory, so you don't have to use the -l option unless you want
|
||||
to change the path of that file. This means that you can run pgBadger in
|
||||
this mode each day on a log file rotated each week, and it will not count
|
||||
the log entries twice.
|
||||
|
||||
To save disk space you may want to use the -X or --extra-files command line
|
||||
option to force pgBadger to write JavaScript and CSS to separate files in
|
||||
the output directory. The resources will then be loaded using script and
|
||||
link tags.
|
||||
|
||||
=head2 Rebuilding reports
|
||||
|
||||
Incremental reports can be rebuilt after a pgbadger report fix or a new
|
||||
feature to update all HTML reports. To rebuild all reports where a binary
|
||||
file is still present proceed as follow:
|
||||
|
||||
rm /path/to/reports/*.js
|
||||
rm /path/to/reports/*.css
|
||||
pgbadger -X -I -O /path/to/reports/ --rebuild
|
||||
|
||||
it will also update all resource files (JS and CSS). Use -E or --explode
|
||||
if the reports were built using this option.
|
||||
|
||||
=head2 Monthly reports
|
||||
|
||||
By default pgBadger in incremental mode only compute daily and weekly reports.
|
||||
If you want monthly cumulative reports you will have to use a separate command
|
||||
to specify the report to build. For example to build a report for August 2019:
|
||||
|
||||
pgbadger -X --month-report 2919-08 /var/www/pg_reports/
|
||||
|
||||
this will add a link to the month name into the calendar view of incremental
|
||||
reports to look at monthly report. The report for a current month can be run
|
||||
every day it is entirely rebuilt each time. The monthly report is not built by
|
||||
default because it could take lot of time following the amount of data.
|
||||
|
||||
If reports were built with the per database option ( -E | --explode ) it must
|
||||
be used too when calling pgbadger to build monthly report:
|
||||
|
||||
pgbadger -E -X --month-report 2919-08 /var/www/pg_reports/
|
||||
|
||||
This is the same when using the rebuild option ( -R | --rebuild ).
|
||||
|
||||
=head1 BINARY FORMAT
|
||||
|
||||
Using the binary format it is possible to create custom incremental and
|
||||
cumulative reports. For example, if you want to refresh a pgBadger
|
||||
report each hour from a daily PostgreSQL log file, you can proceed by
|
||||
running each hour the following commands:
|
||||
|
||||
pgbadger --last-parsed .pgbadger_last_state_file -o sunday/hourX.bin /var/log/pgsql/postgresql-Sun.log
|
||||
|
||||
to generate the incremental data files in binary format. And to generate the fresh HTML
|
||||
report from that binary file:
|
||||
|
||||
pgbadger sunday/*.bin
|
||||
|
||||
Or as another example, if you generate one log file per hour and you want
|
||||
reports to be rebuilt each time the log file is rotated, proceed as
|
||||
follows:
|
||||
|
||||
pgbadger -o day1/hour01.bin /var/log/pgsql/pglog/postgresql-2012-03-23_10.log
|
||||
pgbadger -o day1/hour02.bin /var/log/pgsql/pglog/postgresql-2012-03-23_11.log
|
||||
pgbadger -o day1/hour03.bin /var/log/pgsql/pglog/postgresql-2012-03-23_12.log
|
||||
...
|
||||
|
||||
When you want to refresh the HTML report, for example each time after a new binary file
|
||||
is generated, just do the following:
|
||||
|
||||
pgbadger -o day1_report.html day1/*.bin
|
||||
|
||||
Adjust the commands to suit your particular needs.
|
||||
|
||||
=head1 JSON FORMAT
|
||||
|
||||
JSON format is good for sharing data with other languages, which makes it
|
||||
easy to integrate pgBadger result into other monitoring tools like Cacti
|
||||
or Graphite.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
pgBadger is an original work from Gilles Darold.
|
||||
|
||||
The pgBadger logo is an original creation of Damien Cazeils.
|
||||
|
||||
The pgBadger v4.x design comes from the "Art is code" company.
|
||||
|
||||
This web site is a work of Gilles Darold.
|
||||
|
||||
pgBadger is maintained by Gilles Darold and every one who wants to contribute.
|
||||
|
||||
Many people have contributed to pgBadger, they are all quoted in the Changelog file.
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
pgBadger is free software distributed under the PostgreSQL Licence.
|
||||
|
||||
Copyright (c) 2012-2019, Gilles Darold
|
||||
|
||||
A modified version of the SQL::Beautify Perl Module is embedded in pgBadger
|
||||
with copyright (C) 2009 by Jonas Kramer and is published under the terms of
|
||||
the Artistic License 2.0.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
min/
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
Licenses
|
||||
--------
|
||||
|
||||
bean.js:
|
||||
copyright (c) Jacob Thornton 2011-2012
|
||||
* https://github.com/fat/bean
|
||||
* MIT license
|
||||
|
||||
bootstrap.*:
|
||||
Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under the MIT license
|
||||
|
||||
fontawesome.*:
|
||||
* - The Font Awesome font is licensed under SIL OFL 1.1 -
|
||||
* http://scripts.sil.org/OFL
|
||||
* - Font Awesome CSS, LESS, and SASS files are licensed under MIT License -
|
||||
* http://opensource.org/licenses/mit-license.html
|
||||
* - Attribution is no longer required in Font Awesome 3.0, but much appreciated:
|
||||
* "Font Awesome by Dave Gandy - http://fontawesome.io"
|
||||
|
||||
jqplot.*:
|
||||
Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
|
||||
jquery.jqplot.*:
|
||||
Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT and GPL version 2.0 licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* See <GPL Version 2> and <MIT License> contained within this distribution for further information.
|
||||
*
|
||||
* The author would appreciate an email letting him know of any substantial
|
||||
* use of jqPlot. You can reach the author at: chris at jqplot dot com
|
||||
* or see http://www.jqplot.com/info.php. This is, of course, not required.
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php.
|
||||
|
||||
jquery.*:
|
||||
Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
|
||||
* Released under the MIT license
|
||||
* http://jquery.org/license
|
||||
|
||||
underscore.js:
|
||||
(c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
// Underscore may be freely distributed under the MIT license.
|
||||
|
||||
|
||||
The MIT License (MIT)
|
||||
---------------------
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
----------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
Resources files are collected from their respective download places as follow:
|
||||
|
||||
jqPlot:
|
||||
-------
|
||||
|
||||
mkdir jqPlot-1.0.8/ && cd jqPlot-1.0.8/
|
||||
wget http://www.jqplot.com/download/jquery.jqplot.1.0.8r1250.tar.gz
|
||||
tar xzf jquery.jqplot.1.0.8r1250.tar.gz
|
||||
cp dist/jquery.jqplot.js /home/git/pgbadger/resources/
|
||||
cp dist/jquery.jqplot.css /home/git/pgbadger/resources/
|
||||
cp dist/jquery.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.pieRenderer.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.barRenderer.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.dateAxisRenderer.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.canvasTextRenderer.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.categoryAxisRenderer.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.canvasAxisTickRenderer.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.highlighter.js /home/git/pgbadger/resources/
|
||||
cp dist/plugins/jqplot.cursor.js /home/git/pgbadger/resources/
|
||||
cd ..
|
||||
|
||||
Bootstrap:
|
||||
----------
|
||||
|
||||
wget https://github.com/twbs/bootstrap/archive/v3.3.7.zip
|
||||
unzip v3.3.7.zip bootstrap-3.3.7/dist/js/bootstrap.js bootstrap-3.3.7/dist/css/bootstrap.css
|
||||
|
||||
cp bootstrap-3.3.7/dist/js/bootstrap.js /home/git/pgbadger/resources/
|
||||
cp bootstrap-3.3.7/dist/css/bootstrap.css /home/git/pgbadger/resources/
|
||||
|
||||
Fontawesome:
|
||||
------------
|
||||
http://fontawesome.io/3.2.1/assets/font-awesome.zip
|
||||
unzip font-awesome.zip font-awesome/css/font-awesome.css font-awesome/fonts/fontawesome-webfont.ttf
|
||||
|
||||
cp font-awesome/css/font-awesome.css /home/git/pgbadger/resources/fontawesome.css
|
||||
cp font-awesome/font/FontAwesome.otf /home/git/pgbadger/resources/font/
|
||||
|
||||
Note that the hyphen is removed from font-awesome.css during the copy for
|
||||
backward compatibility.
|
||||
|
||||
bean:
|
||||
-----
|
||||
|
||||
wget https://github.com/fat/bean/archive/v1.0.14.tar.gz
|
||||
tar xzf v1.0.14.tar.gz bean-1.0.14/src/bean.js
|
||||
|
||||
cp bean-1.0.14/src/bean.js /home/git/pgbadger/resources/
|
||||
|
||||
underscore.js:
|
||||
--------------
|
||||
|
||||
wget http://underscorejs.org/underscore.js -O /home/git/pgbadger/resources/underscore.js
|
||||
|
||||
Then file are minified using yui-compressor and the font embedded using the
|
||||
Perl script tools/updt_embedded_rsc.pl This script also embedded all minified
|
||||
files into the pgbadger Perl script.
|
||||
|
||||
|
|
@ -0,0 +1,741 @@
|
|||
/*!
|
||||
* Bean - copyright (c) Jacob Thornton 2011-2012
|
||||
* https://github.com/fat/bean
|
||||
* MIT license
|
||||
*/
|
||||
(function (name, context, definition) {
|
||||
if (typeof module != 'undefined' && module.exports) module.exports = definition()
|
||||
else if (typeof define == 'function' && define.amd) define(definition)
|
||||
else context[name] = definition()
|
||||
})('bean', this, function (name, context) {
|
||||
name = name || 'bean'
|
||||
context = context || this
|
||||
|
||||
var win = window
|
||||
, old = context[name]
|
||||
, namespaceRegex = /[^\.]*(?=\..*)\.|.*/
|
||||
, nameRegex = /\..*/
|
||||
, addEvent = 'addEventListener'
|
||||
, removeEvent = 'removeEventListener'
|
||||
, doc = document || {}
|
||||
, root = doc.documentElement || {}
|
||||
, W3C_MODEL = root[addEvent]
|
||||
, eventSupport = W3C_MODEL ? addEvent : 'attachEvent'
|
||||
, ONE = {} // singleton for quick matching making add() do one()
|
||||
|
||||
, slice = Array.prototype.slice
|
||||
, str2arr = function (s, d) { return s.split(d || ' ') }
|
||||
, isString = function (o) { return typeof o == 'string' }
|
||||
, isFunction = function (o) { return typeof o == 'function' }
|
||||
|
||||
// events that we consider to be 'native', anything not in this list will
|
||||
// be treated as a custom event
|
||||
, standardNativeEvents =
|
||||
'click dblclick mouseup mousedown contextmenu ' + // mouse buttons
|
||||
'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel
|
||||
'mouseover mouseout mousemove selectstart selectend ' + // mouse movement
|
||||
'keydown keypress keyup ' + // keyboard
|
||||
'orientationchange ' + // mobile
|
||||
'focus blur change reset select submit ' + // form elements
|
||||
'load unload beforeunload resize move DOMContentLoaded ' + // window
|
||||
'readystatechange message ' + // window
|
||||
'error abort scroll ' // misc
|
||||
// element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
|
||||
// that doesn't actually exist, so make sure we only do these on newer browsers
|
||||
, w3cNativeEvents =
|
||||
'show ' + // mouse buttons
|
||||
'input invalid ' + // form elements
|
||||
'touchstart touchmove touchend touchcancel ' + // touch
|
||||
'gesturestart gesturechange gestureend ' + // gesture
|
||||
'textinput ' + // TextEvent
|
||||
'readystatechange pageshow pagehide popstate ' + // window
|
||||
'hashchange offline online ' + // window
|
||||
'afterprint beforeprint ' + // printing
|
||||
'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd
|
||||
'loadstart progress suspend emptied stalled loadmetadata ' + // media
|
||||
'loadeddata canplay canplaythrough playing waiting seeking ' + // media
|
||||
'seeked ended durationchange timeupdate play pause ratechange ' + // media
|
||||
'volumechange cuechange ' + // media
|
||||
'checking noupdate downloading cached updateready obsolete ' // appcache
|
||||
|
||||
// convert to a hash for quick lookups
|
||||
, nativeEvents = (function (hash, events, i) {
|
||||
for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1)
|
||||
return hash
|
||||
}({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : ''))))
|
||||
|
||||
// custom events are events that we *fake*, they are not provided natively but
|
||||
// we can use native events to generate them
|
||||
, customEvents = (function () {
|
||||
var isAncestor = 'compareDocumentPosition' in root
|
||||
? function (element, container) {
|
||||
return container.compareDocumentPosition && (container.compareDocumentPosition(element) & 16) === 16
|
||||
}
|
||||
: 'contains' in root
|
||||
? function (element, container) {
|
||||
container = container.nodeType === 9 || container === window ? root : container
|
||||
return container !== element && container.contains(element)
|
||||
}
|
||||
: function (element, container) {
|
||||
while (element = element.parentNode) if (element === container) return 1
|
||||
return 0
|
||||
}
|
||||
, check = function (event) {
|
||||
var related = event.relatedTarget
|
||||
return !related
|
||||
? related == null
|
||||
: (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString())
|
||||
&& !isAncestor(related, this))
|
||||
}
|
||||
|
||||
return {
|
||||
mouseenter: { base: 'mouseover', condition: check }
|
||||
, mouseleave: { base: 'mouseout', condition: check }
|
||||
, mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
|
||||
}
|
||||
}())
|
||||
|
||||
// we provide a consistent Event object across browsers by taking the actual DOM
|
||||
// event object and generating a new one from its properties.
|
||||
, Event = (function () {
|
||||
// a whitelist of properties (for different event types) tells us what to check for and copy
|
||||
var commonProps = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' +
|
||||
'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey ' +
|
||||
'srcElement target timeStamp type view which propertyName')
|
||||
, mouseProps = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer ' +
|
||||
'fromElement offsetX offsetY pageX pageY screenX screenY toElement'))
|
||||
, mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' +
|
||||
'axis')) // 'axis' is FF specific
|
||||
, keyProps = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier ' +
|
||||
'keyLocation location'))
|
||||
, textProps = commonProps.concat(str2arr('data'))
|
||||
, touchProps = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation'))
|
||||
, messageProps = commonProps.concat(str2arr('data origin source'))
|
||||
, stateProps = commonProps.concat(str2arr('state'))
|
||||
, overOutRegex = /over|out/
|
||||
// some event types need special handling and some need special properties, do that all here
|
||||
, typeFixers = [
|
||||
{ // key events
|
||||
reg: /key/i
|
||||
, fix: function (event, newEvent) {
|
||||
newEvent.keyCode = event.keyCode || event.which
|
||||
return keyProps
|
||||
}
|
||||
}
|
||||
, { // mouse events
|
||||
reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i
|
||||
, fix: function (event, newEvent, type) {
|
||||
newEvent.rightClick = event.which === 3 || event.button === 2
|
||||
newEvent.pos = { x: 0, y: 0 }
|
||||
if (event.pageX || event.pageY) {
|
||||
newEvent.clientX = event.pageX
|
||||
newEvent.clientY = event.pageY
|
||||
} else if (event.clientX || event.clientY) {
|
||||
newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
|
||||
newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
|
||||
}
|
||||
if (overOutRegex.test(type)) {
|
||||
newEvent.relatedTarget = event.relatedTarget
|
||||
|| event[(type == 'mouseover' ? 'from' : 'to') + 'Element']
|
||||
}
|
||||
return mouseProps
|
||||
}
|
||||
}
|
||||
, { // mouse wheel events
|
||||
reg: /mouse.*(wheel|scroll)/i
|
||||
, fix: function () { return mouseWheelProps }
|
||||
}
|
||||
, { // TextEvent
|
||||
reg: /^text/i
|
||||
, fix: function () { return textProps }
|
||||
}
|
||||
, { // touch and gesture events
|
||||
reg: /^touch|^gesture/i
|
||||
, fix: function () { return touchProps }
|
||||
}
|
||||
, { // message events
|
||||
reg: /^message$/i
|
||||
, fix: function () { return messageProps }
|
||||
}
|
||||
, { // popstate events
|
||||
reg: /^popstate$/i
|
||||
, fix: function () { return stateProps }
|
||||
}
|
||||
, { // everything else
|
||||
reg: /.*/
|
||||
, fix: function () { return commonProps }
|
||||
}
|
||||
]
|
||||
, typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism
|
||||
|
||||
, Event = function (event, element, isNative) {
|
||||
if (!arguments.length) return
|
||||
event = event || ((element.ownerDocument || element.document || element).parentWindow || win).event
|
||||
this.originalEvent = event
|
||||
this.isNative = isNative
|
||||
this.isBean = true
|
||||
|
||||
if (!event) return
|
||||
|
||||
var type = event.type
|
||||
, target = event.target || event.srcElement
|
||||
, i, l, p, props, fixer
|
||||
|
||||
this.target = target && target.nodeType === 3 ? target.parentNode : target
|
||||
|
||||
if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless
|
||||
fixer = typeFixerMap[type]
|
||||
if (!fixer) { // haven't encountered this event type before, map a fixer function for it
|
||||
for (i = 0, l = typeFixers.length; i < l; i++) {
|
||||
if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .*
|
||||
typeFixerMap[type] = fixer = typeFixers[i].fix
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
props = fixer(event, this, type)
|
||||
for (i = props.length; i--;) {
|
||||
if (!((p = props[i]) in this) && p in event) this[p] = event[p]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preventDefault() and stopPropagation() are a consistent interface to those functions
|
||||
// on the DOM, stop() is an alias for both of them together
|
||||
Event.prototype.preventDefault = function () {
|
||||
if (this.originalEvent.preventDefault) this.originalEvent.preventDefault()
|
||||
else this.originalEvent.returnValue = false
|
||||
}
|
||||
Event.prototype.stopPropagation = function () {
|
||||
if (this.originalEvent.stopPropagation) this.originalEvent.stopPropagation()
|
||||
else this.originalEvent.cancelBubble = true
|
||||
}
|
||||
Event.prototype.stop = function () {
|
||||
this.preventDefault()
|
||||
this.stopPropagation()
|
||||
this.stopped = true
|
||||
}
|
||||
// stopImmediatePropagation() has to be handled internally because we manage the event list for
|
||||
// each element
|
||||
// note that originalElement may be a Bean#Event object in some situations
|
||||
Event.prototype.stopImmediatePropagation = function () {
|
||||
if (this.originalEvent.stopImmediatePropagation) this.originalEvent.stopImmediatePropagation()
|
||||
this.isImmediatePropagationStopped = function () { return true }
|
||||
}
|
||||
Event.prototype.isImmediatePropagationStopped = function () {
|
||||
return this.originalEvent.isImmediatePropagationStopped && this.originalEvent.isImmediatePropagationStopped()
|
||||
}
|
||||
Event.prototype.clone = function (currentTarget) {
|
||||
//TODO: this is ripe for optimisation, new events are *expensive*
|
||||
// improving this will speed up delegated events
|
||||
var ne = new Event(this, this.element, this.isNative)
|
||||
ne.currentTarget = currentTarget
|
||||
return ne
|
||||
}
|
||||
|
||||
return Event
|
||||
}())
|
||||
|
||||
// if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
|
||||
, targetElement = function (element, isNative) {
|
||||
return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean maintains an internal registry for event listeners. We don't touch elements, objects
|
||||
* or functions to identify them, instead we store everything in the registry.
|
||||
* Each event listener has a RegEntry object, we have one 'registry' for the whole instance.
|
||||
*/
|
||||
, RegEntry = (function () {
|
||||
// each handler is wrapped so we can handle delegation and custom events
|
||||
var wrappedHandler = function (element, fn, condition, args) {
|
||||
var call = function (event, eargs) {
|
||||
return fn.apply(element, args ? slice.call(eargs, event ? 0 : 1).concat(args) : eargs)
|
||||
}
|
||||
, findTarget = function (event, eventElement) {
|
||||
return fn.__beanDel ? fn.__beanDel.ft(event.target, element) : eventElement
|
||||
}
|
||||
, handler = condition
|
||||
? function (event) {
|
||||
var target = findTarget(event, this) // deleated event
|
||||
if (condition.apply(target, arguments)) {
|
||||
if (event) event.currentTarget = target
|
||||
return call(event, arguments)
|
||||
}
|
||||
}
|
||||
: function (event) {
|
||||
if (fn.__beanDel) event = event.clone(findTarget(event)) // delegated event, fix the fix
|
||||
return call(event, arguments)
|
||||
}
|
||||
handler.__beanDel = fn.__beanDel
|
||||
return handler
|
||||
}
|
||||
|
||||
, RegEntry = function (element, type, handler, original, namespaces, args, root) {
|
||||
var customType = customEvents[type]
|
||||
, isNative
|
||||
|
||||
if (type == 'unload') {
|
||||
// self clean-up
|
||||
handler = once(removeListener, element, type, handler, original)
|
||||
}
|
||||
|
||||
if (customType) {
|
||||
if (customType.condition) {
|
||||
handler = wrappedHandler(element, handler, customType.condition, args)
|
||||
}
|
||||
type = customType.base || type
|
||||
}
|
||||
|
||||
this.isNative = isNative = nativeEvents[type] && !!element[eventSupport]
|
||||
this.customType = !W3C_MODEL && !isNative && type
|
||||
this.element = element
|
||||
this.type = type
|
||||
this.original = original
|
||||
this.namespaces = namespaces
|
||||
this.eventType = W3C_MODEL || isNative ? type : 'propertychange'
|
||||
this.target = targetElement(element, isNative)
|
||||
this[eventSupport] = !!this.target[eventSupport]
|
||||
this.root = root
|
||||
this.handler = wrappedHandler(element, handler, null, args)
|
||||
}
|
||||
|
||||
// given a list of namespaces, is our entry in any of them?
|
||||
RegEntry.prototype.inNamespaces = function (checkNamespaces) {
|
||||
var i, j, c = 0
|
||||
if (!checkNamespaces) return true
|
||||
if (!this.namespaces) return false
|
||||
for (i = checkNamespaces.length; i--;) {
|
||||
for (j = this.namespaces.length; j--;) {
|
||||
if (checkNamespaces[i] == this.namespaces[j]) c++
|
||||
}
|
||||
}
|
||||
return checkNamespaces.length === c
|
||||
}
|
||||
|
||||
// match by element, original fn (opt), handler fn (opt)
|
||||
RegEntry.prototype.matches = function (checkElement, checkOriginal, checkHandler) {
|
||||
return this.element === checkElement &&
|
||||
(!checkOriginal || this.original === checkOriginal) &&
|
||||
(!checkHandler || this.handler === checkHandler)
|
||||
}
|
||||
|
||||
return RegEntry
|
||||
}())
|
||||
|
||||
, registry = (function () {
|
||||
// our map stores arrays by event type, just because it's better than storing
|
||||
// everything in a single array.
|
||||
// uses '$' as a prefix for the keys for safety and 'r' as a special prefix for
|
||||
// rootListeners so we can look them up fast
|
||||
var map = {}
|
||||
|
||||
// generic functional search of our registry for matching listeners,
|
||||
// `fn` returns false to break out of the loop
|
||||
, forAll = function (element, type, original, handler, root, fn) {
|
||||
var pfx = root ? 'r' : '$'
|
||||
if (!type || type == '*') {
|
||||
// search the whole registry
|
||||
for (var t in map) {
|
||||
if (t.charAt(0) == pfx) {
|
||||
forAll(element, t.substr(1), original, handler, root, fn)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var i = 0, l, list = map[pfx + type], all = element == '*'
|
||||
if (!list) return
|
||||
for (l = list.length; i < l; i++) {
|
||||
if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
, has = function (element, type, original, root) {
|
||||
// we're not using forAll here simply because it's a bit slower and this
|
||||
// needs to be fast
|
||||
var i, list = map[(root ? 'r' : '$') + type]
|
||||
if (list) {
|
||||
for (i = list.length; i--;) {
|
||||
if (!list[i].root && list[i].matches(element, original, null)) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
, get = function (element, type, original, root) {
|
||||
var entries = []
|
||||
forAll(element, type, original, null, root, function (entry) {
|
||||
return entries.push(entry)
|
||||
})
|
||||
return entries
|
||||
}
|
||||
|
||||
, put = function (entry) {
|
||||
var has = !entry.root && !this.has(entry.element, entry.type, null, false)
|
||||
, key = (entry.root ? 'r' : '$') + entry.type
|
||||
;(map[key] || (map[key] = [])).push(entry)
|
||||
return has
|
||||
}
|
||||
|
||||
, del = function (entry) {
|
||||
forAll(entry.element, entry.type, null, entry.handler, entry.root, function (entry, list, i) {
|
||||
list.splice(i, 1)
|
||||
entry.removed = true
|
||||
if (list.length === 0) delete map[(entry.root ? 'r' : '$') + entry.type]
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// dump all entries, used for onunload
|
||||
, entries = function () {
|
||||
var t, entries = []
|
||||
for (t in map) {
|
||||
if (t.charAt(0) == '$') entries = entries.concat(map[t])
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
return { has: has, get: get, put: put, del: del, entries: entries }
|
||||
}())
|
||||
|
||||
// we need a selector engine for delegated events, use querySelectorAll if it exists
|
||||
// but for older browsers we need Qwery, Sizzle or similar
|
||||
, selectorEngine
|
||||
, setSelectorEngine = function (e) {
|
||||
if (!arguments.length) {
|
||||
selectorEngine = doc.querySelectorAll
|
||||
? function (s, r) {
|
||||
return r.querySelectorAll(s)
|
||||
}
|
||||
: function () {
|
||||
throw new Error('Bean: No selector engine installed') // eeek
|
||||
}
|
||||
} else {
|
||||
selectorEngine = e
|
||||
}
|
||||
}
|
||||
|
||||
// we attach this listener to each DOM event that we need to listen to, only once
|
||||
// per event type per DOM element
|
||||
, rootListener = function (event, type) {
|
||||
if (!W3C_MODEL && type && event && event.propertyName != '_on' + type) return
|
||||
|
||||
var listeners = registry.get(this, type || event.type, null, false)
|
||||
, l = listeners.length
|
||||
, i = 0
|
||||
|
||||
event = new Event(event, this, true)
|
||||
if (type) event.type = type
|
||||
|
||||
// iterate through all handlers registered for this type, calling them unless they have
|
||||
// been removed by a previous handler or stopImmediatePropagation() has been called
|
||||
for (; i < l && !event.isImmediatePropagationStopped(); i++) {
|
||||
if (!listeners[i].removed) listeners[i].handler.call(this, event)
|
||||
}
|
||||
}
|
||||
|
||||
// add and remove listeners to DOM elements
|
||||
, listener = W3C_MODEL
|
||||
? function (element, type, add) {
|
||||
// new browsers
|
||||
element[add ? addEvent : removeEvent](type, rootListener, false)
|
||||
}
|
||||
: function (element, type, add, custom) {
|
||||
// IE8 and below, use attachEvent/detachEvent and we have to piggy-back propertychange events
|
||||
// to simulate event bubbling etc.
|
||||
var entry
|
||||
if (add) {
|
||||
registry.put(entry = new RegEntry(
|
||||
element
|
||||
, custom || type
|
||||
, function (event) { // handler
|
||||
rootListener.call(element, event, custom)
|
||||
}
|
||||
, rootListener
|
||||
, null
|
||||
, null
|
||||
, true // is root
|
||||
))
|
||||
if (custom && element['_on' + custom] == null) element['_on' + custom] = 0
|
||||
entry.target.attachEvent('on' + entry.eventType, entry.handler)
|
||||
} else {
|
||||
entry = registry.get(element, custom || type, rootListener, true)[0]
|
||||
if (entry) {
|
||||
entry.target.detachEvent('on' + entry.eventType, entry.handler)
|
||||
registry.del(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
, once = function (rm, element, type, fn, originalFn) {
|
||||
// wrap the handler in a handler that does a remove as well
|
||||
return function () {
|
||||
fn.apply(this, arguments)
|
||||
rm(element, type, originalFn)
|
||||
}
|
||||
}
|
||||
|
||||
, removeListener = function (element, orgType, handler, namespaces) {
|
||||
var type = orgType && orgType.replace(nameRegex, '')
|
||||
, handlers = registry.get(element, type, null, false)
|
||||
, removed = {}
|
||||
, i, l
|
||||
|
||||
for (i = 0, l = handlers.length; i < l; i++) {
|
||||
if ((!handler || handlers[i].original === handler) && handlers[i].inNamespaces(namespaces)) {
|
||||
// TODO: this is problematic, we have a registry.get() and registry.del() that
|
||||
// both do registry searches so we waste cycles doing this. Needs to be rolled into
|
||||
// a single registry.forAll(fn) that removes while finding, but the catch is that
|
||||
// we'll be splicing the arrays that we're iterating over. Needs extra tests to
|
||||
// make sure we don't screw it up. @rvagg
|
||||
registry.del(handlers[i])
|
||||
if (!removed[handlers[i].eventType] && handlers[i][eventSupport])
|
||||
removed[handlers[i].eventType] = { t: handlers[i].eventType, c: handlers[i].type }
|
||||
}
|
||||
}
|
||||
// check each type/element for removed listeners and remove the rootListener where it's no longer needed
|
||||
for (i in removed) {
|
||||
if (!registry.has(element, removed[i].t, null, false)) {
|
||||
// last listener of this type, remove the rootListener
|
||||
listener(element, removed[i].t, false, removed[i].c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set up a delegate helper using the given selector, wrap the handler function
|
||||
, delegate = function (selector, fn) {
|
||||
//TODO: findTarget (therefore $) is called twice, once for match and once for
|
||||
// setting e.currentTarget, fix this so it's only needed once
|
||||
var findTarget = function (target, root) {
|
||||
var i, array = isString(selector) ? selectorEngine(selector, root) : selector
|
||||
for (; target && target !== root; target = target.parentNode) {
|
||||
for (i = array.length; i--;) {
|
||||
if (array[i] === target) return target
|
||||
}
|
||||
}
|
||||
}
|
||||
, handler = function (e) {
|
||||
var match = findTarget(e.target, this)
|
||||
if (match) fn.apply(match, arguments)
|
||||
}
|
||||
|
||||
// __beanDel isn't pleasant but it's a private function, not exposed outside of Bean
|
||||
handler.__beanDel = {
|
||||
ft : findTarget // attach it here for customEvents to use too
|
||||
, selector : selector
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
, fireListener = W3C_MODEL ? function (isNative, type, element) {
|
||||
// modern browsers, do a proper dispatchEvent()
|
||||
var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
|
||||
evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
|
||||
element.dispatchEvent(evt)
|
||||
} : function (isNative, type, element) {
|
||||
// old browser use onpropertychange, just increment a custom property to trigger the event
|
||||
element = targetElement(element, isNative)
|
||||
isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API: off(), on(), add(), (remove()), one(), fire(), clone()
|
||||
*/
|
||||
|
||||
/**
|
||||
* off(element[, eventType(s)[, handler ]])
|
||||
*/
|
||||
, off = function (element, typeSpec, fn) {
|
||||
var isTypeStr = isString(typeSpec)
|
||||
, k, type, namespaces, i
|
||||
|
||||
if (isTypeStr && typeSpec.indexOf(' ') > 0) {
|
||||
// off(el, 't1 t2 t3', fn) or off(el, 't1 t2 t3')
|
||||
typeSpec = str2arr(typeSpec)
|
||||
for (i = typeSpec.length; i--;)
|
||||
off(element, typeSpec[i], fn)
|
||||
return element
|
||||
}
|
||||
|
||||
type = isTypeStr && typeSpec.replace(nameRegex, '')
|
||||
if (type && customEvents[type]) type = customEvents[type].base
|
||||
|
||||
if (!typeSpec || isTypeStr) {
|
||||
// off(el) or off(el, t1.ns) or off(el, .ns) or off(el, .ns1.ns2.ns3)
|
||||
if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.')
|
||||
removeListener(element, type, fn, namespaces)
|
||||
} else if (isFunction(typeSpec)) {
|
||||
// off(el, fn)
|
||||
removeListener(element, null, typeSpec)
|
||||
} else {
|
||||
// off(el, { t1: fn1, t2, fn2 })
|
||||
for (k in typeSpec) {
|
||||
if (typeSpec.hasOwnProperty(k)) off(element, k, typeSpec[k])
|
||||
}
|
||||
}
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
/**
|
||||
* on(element, eventType(s)[, selector], handler[, args ])
|
||||
*/
|
||||
, on = function(element, events, selector, fn) {
|
||||
var originalFn, type, types, i, args, entry, first
|
||||
|
||||
//TODO: the undefined check means you can't pass an 'args' argument, fix this perhaps?
|
||||
if (selector === undefined && typeof events == 'object') {
|
||||
//TODO: this can't handle delegated events
|
||||
for (type in events) {
|
||||
if (events.hasOwnProperty(type)) {
|
||||
on.call(this, element, type, events[type])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!isFunction(selector)) {
|
||||
// delegated event
|
||||
originalFn = fn
|
||||
args = slice.call(arguments, 4)
|
||||
fn = delegate(selector, originalFn, selectorEngine)
|
||||
} else {
|
||||
args = slice.call(arguments, 3)
|
||||
fn = originalFn = selector
|
||||
}
|
||||
|
||||
types = str2arr(events)
|
||||
|
||||
// special case for one(), wrap in a self-removing handler
|
||||
if (this === ONE) {
|
||||
fn = once(off, element, events, fn, originalFn)
|
||||
}
|
||||
|
||||
for (i = types.length; i--;) {
|
||||
// add new handler to the registry and check if it's the first for this element/type
|
||||
first = registry.put(entry = new RegEntry(
|
||||
element
|
||||
, types[i].replace(nameRegex, '') // event type
|
||||
, fn
|
||||
, originalFn
|
||||
, str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces
|
||||
, args
|
||||
, false // not root
|
||||
))
|
||||
if (entry[eventSupport] && first) {
|
||||
// first event of this type on this element, add root listener
|
||||
listener(element, entry.eventType, true, entry.customType)
|
||||
}
|
||||
}
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
/**
|
||||
* add(element[, selector], eventType(s), handler[, args ])
|
||||
*
|
||||
* Deprecated: kept (for now) for backward-compatibility
|
||||
*/
|
||||
, add = function (element, events, fn, delfn) {
|
||||
return on.apply(
|
||||
null
|
||||
, !isString(fn)
|
||||
? slice.call(arguments)
|
||||
: [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : [])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* one(element, eventType(s)[, selector], handler[, args ])
|
||||
*/
|
||||
, one = function () {
|
||||
return on.apply(ONE, arguments)
|
||||
}
|
||||
|
||||
/**
|
||||
* fire(element, eventType(s)[, args ])
|
||||
*
|
||||
* The optional 'args' argument must be an array, if no 'args' argument is provided
|
||||
* then we can use the browser's DOM event system, otherwise we trigger handlers manually
|
||||
*/
|
||||
, fire = function (element, type, args) {
|
||||
var types = str2arr(type)
|
||||
, i, j, l, names, handlers
|
||||
|
||||
for (i = types.length; i--;) {
|
||||
type = types[i].replace(nameRegex, '')
|
||||
if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.')
|
||||
if (!names && !args && element[eventSupport]) {
|
||||
fireListener(nativeEvents[type], type, element)
|
||||
} else {
|
||||
// non-native event, either because of a namespace, arguments or a non DOM element
|
||||
// iterate over all listeners and manually 'fire'
|
||||
handlers = registry.get(element, type, null, false)
|
||||
args = [false].concat(args)
|
||||
for (j = 0, l = handlers.length; j < l; j++) {
|
||||
if (handlers[j].inNamespaces(names)) {
|
||||
handlers[j].handler.apply(element, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
/**
|
||||
* clone(dstElement, srcElement[, eventType ])
|
||||
*
|
||||
* TODO: perhaps for consistency we should allow the same flexibility in type specifiers?
|
||||
*/
|
||||
, clone = function (element, from, type) {
|
||||
var handlers = registry.get(from, type, null, false)
|
||||
, l = handlers.length
|
||||
, i = 0
|
||||
, args, beanDel
|
||||
|
||||
for (; i < l; i++) {
|
||||
if (handlers[i].original) {
|
||||
args = [ element, handlers[i].type ]
|
||||
if (beanDel = handlers[i].handler.__beanDel) args.push(beanDel.selector)
|
||||
args.push(handlers[i].original)
|
||||
on.apply(null, args)
|
||||
}
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
, bean = {
|
||||
'on' : on
|
||||
, 'add' : add
|
||||
, 'one' : one
|
||||
, 'off' : off
|
||||
, 'remove' : off
|
||||
, 'clone' : clone
|
||||
, 'fire' : fire
|
||||
, 'Event' : Event
|
||||
, 'setSelectorEngine' : setSelectorEngine
|
||||
, 'noConflict' : function () {
|
||||
context[name] = old
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
// for IE, clean up on unload to avoid leaks
|
||||
if (win.attachEvent) {
|
||||
var cleanup = function () {
|
||||
var i, entries = registry.entries()
|
||||
for (i in entries) {
|
||||
if (entries[i].type && entries[i].type !== 'unload') off(entries[i].element, entries[i].type)
|
||||
}
|
||||
win.detachEvent('onunload', cleanup)
|
||||
win.CollectGarbage && win.CollectGarbage()
|
||||
}
|
||||
win.attachEvent('onunload', cleanup)
|
||||
}
|
||||
|
||||
// initialize selector engine to internal default (qSA or throw Error)
|
||||
setSelectorEngine()
|
||||
|
||||
return bean
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,801 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
// Class: $.jqplot.BarRenderer
|
||||
// A plugin renderer for jqPlot to draw a bar plot.
|
||||
// Draws series as a line.
|
||||
|
||||
$.jqplot.BarRenderer = function(){
|
||||
$.jqplot.LineRenderer.call(this);
|
||||
};
|
||||
|
||||
$.jqplot.BarRenderer.prototype = new $.jqplot.LineRenderer();
|
||||
$.jqplot.BarRenderer.prototype.constructor = $.jqplot.BarRenderer;
|
||||
|
||||
// called with scope of series.
|
||||
$.jqplot.BarRenderer.prototype.init = function(options, plot) {
|
||||
// Group: Properties
|
||||
//
|
||||
// prop: barPadding
|
||||
// Number of pixels between adjacent bars at the same axis value.
|
||||
this.barPadding = 8;
|
||||
// prop: barMargin
|
||||
// Number of pixels between groups of bars at adjacent axis values.
|
||||
this.barMargin = 10;
|
||||
// prop: barDirection
|
||||
// 'vertical' = up and down bars, 'horizontal' = side to side bars
|
||||
this.barDirection = 'vertical';
|
||||
// prop: barWidth
|
||||
// Width of the bar in pixels (auto by devaul). null = calculated automatically.
|
||||
this.barWidth = null;
|
||||
// prop: shadowOffset
|
||||
// offset of the shadow from the slice and offset of
|
||||
// each succesive stroke of the shadow from the last.
|
||||
this.shadowOffset = 2;
|
||||
// prop: shadowDepth
|
||||
// number of strokes to apply to the shadow,
|
||||
// each stroke offset shadowOffset from the last.
|
||||
this.shadowDepth = 5;
|
||||
// prop: shadowAlpha
|
||||
// transparency of the shadow (0 = transparent, 1 = opaque)
|
||||
this.shadowAlpha = 0.08;
|
||||
// prop: waterfall
|
||||
// true to enable waterfall plot.
|
||||
this.waterfall = false;
|
||||
// prop: groups
|
||||
// group bars into this many groups
|
||||
this.groups = 1;
|
||||
// prop: varyBarColor
|
||||
// true to color each bar of a series separately rather than
|
||||
// have every bar of a given series the same color.
|
||||
// If used for non-stacked multiple series bar plots, user should
|
||||
// specify a separate 'seriesColors' array for each series.
|
||||
// Otherwise, each series will set their bars to the same color array.
|
||||
// This option has no Effect for stacked bar charts and is disabled.
|
||||
this.varyBarColor = false;
|
||||
// prop: highlightMouseOver
|
||||
// True to highlight slice when moused over.
|
||||
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
|
||||
this.highlightMouseOver = true;
|
||||
// prop: highlightMouseDown
|
||||
// True to highlight when a mouse button is pressed over a slice.
|
||||
// This will be disabled if highlightMouseOver is true.
|
||||
this.highlightMouseDown = false;
|
||||
// prop: highlightColors
|
||||
// an array of colors to use when highlighting a bar.
|
||||
this.highlightColors = [];
|
||||
// prop: transposedData
|
||||
// NOT IMPLEMENTED YET. True if this is a horizontal bar plot and
|
||||
// x and y values are "transposed". Tranposed, or "swapped", data is
|
||||
// required prior to rev. 894 builds of jqPlot with horizontal bars.
|
||||
// Allows backward compatability of bar renderer horizontal bars with
|
||||
// old style data sets.
|
||||
this.transposedData = true;
|
||||
this.renderer.animation = {
|
||||
show: false,
|
||||
direction: 'down',
|
||||
speed: 3000,
|
||||
_supported: true
|
||||
};
|
||||
this._type = 'bar';
|
||||
|
||||
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
|
||||
if (options.highlightMouseDown && options.highlightMouseOver == null) {
|
||||
options.highlightMouseOver = false;
|
||||
}
|
||||
|
||||
//////
|
||||
// This is probably wrong here.
|
||||
// After going back and forth on whether renderer should be the thing
|
||||
// or extend the thing, it seems that it it best if it is a property
|
||||
// on the thing. This should be something that is commonized
|
||||
// among series renderers in the future.
|
||||
//////
|
||||
$.extend(true, this, options);
|
||||
|
||||
// really should probably do this
|
||||
$.extend(true, this.renderer, options);
|
||||
// fill is still needed to properly draw the legend.
|
||||
// bars have to be filled.
|
||||
this.fill = true;
|
||||
|
||||
// if horizontal bar and animating, reset the default direction
|
||||
if (this.barDirection === 'horizontal' && this.rendererOptions.animation && this.rendererOptions.animation.direction == null) {
|
||||
this.renderer.animation.direction = 'left';
|
||||
}
|
||||
|
||||
if (this.waterfall) {
|
||||
this.fillToZero = false;
|
||||
this.disableStack = true;
|
||||
}
|
||||
|
||||
if (this.barDirection == 'vertical' ) {
|
||||
this._primaryAxis = '_xaxis';
|
||||
this._stackAxis = 'y';
|
||||
this.fillAxis = 'y';
|
||||
}
|
||||
else {
|
||||
this._primaryAxis = '_yaxis';
|
||||
this._stackAxis = 'x';
|
||||
this.fillAxis = 'x';
|
||||
}
|
||||
// index of the currenty highlighted point, if any
|
||||
this._highlightedPoint = null;
|
||||
// total number of values for all bar series, total number of bar series, and position of this series
|
||||
this._plotSeriesInfo = null;
|
||||
// Array of actual data colors used for each data point.
|
||||
this._dataColors = [];
|
||||
this._barPoints = [];
|
||||
|
||||
// set the shape renderer options
|
||||
var opts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill};
|
||||
this.renderer.shapeRenderer.init(opts);
|
||||
// set the shadow renderer options
|
||||
var sopts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill};
|
||||
this.renderer.shadowRenderer.init(sopts);
|
||||
|
||||
plot.postInitHooks.addOnce(postInit);
|
||||
plot.postDrawHooks.addOnce(postPlotDraw);
|
||||
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
|
||||
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
|
||||
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
|
||||
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
|
||||
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
|
||||
};
|
||||
|
||||
// called with scope of series
|
||||
function barPreInit(target, data, seriesDefaults, options) {
|
||||
if (this.rendererOptions.barDirection == 'horizontal') {
|
||||
this._stackAxis = 'x';
|
||||
this._primaryAxis = '_yaxis';
|
||||
}
|
||||
if (this.rendererOptions.waterfall == true) {
|
||||
this._data = $.extend(true, [], this.data);
|
||||
var sum = 0;
|
||||
var pos = (!this.rendererOptions.barDirection || this.rendererOptions.barDirection === 'vertical' || this.transposedData === false) ? 1 : 0;
|
||||
for(var i=0; i<this.data.length; i++) {
|
||||
sum += this.data[i][pos];
|
||||
if (i>0) {
|
||||
this.data[i][pos] += this.data[i-1][pos];
|
||||
}
|
||||
}
|
||||
this.data[this.data.length] = (pos == 1) ? [this.data.length+1, sum] : [sum, this.data.length+1];
|
||||
this._data[this._data.length] = (pos == 1) ? [this._data.length+1, sum] : [sum, this._data.length+1];
|
||||
}
|
||||
if (this.rendererOptions.groups > 1) {
|
||||
this.breakOnNull = true;
|
||||
var l = this.data.length;
|
||||
var skip = parseInt(l/this.rendererOptions.groups, 10);
|
||||
var count = 0;
|
||||
for (var i=skip; i<l; i+=skip) {
|
||||
this.data.splice(i+count, 0, [null, null]);
|
||||
this._plotData.splice(i+count, 0, [null, null]);
|
||||
this._stackData.splice(i+count, 0, [null, null]);
|
||||
count++;
|
||||
}
|
||||
for (i=0; i<this.data.length; i++) {
|
||||
if (this._primaryAxis == '_xaxis') {
|
||||
this.data[i][0] = i+1;
|
||||
this._plotData[i][0] = i+1;
|
||||
this._stackData[i][0] = i+1;
|
||||
}
|
||||
else {
|
||||
this.data[i][1] = i+1;
|
||||
this._plotData[i][1] = i+1;
|
||||
this._stackData[i][1] = i+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$.jqplot.preSeriesInitHooks.push(barPreInit);
|
||||
|
||||
// needs to be called with scope of series, not renderer.
|
||||
$.jqplot.BarRenderer.prototype.calcSeriesNumbers = function() {
|
||||
var nvals = 0;
|
||||
var nseries = 0;
|
||||
var paxis = this[this._primaryAxis];
|
||||
var s, series, pos;
|
||||
// loop through all series on this axis
|
||||
for (var i=0; i < paxis._series.length; i++) {
|
||||
series = paxis._series[i];
|
||||
if (series === this) {
|
||||
pos = i;
|
||||
}
|
||||
// is the series rendered as a bar?
|
||||
if (series.renderer.constructor == $.jqplot.BarRenderer) {
|
||||
// gridData may not be computed yet, use data length insted
|
||||
nvals += series.data.length;
|
||||
nseries += 1;
|
||||
}
|
||||
}
|
||||
// return total number of values for all bar series, total number of bar series, and position of this series
|
||||
return [nvals, nseries, pos];
|
||||
};
|
||||
|
||||
$.jqplot.BarRenderer.prototype.setBarWidth = function() {
|
||||
// need to know how many data values we have on the approprate axis and figure it out.
|
||||
var i;
|
||||
var nvals = 0;
|
||||
var nseries = 0;
|
||||
var paxis = this[this._primaryAxis];
|
||||
var s, series, pos;
|
||||
var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
|
||||
nvals = temp[0];
|
||||
nseries = temp[1];
|
||||
var nticks = paxis.numberTicks;
|
||||
var nbins = (nticks-1)/2;
|
||||
// so, now we have total number of axis values.
|
||||
if (paxis.name == 'xaxis' || paxis.name == 'x2axis') {
|
||||
if (this._stack) {
|
||||
this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals * nseries - this.barMargin;
|
||||
}
|
||||
else {
|
||||
this.barWidth = ((paxis._offsets.max - paxis._offsets.min)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
|
||||
// this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals - this.barPadding - this.barMargin/nseries;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this._stack) {
|
||||
this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals * nseries - this.barMargin;
|
||||
}
|
||||
else {
|
||||
this.barWidth = ((paxis._offsets.min - paxis._offsets.max)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
|
||||
// this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals - this.barPadding - this.barMargin/nseries;
|
||||
}
|
||||
}
|
||||
return [nvals, nseries];
|
||||
};
|
||||
|
||||
function computeHighlightColors (colors) {
|
||||
var ret = [];
|
||||
for (var i=0; i<colors.length; i++){
|
||||
var rgba = $.jqplot.getColorComponents(colors[i]);
|
||||
var newrgb = [rgba[0], rgba[1], rgba[2]];
|
||||
var sum = newrgb[0] + newrgb[1] + newrgb[2];
|
||||
for (var j=0; j<3; j++) {
|
||||
// when darkening, lowest color component can be is 60.
|
||||
newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
|
||||
newrgb[j] = parseInt(newrgb[j], 10);
|
||||
}
|
||||
ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getStart(sidx, didx, comp, plot, axis) {
|
||||
// check if sign change
|
||||
var seriesIndex = sidx,
|
||||
prevSeriesIndex = sidx - 1,
|
||||
start,
|
||||
prevVal,
|
||||
aidx = (axis === 'x') ? 0 : 1;
|
||||
|
||||
// is this not the first series?
|
||||
if (seriesIndex > 0) {
|
||||
prevVal = plot.series[prevSeriesIndex]._plotData[didx][aidx];
|
||||
|
||||
// is there a sign change
|
||||
if ((comp * prevVal) < 0) {
|
||||
start = getStart(prevSeriesIndex, didx, comp, plot, axis);
|
||||
}
|
||||
|
||||
// no sign change.
|
||||
else {
|
||||
start = plot.series[prevSeriesIndex].gridData[didx][aidx];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if first series, return value at 0
|
||||
else {
|
||||
|
||||
start = (aidx === 0) ? plot.series[seriesIndex]._xaxis.series_u2p(0) : plot.series[seriesIndex]._yaxis.series_u2p(0);
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
$.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options, plot) {
|
||||
var i;
|
||||
// Ughhh, have to make a copy of options b/c it may be modified later.
|
||||
var opts = $.extend({}, options);
|
||||
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
|
||||
var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
|
||||
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
|
||||
var xaxis = this.xaxis;
|
||||
var yaxis = this.yaxis;
|
||||
var xp = this._xaxis.series_u2p;
|
||||
var yp = this._yaxis.series_u2p;
|
||||
var pointx, pointy;
|
||||
// clear out data colors.
|
||||
this._dataColors = [];
|
||||
this._barPoints = [];
|
||||
|
||||
if (this.barWidth == null) {
|
||||
this.renderer.setBarWidth.call(this);
|
||||
}
|
||||
|
||||
var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
|
||||
var nvals = temp[0];
|
||||
var nseries = temp[1];
|
||||
var pos = temp[2];
|
||||
var points = [];
|
||||
|
||||
if (this._stack) {
|
||||
this._barNudge = 0;
|
||||
}
|
||||
else {
|
||||
this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
|
||||
}
|
||||
if (showLine) {
|
||||
var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors);
|
||||
var positiveColors = new $.jqplot.ColorGenerator(this.seriesColors);
|
||||
var negativeColor = negativeColors.get(this.index);
|
||||
if (! this.useNegativeColors) {
|
||||
negativeColor = opts.fillStyle;
|
||||
}
|
||||
var positiveColor = opts.fillStyle;
|
||||
var base;
|
||||
var xstart;
|
||||
var ystart;
|
||||
|
||||
if (this.barDirection == 'vertical') {
|
||||
for (var i=0; i<gridData.length; i++) {
|
||||
if (!this._stack && this.data[i][1] == null) {
|
||||
continue;
|
||||
}
|
||||
points = [];
|
||||
base = gridData[i][0] + this._barNudge;
|
||||
|
||||
// stacked
|
||||
if (this._stack && this._prevGridData.length) {
|
||||
ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
|
||||
}
|
||||
|
||||
// not stacked
|
||||
else {
|
||||
if (this.fillToZero) {
|
||||
ystart = this._yaxis.series_u2p(0);
|
||||
}
|
||||
else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
|
||||
ystart = this.gridData[i-1][1];
|
||||
}
|
||||
else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
|
||||
if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
|
||||
ystart = this._yaxis.series_u2p(0);
|
||||
}
|
||||
else if (this._yaxis.min > 0) {
|
||||
ystart = ctx.canvas.height;
|
||||
}
|
||||
else {
|
||||
ystart = 0;
|
||||
}
|
||||
}
|
||||
else if (this.waterfall && i == this.gridData.length - 1) {
|
||||
if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
|
||||
ystart = this._yaxis.series_u2p(0);
|
||||
}
|
||||
else if (this._yaxis.min > 0) {
|
||||
ystart = ctx.canvas.height;
|
||||
}
|
||||
else {
|
||||
ystart = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ystart = ctx.canvas.height;
|
||||
}
|
||||
}
|
||||
if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) {
|
||||
if (this.varyBarColor && !this._stack) {
|
||||
if (this.useNegativeColors) {
|
||||
opts.fillStyle = negativeColors.next();
|
||||
}
|
||||
else {
|
||||
opts.fillStyle = positiveColors.next();
|
||||
}
|
||||
}
|
||||
else {
|
||||
opts.fillStyle = negativeColor;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.varyBarColor && !this._stack) {
|
||||
opts.fillStyle = positiveColors.next();
|
||||
}
|
||||
else {
|
||||
opts.fillStyle = positiveColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.fillToZero || this._plotData[i][1] >= 0) {
|
||||
points.push([base-this.barWidth/2, ystart]);
|
||||
points.push([base-this.barWidth/2, gridData[i][1]]);
|
||||
points.push([base+this.barWidth/2, gridData[i][1]]);
|
||||
points.push([base+this.barWidth/2, ystart]);
|
||||
}
|
||||
// for negative bars make sure points are always ordered clockwise
|
||||
else {
|
||||
points.push([base-this.barWidth/2, gridData[i][1]]);
|
||||
points.push([base-this.barWidth/2, ystart]);
|
||||
points.push([base+this.barWidth/2, ystart]);
|
||||
points.push([base+this.barWidth/2, gridData[i][1]]);
|
||||
}
|
||||
this._barPoints.push(points);
|
||||
// now draw the shadows if not stacked.
|
||||
// for stacked plots, they are predrawn by drawShadow
|
||||
if (shadow && !this._stack) {
|
||||
var sopts = $.extend(true, {}, opts);
|
||||
// need to get rid of fillStyle on shadow.
|
||||
delete sopts.fillStyle;
|
||||
this.renderer.shadowRenderer.draw(ctx, points, sopts);
|
||||
}
|
||||
var clr = opts.fillStyle || this.color;
|
||||
this._dataColors.push(clr);
|
||||
this.renderer.shapeRenderer.draw(ctx, points, opts);
|
||||
}
|
||||
}
|
||||
|
||||
else if (this.barDirection == 'horizontal'){
|
||||
for (var i=0; i<gridData.length; i++) {
|
||||
if (!this._stack && this.data[i][0] == null) {
|
||||
continue;
|
||||
}
|
||||
points = [];
|
||||
base = gridData[i][1] - this._barNudge;
|
||||
xstart;
|
||||
|
||||
if (this._stack && this._prevGridData.length) {
|
||||
xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
|
||||
}
|
||||
// not stacked
|
||||
else {
|
||||
if (this.fillToZero) {
|
||||
xstart = this._xaxis.series_u2p(0);
|
||||
}
|
||||
else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
|
||||
xstart = this.gridData[i-1][0];
|
||||
}
|
||||
else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
|
||||
if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
|
||||
xstart = this._xaxis.series_u2p(0);
|
||||
}
|
||||
else if (this._xaxis.min > 0) {
|
||||
xstart = 0;
|
||||
}
|
||||
else {
|
||||
xstart = 0;
|
||||
}
|
||||
}
|
||||
else if (this.waterfall && i == this.gridData.length - 1) {
|
||||
if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
|
||||
xstart = this._xaxis.series_u2p(0);
|
||||
}
|
||||
else if (this._xaxis.min > 0) {
|
||||
xstart = 0;
|
||||
}
|
||||
else {
|
||||
xstart = ctx.canvas.width;
|
||||
}
|
||||
}
|
||||
else {
|
||||
xstart = 0;
|
||||
}
|
||||
}
|
||||
if ((this.fillToZero && this._plotData[i][0] < 0) || (this.waterfall && this._data[i][0] < 0)) {
|
||||
if (this.varyBarColor && !this._stack) {
|
||||
if (this.useNegativeColors) {
|
||||
opts.fillStyle = negativeColors.next();
|
||||
}
|
||||
else {
|
||||
opts.fillStyle = positiveColors.next();
|
||||
}
|
||||
}
|
||||
else {
|
||||
opts.fillStyle = negativeColor;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.varyBarColor && !this._stack) {
|
||||
opts.fillStyle = positiveColors.next();
|
||||
}
|
||||
else {
|
||||
opts.fillStyle = positiveColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!this.fillToZero || this._plotData[i][0] >= 0) {
|
||||
points.push([xstart, base + this.barWidth / 2]);
|
||||
points.push([xstart, base - this.barWidth / 2]);
|
||||
points.push([gridData[i][0], base - this.barWidth / 2]);
|
||||
points.push([gridData[i][0], base + this.barWidth / 2]);
|
||||
}
|
||||
else {
|
||||
points.push([gridData[i][0], base + this.barWidth / 2]);
|
||||
points.push([gridData[i][0], base - this.barWidth / 2]);
|
||||
points.push([xstart, base - this.barWidth / 2]);
|
||||
points.push([xstart, base + this.barWidth / 2]);
|
||||
}
|
||||
|
||||
this._barPoints.push(points);
|
||||
// now draw the shadows if not stacked.
|
||||
// for stacked plots, they are predrawn by drawShadow
|
||||
if (shadow && !this._stack) {
|
||||
var sopts = $.extend(true, {}, opts);
|
||||
delete sopts.fillStyle;
|
||||
this.renderer.shadowRenderer.draw(ctx, points, sopts);
|
||||
}
|
||||
var clr = opts.fillStyle || this.color;
|
||||
this._dataColors.push(clr);
|
||||
this.renderer.shapeRenderer.draw(ctx, points, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.highlightColors.length == 0) {
|
||||
this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors);
|
||||
}
|
||||
|
||||
else if (typeof(this.highlightColors) == 'string') {
|
||||
var temp = this.highlightColors;
|
||||
this.highlightColors = [];
|
||||
for (var i=0; i<this._dataColors.length; i++) {
|
||||
this.highlightColors.push(temp);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// for stacked plots, shadows will be pre drawn by drawShadow.
|
||||
$.jqplot.BarRenderer.prototype.drawShadow = function(ctx, gridData, options, plot) {
|
||||
var i;
|
||||
var opts = (options != undefined) ? options : {};
|
||||
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
|
||||
var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
|
||||
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
|
||||
var xaxis = this.xaxis;
|
||||
var yaxis = this.yaxis;
|
||||
var xp = this._xaxis.series_u2p;
|
||||
var yp = this._yaxis.series_u2p;
|
||||
var pointx, points, pointy, nvals, nseries, pos;
|
||||
|
||||
if (this._stack && this.shadow) {
|
||||
if (this.barWidth == null) {
|
||||
this.renderer.setBarWidth.call(this);
|
||||
}
|
||||
|
||||
var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
|
||||
nvals = temp[0];
|
||||
nseries = temp[1];
|
||||
pos = temp[2];
|
||||
|
||||
if (this._stack) {
|
||||
this._barNudge = 0;
|
||||
}
|
||||
else {
|
||||
this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
|
||||
}
|
||||
if (showLine) {
|
||||
|
||||
if (this.barDirection == 'vertical') {
|
||||
for (var i=0; i<gridData.length; i++) {
|
||||
if (this.data[i][1] == null) {
|
||||
continue;
|
||||
}
|
||||
points = [];
|
||||
var base = gridData[i][0] + this._barNudge;
|
||||
var ystart;
|
||||
|
||||
if (this._stack && this._prevGridData.length) {
|
||||
ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
|
||||
}
|
||||
else {
|
||||
if (this.fillToZero) {
|
||||
ystart = this._yaxis.series_u2p(0);
|
||||
}
|
||||
else {
|
||||
ystart = ctx.canvas.height;
|
||||
}
|
||||
}
|
||||
|
||||
points.push([base-this.barWidth/2, ystart]);
|
||||
points.push([base-this.barWidth/2, gridData[i][1]]);
|
||||
points.push([base+this.barWidth/2, gridData[i][1]]);
|
||||
points.push([base+this.barWidth/2, ystart]);
|
||||
this.renderer.shadowRenderer.draw(ctx, points, opts);
|
||||
}
|
||||
}
|
||||
|
||||
else if (this.barDirection == 'horizontal'){
|
||||
for (var i=0; i<gridData.length; i++) {
|
||||
if (this.data[i][0] == null) {
|
||||
continue;
|
||||
}
|
||||
points = [];
|
||||
var base = gridData[i][1] - this._barNudge;
|
||||
var xstart;
|
||||
|
||||
if (this._stack && this._prevGridData.length) {
|
||||
xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
|
||||
}
|
||||
else {
|
||||
if (this.fillToZero) {
|
||||
xstart = this._xaxis.series_u2p(0);
|
||||
}
|
||||
else {
|
||||
xstart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
points.push([xstart, base+this.barWidth/2]);
|
||||
points.push([gridData[i][0], base+this.barWidth/2]);
|
||||
points.push([gridData[i][0], base-this.barWidth/2]);
|
||||
points.push([xstart, base-this.barWidth/2]);
|
||||
this.renderer.shadowRenderer.draw(ctx, points, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function postInit(target, data, options) {
|
||||
for (var i=0; i<this.series.length; i++) {
|
||||
if (this.series[i].renderer.constructor == $.jqplot.BarRenderer) {
|
||||
// don't allow mouseover and mousedown at same time.
|
||||
if (this.series[i].highlightMouseOver) {
|
||||
this.series[i].highlightMouseDown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called within context of plot
|
||||
// create a canvas which we can draw on.
|
||||
// insert it before the eventCanvas, so eventCanvas will still capture events.
|
||||
function postPlotDraw() {
|
||||
// Memory Leaks patch
|
||||
if (this.plugins.barRenderer && this.plugins.barRenderer.highlightCanvas) {
|
||||
|
||||
this.plugins.barRenderer.highlightCanvas.resetCanvas();
|
||||
this.plugins.barRenderer.highlightCanvas = null;
|
||||
}
|
||||
|
||||
this.plugins.barRenderer = {highlightedSeriesIndex:null};
|
||||
this.plugins.barRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
|
||||
|
||||
this.eventCanvas._elem.before(this.plugins.barRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-barRenderer-highlight-canvas', this._plotDimensions, this));
|
||||
this.plugins.barRenderer.highlightCanvas.setContext();
|
||||
this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
|
||||
}
|
||||
|
||||
function highlight (plot, sidx, pidx, points) {
|
||||
var s = plot.series[sidx];
|
||||
var canvas = plot.plugins.barRenderer.highlightCanvas;
|
||||
canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
|
||||
s._highlightedPoint = pidx;
|
||||
plot.plugins.barRenderer.highlightedSeriesIndex = sidx;
|
||||
var opts = {fillStyle: s.highlightColors[pidx]};
|
||||
s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
|
||||
canvas = null;
|
||||
}
|
||||
|
||||
function unhighlight (plot) {
|
||||
var canvas = plot.plugins.barRenderer.highlightCanvas;
|
||||
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
|
||||
for (var i=0; i<plot.series.length; i++) {
|
||||
plot.series[i]._highlightedPoint = null;
|
||||
}
|
||||
plot.plugins.barRenderer.highlightedSeriesIndex = null;
|
||||
plot.target.trigger('jqplotDataUnhighlight');
|
||||
canvas = null;
|
||||
}
|
||||
|
||||
|
||||
function handleMove(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
var evt1 = jQuery.Event('jqplotDataMouseOver');
|
||||
evt1.pageX = ev.pageX;
|
||||
evt1.pageY = ev.pageY;
|
||||
plot.target.trigger(evt1, ins);
|
||||
if (plot.series[ins[0]].show && plot.series[ins[0]].highlightMouseOver &&
|
||||
!(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
|
||||
var evt = jQuery.Event('jqplotDataHighlight');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
|
||||
}
|
||||
}
|
||||
else if (neighbor == null) {
|
||||
unhighlight (plot);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
|
||||
var evt = jQuery.Event('jqplotDataHighlight');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
|
||||
}
|
||||
}
|
||||
else if (neighbor == null) {
|
||||
unhighlight (plot);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
|
||||
var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
|
||||
if (idx != null && plot.series[idx].highlightMouseDown) {
|
||||
unhighlight(plot);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
var evt = jQuery.Event('jqplotDataClick');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
|
||||
if (idx != null && plot.series[idx].highlightMouseDown) {
|
||||
unhighlight(plot);
|
||||
}
|
||||
var evt = jQuery.Event('jqplotDataRightClick');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
/**
|
||||
* Class: $.jqplot.CanvasAxisTickRenderer
|
||||
* Renderer to draw axis ticks with a canvas element to support advanced
|
||||
* featrues such as rotated text. This renderer uses a separate rendering engine
|
||||
* to draw the text on the canvas. Two modes of rendering the text are available.
|
||||
* If the browser has native font support for canvas fonts (currently Mozila 3.5
|
||||
* and Safari 4), you can enable text rendering with the canvas fillText method.
|
||||
* You do so by setting the "enableFontSupport" option to true.
|
||||
*
|
||||
* Browsers lacking native font support will have the text drawn on the canvas
|
||||
* using the Hershey font metrics. Even if the "enableFontSupport" option is true
|
||||
* non-supporting browsers will still render with the Hershey font.
|
||||
*/
|
||||
$.jqplot.CanvasAxisTickRenderer = function(options) {
|
||||
// Group: Properties
|
||||
|
||||
// prop: mark
|
||||
// tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null.
|
||||
this.mark = 'outside';
|
||||
// prop: showMark
|
||||
// whether or not to show the mark on the axis.
|
||||
this.showMark = true;
|
||||
// prop: showGridline
|
||||
// whether or not to draw the gridline on the grid at this tick.
|
||||
this.showGridline = true;
|
||||
// prop: isMinorTick
|
||||
// if this is a minor tick.
|
||||
this.isMinorTick = false;
|
||||
// prop: angle
|
||||
// angle of text, measured clockwise from x axis.
|
||||
this.angle = 0;
|
||||
// prop: markSize
|
||||
// Length of the tick marks in pixels. For 'cross' style, length
|
||||
// will be stoked above and below axis, so total length will be twice this.
|
||||
this.markSize = 4;
|
||||
// prop: show
|
||||
// whether or not to show the tick (mark and label).
|
||||
this.show = true;
|
||||
// prop: showLabel
|
||||
// whether or not to show the label.
|
||||
this.showLabel = true;
|
||||
// prop: labelPosition
|
||||
// 'auto', 'start', 'middle' or 'end'.
|
||||
// Whether tick label should be positioned so the start, middle, or end
|
||||
// of the tick mark.
|
||||
this.labelPosition = 'auto';
|
||||
this.label = '';
|
||||
this.value = null;
|
||||
this._styles = {};
|
||||
// prop: formatter
|
||||
// A class of a formatter for the tick text.
|
||||
// The default $.jqplot.DefaultTickFormatter uses sprintf.
|
||||
this.formatter = $.jqplot.DefaultTickFormatter;
|
||||
// prop: formatString
|
||||
// string passed to the formatter.
|
||||
this.formatString = '';
|
||||
// prop: prefix
|
||||
// String to prepend to the tick label.
|
||||
// Prefix is prepended to the formatted tick label.
|
||||
this.prefix = '';
|
||||
// prop: fontFamily
|
||||
// css spec for the font-family css attribute.
|
||||
this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
|
||||
// prop: fontSize
|
||||
// CSS spec for font size.
|
||||
this.fontSize = '10pt';
|
||||
// prop: fontWeight
|
||||
// CSS spec for fontWeight
|
||||
this.fontWeight = 'normal';
|
||||
// prop: fontStretch
|
||||
// Multiplier to condense or expand font width.
|
||||
// Applies only to browsers which don't support canvas native font rendering.
|
||||
this.fontStretch = 1.0;
|
||||
// prop: textColor
|
||||
// css spec for the color attribute.
|
||||
this.textColor = '#666666';
|
||||
// prop: enableFontSupport
|
||||
// true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
|
||||
// If true, tick label will be drawn with canvas tag native support for fonts.
|
||||
// If false, tick label will be drawn with Hershey font metrics.
|
||||
this.enableFontSupport = true;
|
||||
// prop: pt2px
|
||||
// Point to pixel scaling factor, used for computing height of bounding box
|
||||
// around a label. The labels text renderer has a default setting of 1.4, which
|
||||
// should be suitable for most fonts. Leave as null to use default. If tops of
|
||||
// letters appear clipped, increase this. If bounding box seems too big, decrease.
|
||||
// This is an issue only with the native font renderering capabilities of Mozilla
|
||||
// 3.5 and Safari 4 since they do not provide a method to determine the font height.
|
||||
this.pt2px = null;
|
||||
|
||||
this._elem;
|
||||
this._ctx;
|
||||
this._plotWidth;
|
||||
this._plotHeight;
|
||||
this._plotDimensions = {height:null, width:null};
|
||||
|
||||
$.extend(true, this, options);
|
||||
|
||||
var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
|
||||
if (this.pt2px) {
|
||||
ropts.pt2px = this.pt2px;
|
||||
}
|
||||
|
||||
if (this.enableFontSupport) {
|
||||
if ($.jqplot.support_canvas_text()) {
|
||||
this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
|
||||
}
|
||||
|
||||
else {
|
||||
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.init = function(options) {
|
||||
$.extend(true, this, options);
|
||||
this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
|
||||
};
|
||||
|
||||
// return width along the x axis
|
||||
// will check first to see if an element exists.
|
||||
// if not, will return the computed text box width.
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.getWidth = function(ctx) {
|
||||
if (this._elem) {
|
||||
return this._elem.outerWidth(true);
|
||||
}
|
||||
else {
|
||||
var tr = this._textRenderer;
|
||||
var l = tr.getWidth(ctx);
|
||||
var h = tr.getHeight(ctx);
|
||||
var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
|
||||
return w;
|
||||
}
|
||||
};
|
||||
|
||||
// return height along the y axis.
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.getHeight = function(ctx) {
|
||||
if (this._elem) {
|
||||
return this._elem.outerHeight(true);
|
||||
}
|
||||
else {
|
||||
var tr = this._textRenderer;
|
||||
var l = tr.getWidth(ctx);
|
||||
var h = tr.getHeight(ctx);
|
||||
var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
|
||||
return w;
|
||||
}
|
||||
};
|
||||
|
||||
// return top.
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.getTop = function(ctx) {
|
||||
if (this._elem) {
|
||||
return this._elem.position().top;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad = function() {
|
||||
var a = this.angle * Math.PI/180;
|
||||
return a;
|
||||
};
|
||||
|
||||
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
|
||||
this.value = value;
|
||||
if (isMinor) {
|
||||
this.isMinorTick = true;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.draw = function(ctx, plot) {
|
||||
if (!this.label) {
|
||||
this.label = this.prefix + this.formatter(this.formatString, this.value);
|
||||
}
|
||||
|
||||
// Memory Leaks patch
|
||||
if (this._elem) {
|
||||
if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
|
||||
window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
|
||||
}
|
||||
|
||||
this._elem.emptyForce();
|
||||
this._elem = null;
|
||||
}
|
||||
|
||||
// create a canvas here, but can't draw on it untill it is appended
|
||||
// to dom for IE compatability.
|
||||
|
||||
var elem = plot.canvasManager.getCanvas();
|
||||
|
||||
this._textRenderer.setText(this.label, ctx);
|
||||
var w = this.getWidth(ctx);
|
||||
var h = this.getHeight(ctx);
|
||||
// canvases seem to need to have width and heigh attributes directly set.
|
||||
elem.width = w;
|
||||
elem.height = h;
|
||||
elem.style.width = w;
|
||||
elem.style.height = h;
|
||||
elem.style.textAlign = 'left';
|
||||
elem.style.position = 'absolute';
|
||||
|
||||
elem = plot.canvasManager.initCanvas(elem);
|
||||
|
||||
this._elem = $(elem);
|
||||
this._elem.css(this._styles);
|
||||
this._elem.addClass('jqplot-'+this.axis+'-tick');
|
||||
|
||||
elem = null;
|
||||
return this._elem;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasAxisTickRenderer.prototype.pack = function() {
|
||||
this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
* included jsDate library by Chris Leonello:
|
||||
*
|
||||
* Copyright (c) 2010-2013 Chris Leonello
|
||||
*
|
||||
* jsDate is currently available for use in all personal or commercial projects
|
||||
* under both the MIT and GPL version 2.0 licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* jsDate borrows many concepts and ideas from the Date Instance
|
||||
* Methods by Ken Snyder along with some parts of Ken's actual code.
|
||||
*
|
||||
* Ken's original Date Instance Methods and copyright notice:
|
||||
*
|
||||
* Ken Snyder (ken d snyder at gmail dot com)
|
||||
* 2008-09-10
|
||||
* version 2.0.2 (http://kendsnyder.com/sandbox/date/)
|
||||
* Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
|
||||
*
|
||||
* jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
|
||||
* Larry has generously given permission to adapt his code for inclusion
|
||||
* into jqPlot.
|
||||
*
|
||||
* Larry's original code can be found here:
|
||||
*
|
||||
* https://github.com/lsiden/export-jqplot-to-png
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
// This code is a modified version of the canvastext.js code, copyright below:
|
||||
//
|
||||
// This code is released to the public domain by Jim Studt, 2007.
|
||||
// He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/
|
||||
//
|
||||
$.jqplot.CanvasTextRenderer = function(options){
|
||||
this.fontStyle = 'normal'; // normal, italic, oblique [not implemented]
|
||||
this.fontVariant = 'normal'; // normal, small caps [not implemented]
|
||||
this.fontWeight = 'normal'; // normal, bold, bolder, lighter, 100 - 900
|
||||
this.fontSize = '10px';
|
||||
this.fontFamily = 'sans-serif';
|
||||
this.fontStretch = 1.0;
|
||||
this.fillStyle = '#666666';
|
||||
this.angle = 0;
|
||||
this.textAlign = 'start';
|
||||
this.textBaseline = 'alphabetic';
|
||||
this.text;
|
||||
this.width;
|
||||
this.height;
|
||||
this.pt2px = 1.28;
|
||||
|
||||
$.extend(true, this, options);
|
||||
this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
|
||||
this.setHeight();
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.init = function(options) {
|
||||
$.extend(true, this, options);
|
||||
this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
|
||||
this.setHeight();
|
||||
};
|
||||
|
||||
// convert css spec into point size
|
||||
// returns float
|
||||
$.jqplot.CanvasTextRenderer.prototype.normalizeFontSize = function(sz) {
|
||||
sz = String(sz);
|
||||
var n = parseFloat(sz);
|
||||
if (sz.indexOf('px') > -1) {
|
||||
return n/this.pt2px;
|
||||
}
|
||||
else if (sz.indexOf('pt') > -1) {
|
||||
return n;
|
||||
}
|
||||
else if (sz.indexOf('em') > -1) {
|
||||
return n*12;
|
||||
}
|
||||
else if (sz.indexOf('%') > -1) {
|
||||
return n*12/100;
|
||||
}
|
||||
// default to pixels;
|
||||
else {
|
||||
return n/this.pt2px;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.fontWeight2Float = function(w) {
|
||||
// w = normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
||||
// return values adjusted for Hershey font.
|
||||
if (Number(w)) {
|
||||
return w/400;
|
||||
}
|
||||
else {
|
||||
switch (w) {
|
||||
case 'normal':
|
||||
return 1;
|
||||
break;
|
||||
case 'bold':
|
||||
return 1.75;
|
||||
break;
|
||||
case 'bolder':
|
||||
return 2.25;
|
||||
break;
|
||||
case 'lighter':
|
||||
return 0.75;
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.getText = function() {
|
||||
return this.text;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.setText = function(t, ctx) {
|
||||
this.text = t;
|
||||
this.setWidth(ctx);
|
||||
return this;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.getWidth = function(ctx) {
|
||||
return this.width;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.setWidth = function(ctx, w) {
|
||||
if (!w) {
|
||||
this.width = this.measure(ctx, this.text);
|
||||
}
|
||||
else {
|
||||
this.width = w;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
// return height in pixels.
|
||||
$.jqplot.CanvasTextRenderer.prototype.getHeight = function(ctx) {
|
||||
return this.height;
|
||||
};
|
||||
|
||||
// w - height in pt
|
||||
// set heigh in px
|
||||
$.jqplot.CanvasTextRenderer.prototype.setHeight = function(w) {
|
||||
if (!w) {
|
||||
//height = this.fontSize /0.75;
|
||||
this.height = this.normalizedFontSize * this.pt2px;
|
||||
}
|
||||
else {
|
||||
this.height = w;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.letter = function (ch)
|
||||
{
|
||||
return this.letters[ch];
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.ascent = function()
|
||||
{
|
||||
return this.normalizedFontSize;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.descent = function()
|
||||
{
|
||||
return 7.0*this.normalizedFontSize/25.0;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.measure = function(ctx, str)
|
||||
{
|
||||
var total = 0;
|
||||
var len = str.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var c = this.letter(str.charAt(i));
|
||||
if (c) {
|
||||
total += c.width * this.normalizedFontSize / 25.0 * this.fontStretch;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.draw = function(ctx,str)
|
||||
{
|
||||
var x = 0;
|
||||
// leave room at bottom for descenders.
|
||||
var y = this.height*0.72;
|
||||
var total = 0;
|
||||
var len = str.length;
|
||||
var mag = this.normalizedFontSize / 25.0;
|
||||
|
||||
ctx.save();
|
||||
var tx, ty;
|
||||
|
||||
// 1st quadrant
|
||||
if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
|
||||
tx = 0;
|
||||
ty = -Math.sin(this.angle) * this.width;
|
||||
}
|
||||
// 4th quadrant
|
||||
else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
|
||||
tx = Math.sin(this.angle) * this.height;
|
||||
ty = 0;
|
||||
}
|
||||
// 2nd quadrant
|
||||
else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
|
||||
tx = -Math.cos(this.angle) * this.width;
|
||||
ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
|
||||
}
|
||||
// 3rd quadrant
|
||||
else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
|
||||
tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
|
||||
ty = -Math.cos(this.angle) * this.height;
|
||||
}
|
||||
|
||||
ctx.strokeStyle = this.fillStyle;
|
||||
ctx.fillStyle = this.fillStyle;
|
||||
ctx.translate(tx, ty);
|
||||
ctx.rotate(this.angle);
|
||||
ctx.lineCap = "round";
|
||||
// multiplier was 2.0
|
||||
var fact = (this.normalizedFontSize > 30) ? 2.0 : 2 + (30 - this.normalizedFontSize)/20;
|
||||
ctx.lineWidth = fact * mag * this.fontWeight2Float(this.fontWeight);
|
||||
|
||||
for ( var i = 0; i < len; i++) {
|
||||
var c = this.letter( str.charAt(i));
|
||||
if ( !c) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
var penUp = 1;
|
||||
var needStroke = 0;
|
||||
for ( var j = 0; j < c.points.length; j++) {
|
||||
var a = c.points[j];
|
||||
if ( a[0] == -1 && a[1] == -1) {
|
||||
penUp = 1;
|
||||
continue;
|
||||
}
|
||||
if ( penUp) {
|
||||
ctx.moveTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
|
||||
penUp = false;
|
||||
} else {
|
||||
ctx.lineTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
x += c.width*mag*this.fontStretch;
|
||||
}
|
||||
ctx.restore();
|
||||
return total;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasTextRenderer.prototype.letters = {
|
||||
' ': { width: 16, points: [] },
|
||||
'!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
|
||||
'"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] },
|
||||
'#': { width: 21, points: [[11,25],[4,-7],[-1,-1],[17,25],[10,-7],[-1,-1],[4,12],[18,12],[-1,-1],[3,6],[17,6]] },
|
||||
'$': { width: 20, points: [[8,25],[8,-4],[-1,-1],[12,25],[12,-4],[-1,-1],[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
|
||||
'%': { width: 24, points: [[21,21],[3,0],[-1,-1],[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],[10,20],[13,19],[16,19],[19,20],[21,21],[-1,-1],[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
|
||||
'&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },
|
||||
'\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
|
||||
'(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
|
||||
')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
|
||||
'*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] },
|
||||
'+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] },
|
||||
',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
|
||||
'-': { width: 18, points: [[6,9],[12,9]] },
|
||||
'.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
|
||||
'/': { width: 22, points: [[20,25],[2,-7]] },
|
||||
'0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
|
||||
'1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
|
||||
'2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
|
||||
'3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
|
||||
'4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] },
|
||||
'5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
|
||||
'6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
|
||||
'7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] },
|
||||
'8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
|
||||
'9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
|
||||
':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
|
||||
';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
|
||||
'<': { width: 24, points: [[20,18],[4,9],[20,0]] },
|
||||
'=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] },
|
||||
'>': { width: 24, points: [[4,18],[20,9],[4,0]] },
|
||||
'?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]] },
|
||||
'@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]] },
|
||||
'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] },
|
||||
'B': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
|
||||
'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
|
||||
'D': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
|
||||
'E': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]] },
|
||||
'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] },
|
||||
'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]] },
|
||||
'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] },
|
||||
'I': { width: 8, points: [[4,21],[4,0]] },
|
||||
'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
|
||||
'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] },
|
||||
'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] },
|
||||
'M': { width: 24, points: [[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]] },
|
||||
'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] },
|
||||
'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
|
||||
'P': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
|
||||
'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]] },
|
||||
'R': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]] },
|
||||
'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
|
||||
'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] },
|
||||
'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
|
||||
'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] },
|
||||
'W': { width: 24, points: [[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]] },
|
||||
'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] },
|
||||
'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] },
|
||||
'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] },
|
||||
'[': { width: 14, points: [[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]] },
|
||||
'\\': { width: 14, points: [[0,21],[14,-3]] },
|
||||
']': { width: 14, points: [[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]] },
|
||||
'^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] },
|
||||
'_': { width: 16, points: [[0,-2],[16,-2]] },
|
||||
'`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
|
||||
'a': { width: 19, points: [[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
|
||||
'b': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
|
||||
'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
|
||||
'd': { width: 19, points: [[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
|
||||
'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
|
||||
'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] },
|
||||
'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
|
||||
'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
|
||||
'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] },
|
||||
'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
|
||||
'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] },
|
||||
'l': { width: 8, points: [[4,21],[4,0]] },
|
||||
'm': { width: 30, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
|
||||
'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
|
||||
'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
|
||||
'p': { width: 19, points: [[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
|
||||
'q': { width: 19, points: [[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
|
||||
'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] },
|
||||
's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
|
||||
't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] },
|
||||
'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] },
|
||||
'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] },
|
||||
'w': { width: 22, points: [[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]] },
|
||||
'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] },
|
||||
'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
|
||||
'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] },
|
||||
'{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
|
||||
'|': { width: 8, points: [[4,25],[4,-7]] },
|
||||
'}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
|
||||
'~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }
|
||||
};
|
||||
|
||||
$.jqplot.CanvasFontRenderer = function(options) {
|
||||
options = options || {};
|
||||
if (!options.pt2px) {
|
||||
options.pt2px = 1.5;
|
||||
}
|
||||
$.jqplot.CanvasTextRenderer.call(this, options);
|
||||
};
|
||||
|
||||
$.jqplot.CanvasFontRenderer.prototype = new $.jqplot.CanvasTextRenderer({});
|
||||
$.jqplot.CanvasFontRenderer.prototype.constructor = $.jqplot.CanvasFontRenderer;
|
||||
|
||||
$.jqplot.CanvasFontRenderer.prototype.measure = function(ctx, str)
|
||||
{
|
||||
// var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
|
||||
var fstyle = this.fontSize+' '+this.fontFamily;
|
||||
ctx.save();
|
||||
ctx.font = fstyle;
|
||||
var w = ctx.measureText(str).width;
|
||||
ctx.restore();
|
||||
return w;
|
||||
};
|
||||
|
||||
$.jqplot.CanvasFontRenderer.prototype.draw = function(ctx, str)
|
||||
{
|
||||
var x = 0;
|
||||
// leave room at bottom for descenders.
|
||||
var y = this.height*0.72;
|
||||
//var y = 12;
|
||||
|
||||
ctx.save();
|
||||
var tx, ty;
|
||||
|
||||
// 1st quadrant
|
||||
if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
|
||||
tx = 0;
|
||||
ty = -Math.sin(this.angle) * this.width;
|
||||
}
|
||||
// 4th quadrant
|
||||
else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
|
||||
tx = Math.sin(this.angle) * this.height;
|
||||
ty = 0;
|
||||
}
|
||||
// 2nd quadrant
|
||||
else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
|
||||
tx = -Math.cos(this.angle) * this.width;
|
||||
ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
|
||||
}
|
||||
// 3rd quadrant
|
||||
else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
|
||||
tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
|
||||
ty = -Math.cos(this.angle) * this.height;
|
||||
}
|
||||
ctx.strokeStyle = this.fillStyle;
|
||||
ctx.fillStyle = this.fillStyle;
|
||||
// var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
|
||||
var fstyle = this.fontSize+' '+this.fontFamily;
|
||||
ctx.font = fstyle;
|
||||
ctx.translate(tx, ty);
|
||||
ctx.rotate(this.angle);
|
||||
ctx.fillText(str, x, y);
|
||||
// ctx.strokeText(str, x, y);
|
||||
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,679 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
/**
|
||||
* class: $.jqplot.CategoryAxisRenderer
|
||||
* A plugin for jqPlot to render a category style axis, with equal pixel spacing between y data values of a series.
|
||||
*
|
||||
* To use this renderer, include the plugin in your source
|
||||
* > <script type="text/javascript" language="javascript" src="plugins/jqplot.categoryAxisRenderer.js"></script>
|
||||
*
|
||||
* and supply the appropriate options to your plot
|
||||
*
|
||||
* > {axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer}}}
|
||||
**/
|
||||
$.jqplot.CategoryAxisRenderer = function(options) {
|
||||
$.jqplot.LinearAxisRenderer.call(this);
|
||||
// prop: sortMergedLabels
|
||||
// True to sort tick labels when labels are created by merging
|
||||
// x axis values from multiple series. That is, say you have
|
||||
// two series like:
|
||||
// > line1 = [[2006, 4], [2008, 9], [2009, 16]];
|
||||
// > line2 = [[2006, 3], [2007, 7], [2008, 6]];
|
||||
// If no label array is specified, tick labels will be collected
|
||||
// from the x values of the series. With sortMergedLabels
|
||||
// set to true, tick labels will be:
|
||||
// > [2006, 2007, 2008, 2009]
|
||||
// With sortMergedLabels set to false, tick labels will be:
|
||||
// > [2006, 2008, 2009, 2007]
|
||||
//
|
||||
// Note, this property is specified on the renderOptions for the
|
||||
// axes when creating a plot:
|
||||
// > axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer, rendererOptions:{sortMergedLabels:true}}}
|
||||
this.sortMergedLabels = false;
|
||||
};
|
||||
|
||||
$.jqplot.CategoryAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
|
||||
$.jqplot.CategoryAxisRenderer.prototype.constructor = $.jqplot.CategoryAxisRenderer;
|
||||
|
||||
$.jqplot.CategoryAxisRenderer.prototype.init = function(options){
|
||||
this.groups = 1;
|
||||
this.groupLabels = [];
|
||||
this._groupLabels = [];
|
||||
this._grouped = false;
|
||||
this._barsPerGroup = null;
|
||||
this.reverse = false;
|
||||
// prop: tickRenderer
|
||||
// A class of a rendering engine for creating the ticks labels displayed on the plot,
|
||||
// See <$.jqplot.AxisTickRenderer>.
|
||||
// this.tickRenderer = $.jqplot.AxisTickRenderer;
|
||||
// this.labelRenderer = $.jqplot.AxisLabelRenderer;
|
||||
$.extend(true, this, {tickOptions:{formatString:'%d'}}, options);
|
||||
var db = this._dataBounds;
|
||||
// Go through all the series attached to this axis and find
|
||||
// the min/max bounds for this axis.
|
||||
for (var i=0; i<this._series.length; i++) {
|
||||
var s = this._series[i];
|
||||
if (s.groups) {
|
||||
this.groups = s.groups;
|
||||
}
|
||||
var d = s.data;
|
||||
|
||||
for (var j=0; j<d.length; j++) {
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
if (d[j][0] < db.min || db.min == null) {
|
||||
db.min = d[j][0];
|
||||
}
|
||||
if (d[j][0] > db.max || db.max == null) {
|
||||
db.max = d[j][0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (d[j][1] < db.min || db.min == null) {
|
||||
db.min = d[j][1];
|
||||
}
|
||||
if (d[j][1] > db.max || db.max == null) {
|
||||
db.max = d[j][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.groupLabels.length) {
|
||||
this.groups = this.groupLabels.length;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$.jqplot.CategoryAxisRenderer.prototype.createTicks = function() {
|
||||
// we're are operating on an axis here
|
||||
var ticks = this._ticks;
|
||||
var userTicks = this.ticks;
|
||||
var name = this.name;
|
||||
// databounds were set on axis initialization.
|
||||
var db = this._dataBounds;
|
||||
var dim, interval;
|
||||
var min, max;
|
||||
var pos1, pos2;
|
||||
var tt, i;
|
||||
|
||||
// if we already have ticks, use them.
|
||||
if (userTicks.length) {
|
||||
// adjust with blanks if we have groups
|
||||
if (this.groups > 1 && !this._grouped) {
|
||||
var l = userTicks.length;
|
||||
var skip = parseInt(l/this.groups, 10);
|
||||
var count = 0;
|
||||
for (var i=skip; i<l; i+=skip) {
|
||||
userTicks.splice(i+count, 0, ' ');
|
||||
count++;
|
||||
}
|
||||
this._grouped = true;
|
||||
}
|
||||
this.min = 0.5;
|
||||
this.max = userTicks.length + 0.5;
|
||||
var range = this.max - this.min;
|
||||
this.numberTicks = 2*userTicks.length + 1;
|
||||
for (i=0; i<userTicks.length; i++){
|
||||
tt = this.min + 2 * i * range / (this.numberTicks-1);
|
||||
// need a marker before and after the tick
|
||||
var t = new this.tickRenderer(this.tickOptions);
|
||||
t.showLabel = false;
|
||||
// t.showMark = true;
|
||||
t.setTick(tt, this.name);
|
||||
this._ticks.push(t);
|
||||
var t = new this.tickRenderer(this.tickOptions);
|
||||
t.label = userTicks[i];
|
||||
// t.showLabel = true;
|
||||
t.showMark = false;
|
||||
t.showGridline = false;
|
||||
t.setTick(tt+0.5, this.name);
|
||||
this._ticks.push(t);
|
||||
}
|
||||
// now add the last tick at the end
|
||||
var t = new this.tickRenderer(this.tickOptions);
|
||||
t.showLabel = false;
|
||||
// t.showMark = true;
|
||||
t.setTick(tt+1, this.name);
|
||||
this._ticks.push(t);
|
||||
}
|
||||
|
||||
// we don't have any ticks yet, let's make some!
|
||||
else {
|
||||
if (name == 'xaxis' || name == 'x2axis') {
|
||||
dim = this._plotDimensions.width;
|
||||
}
|
||||
else {
|
||||
dim = this._plotDimensions.height;
|
||||
}
|
||||
|
||||
// if min, max and number of ticks specified, user can't specify interval.
|
||||
if (this.min != null && this.max != null && this.numberTicks != null) {
|
||||
this.tickInterval = null;
|
||||
}
|
||||
|
||||
// if max, min, and interval specified and interval won't fit, ignore interval.
|
||||
if (this.min != null && this.max != null && this.tickInterval != null) {
|
||||
if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) {
|
||||
this.tickInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// find out how many categories are in the lines and collect labels
|
||||
var labels = [];
|
||||
var numcats = 0;
|
||||
var min = 0.5;
|
||||
var max, val;
|
||||
var isMerged = false;
|
||||
for (var i=0; i<this._series.length; i++) {
|
||||
var s = this._series[i];
|
||||
for (var j=0; j<s.data.length; j++) {
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
val = s.data[j][0];
|
||||
}
|
||||
else {
|
||||
val = s.data[j][1];
|
||||
}
|
||||
if ($.inArray(val, labels) == -1) {
|
||||
isMerged = true;
|
||||
numcats += 1;
|
||||
labels.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMerged && this.sortMergedLabels) {
|
||||
if (typeof labels[0] == "string") {
|
||||
labels.sort();
|
||||
} else {
|
||||
labels.sort(function(a,b) { return a - b; });
|
||||
}
|
||||
}
|
||||
|
||||
// keep a reference to these tick labels to use for redrawing plot (see bug #57)
|
||||
this.ticks = labels;
|
||||
|
||||
// now bin the data values to the right lables.
|
||||
for (var i=0; i<this._series.length; i++) {
|
||||
var s = this._series[i];
|
||||
for (var j=0; j<s.data.length; j++) {
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
val = s.data[j][0];
|
||||
}
|
||||
else {
|
||||
val = s.data[j][1];
|
||||
}
|
||||
// for category axis, force the values into category bins.
|
||||
// we should have the value in the label array now.
|
||||
var idx = $.inArray(val, labels)+1;
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
s.data[j][0] = idx;
|
||||
}
|
||||
else {
|
||||
s.data[j][1] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adjust with blanks if we have groups
|
||||
if (this.groups > 1 && !this._grouped) {
|
||||
var l = labels.length;
|
||||
var skip = parseInt(l/this.groups, 10);
|
||||
var count = 0;
|
||||
for (var i=skip; i<l; i+=skip+1) {
|
||||
labels[i] = ' ';
|
||||
}
|
||||
this._grouped = true;
|
||||
}
|
||||
|
||||
max = numcats + 0.5;
|
||||
if (this.numberTicks == null) {
|
||||
this.numberTicks = 2*numcats + 1;
|
||||
}
|
||||
|
||||
var range = max - min;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
var track = 0;
|
||||
|
||||
// todo: adjust this so more ticks displayed.
|
||||
var maxVisibleTicks = parseInt(3+dim/10, 10);
|
||||
var skip = parseInt(numcats/maxVisibleTicks, 10);
|
||||
|
||||
if (this.tickInterval == null) {
|
||||
|
||||
this.tickInterval = range / (this.numberTicks-1);
|
||||
|
||||
}
|
||||
// if tickInterval is specified, we will ignore any computed maximum.
|
||||
for (var i=0; i<this.numberTicks; i++){
|
||||
tt = this.min + i * this.tickInterval;
|
||||
var t = new this.tickRenderer(this.tickOptions);
|
||||
// if even tick, it isn't a category, it's a divider
|
||||
if (i/2 == parseInt(i/2, 10)) {
|
||||
t.showLabel = false;
|
||||
t.showMark = true;
|
||||
}
|
||||
else {
|
||||
if (skip>0 && track<skip) {
|
||||
t.showLabel = false;
|
||||
track += 1;
|
||||
}
|
||||
else {
|
||||
t.showLabel = true;
|
||||
track = 0;
|
||||
}
|
||||
t.label = t.formatter(t.formatString, labels[(i-1)/2]);
|
||||
t.showMark = false;
|
||||
t.showGridline = false;
|
||||
}
|
||||
t.setTick(tt, this.name);
|
||||
this._ticks.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// called with scope of axis
|
||||
$.jqplot.CategoryAxisRenderer.prototype.draw = function(ctx, plot) {
|
||||
if (this.show) {
|
||||
// populate the axis label and value properties.
|
||||
// createTicks is a method on the renderer, but
|
||||
// call it within the scope of the axis.
|
||||
this.renderer.createTicks.call(this);
|
||||
// fill a div with axes labels in the right direction.
|
||||
// Need to pregenerate each axis to get its bounds and
|
||||
// position it and the labels correctly on the plot.
|
||||
var dim=0;
|
||||
var temp;
|
||||
// Added for theming.
|
||||
if (this._elem) {
|
||||
// this._elem.empty();
|
||||
// Memory Leaks patch
|
||||
this._elem.emptyForce();
|
||||
}
|
||||
|
||||
this._elem = this._elem || $('<div class="jqplot-axis jqplot-'+this.name+'" style="position:absolute;"></div>');
|
||||
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
this._elem.width(this._plotDimensions.width);
|
||||
}
|
||||
else {
|
||||
this._elem.height(this._plotDimensions.height);
|
||||
}
|
||||
|
||||
// create a _label object.
|
||||
this.labelOptions.axis = this.name;
|
||||
this._label = new this.labelRenderer(this.labelOptions);
|
||||
if (this._label.show) {
|
||||
var elem = this._label.draw(ctx, plot);
|
||||
elem.appendTo(this._elem);
|
||||
}
|
||||
|
||||
var t = this._ticks;
|
||||
for (var i=0; i<t.length; i++) {
|
||||
var tick = t[i];
|
||||
if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
|
||||
var elem = tick.draw(ctx, plot);
|
||||
elem.appendTo(this._elem);
|
||||
}
|
||||
}
|
||||
|
||||
this._groupLabels = [];
|
||||
// now make group labels
|
||||
for (var i=0; i<this.groupLabels.length; i++)
|
||||
{
|
||||
var elem = $('<div style="position:absolute;" class="jqplot-'+this.name+'-groupLabel"></div>');
|
||||
elem.html(this.groupLabels[i]);
|
||||
this._groupLabels.push(elem);
|
||||
elem.appendTo(this._elem);
|
||||
}
|
||||
}
|
||||
return this._elem;
|
||||
};
|
||||
|
||||
// called with scope of axis
|
||||
$.jqplot.CategoryAxisRenderer.prototype.set = function() {
|
||||
var dim = 0;
|
||||
var temp;
|
||||
var w = 0;
|
||||
var h = 0;
|
||||
var lshow = (this._label == null) ? false : this._label.show;
|
||||
if (this.show) {
|
||||
var t = this._ticks;
|
||||
for (var i=0; i<t.length; i++) {
|
||||
var tick = t[i];
|
||||
if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
temp = tick._elem.outerHeight(true);
|
||||
}
|
||||
else {
|
||||
temp = tick._elem.outerWidth(true);
|
||||
}
|
||||
if (temp > dim) {
|
||||
dim = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dim2 = 0;
|
||||
for (var i=0; i<this._groupLabels.length; i++) {
|
||||
var l = this._groupLabels[i];
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
temp = l.outerHeight(true);
|
||||
}
|
||||
else {
|
||||
temp = l.outerWidth(true);
|
||||
}
|
||||
if (temp > dim2) {
|
||||
dim2 = temp;
|
||||
}
|
||||
}
|
||||
|
||||
if (lshow) {
|
||||
w = this._label._elem.outerWidth(true);
|
||||
h = this._label._elem.outerHeight(true);
|
||||
}
|
||||
if (this.name == 'xaxis') {
|
||||
dim += dim2 + h;
|
||||
this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
|
||||
}
|
||||
else if (this.name == 'x2axis') {
|
||||
dim += dim2 + h;
|
||||
this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
|
||||
}
|
||||
else if (this.name == 'yaxis') {
|
||||
dim += dim2 + w;
|
||||
this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
|
||||
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
|
||||
this._label._elem.css('width', w+'px');
|
||||
}
|
||||
}
|
||||
else {
|
||||
dim += dim2 + w;
|
||||
this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
|
||||
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
|
||||
this._label._elem.css('width', w+'px');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// called with scope of axis
|
||||
$.jqplot.CategoryAxisRenderer.prototype.pack = function(pos, offsets) {
|
||||
var ticks = this._ticks;
|
||||
var max = this.max;
|
||||
var min = this.min;
|
||||
var offmax = offsets.max;
|
||||
var offmin = offsets.min;
|
||||
var lshow = (this._label == null) ? false : this._label.show;
|
||||
var i;
|
||||
|
||||
for (var p in pos) {
|
||||
this._elem.css(p, pos[p]);
|
||||
}
|
||||
|
||||
this._offsets = offsets;
|
||||
// pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
|
||||
var pixellength = offmax - offmin;
|
||||
var unitlength = max - min;
|
||||
|
||||
if (!this.reverse) {
|
||||
// point to unit and unit to point conversions references to Plot DOM element top left corner.
|
||||
|
||||
this.u2p = function(u){
|
||||
return (u - min) * pixellength / unitlength + offmin;
|
||||
};
|
||||
|
||||
this.p2u = function(p){
|
||||
return (p - offmin) * unitlength / pixellength + min;
|
||||
};
|
||||
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis'){
|
||||
this.series_u2p = function(u){
|
||||
return (u - min) * pixellength / unitlength;
|
||||
};
|
||||
this.series_p2u = function(p){
|
||||
return p * unitlength / pixellength + min;
|
||||
};
|
||||
}
|
||||
|
||||
else {
|
||||
this.series_u2p = function(u){
|
||||
return (u - max) * pixellength / unitlength;
|
||||
};
|
||||
this.series_p2u = function(p){
|
||||
return p * unitlength / pixellength + max;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
// point to unit and unit to point conversions references to Plot DOM element top left corner.
|
||||
|
||||
this.u2p = function(u){
|
||||
return offmin + (max - u) * pixellength / unitlength;
|
||||
};
|
||||
|
||||
this.p2u = function(p){
|
||||
return min + (p - offmin) * unitlength / pixellength;
|
||||
};
|
||||
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis'){
|
||||
this.series_u2p = function(u){
|
||||
return (max - u) * pixellength / unitlength;
|
||||
};
|
||||
this.series_p2u = function(p){
|
||||
return p * unitlength / pixellength + max;
|
||||
};
|
||||
}
|
||||
|
||||
else {
|
||||
this.series_u2p = function(u){
|
||||
return (min - u) * pixellength / unitlength;
|
||||
};
|
||||
this.series_p2u = function(p){
|
||||
return p * unitlength / pixellength + min;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (this.show) {
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
for (i=0; i<ticks.length; i++) {
|
||||
var t = ticks[i];
|
||||
if (t.show && t.showLabel) {
|
||||
var shim;
|
||||
|
||||
if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
|
||||
// will need to adjust auto positioning based on which axis this is.
|
||||
var temp = (this.name == 'xaxis') ? 1 : -1;
|
||||
switch (t.labelPosition) {
|
||||
case 'auto':
|
||||
// position at end
|
||||
if (temp * t.angle < 0) {
|
||||
shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
|
||||
}
|
||||
// position at start
|
||||
else {
|
||||
shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
|
||||
}
|
||||
break;
|
||||
case 'end':
|
||||
shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
|
||||
break;
|
||||
case 'start':
|
||||
shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
|
||||
break;
|
||||
case 'middle':
|
||||
shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
|
||||
break;
|
||||
default:
|
||||
shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
shim = -t.getWidth()/2;
|
||||
}
|
||||
var val = this.u2p(t.value) + shim + 'px';
|
||||
t._elem.css('left', val);
|
||||
t.pack();
|
||||
}
|
||||
}
|
||||
|
||||
var labeledge=['bottom', 0];
|
||||
if (lshow) {
|
||||
var w = this._label._elem.outerWidth(true);
|
||||
this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
|
||||
if (this.name == 'xaxis') {
|
||||
this._label._elem.css('bottom', '0px');
|
||||
labeledge = ['bottom', this._label._elem.outerHeight(true)];
|
||||
}
|
||||
else {
|
||||
this._label._elem.css('top', '0px');
|
||||
labeledge = ['top', this._label._elem.outerHeight(true)];
|
||||
}
|
||||
this._label.pack();
|
||||
}
|
||||
|
||||
// draw the group labels
|
||||
var step = parseInt(this._ticks.length/this.groups, 10) + 1;
|
||||
for (i=0; i<this._groupLabels.length; i++) {
|
||||
var mid = 0;
|
||||
var count = 0;
|
||||
for (var j=i*step; j<(i+1)*step; j++) {
|
||||
if (j >= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
|
||||
if (this._ticks[j]._elem && this._ticks[j].label != " ") {
|
||||
var t = this._ticks[j]._elem;
|
||||
var p = t.position();
|
||||
mid += p.left + t.outerWidth(true)/2;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
mid = mid/count;
|
||||
this._groupLabels[i].css({'left':(mid - this._groupLabels[i].outerWidth(true)/2)});
|
||||
this._groupLabels[i].css(labeledge[0], labeledge[1]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i=0; i<ticks.length; i++) {
|
||||
var t = ticks[i];
|
||||
if (t.show && t.showLabel) {
|
||||
var shim;
|
||||
if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
|
||||
// will need to adjust auto positioning based on which axis this is.
|
||||
var temp = (this.name == 'yaxis') ? 1 : -1;
|
||||
switch (t.labelPosition) {
|
||||
case 'auto':
|
||||
// position at end
|
||||
case 'end':
|
||||
if (temp * t.angle < 0) {
|
||||
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
|
||||
}
|
||||
else {
|
||||
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
|
||||
}
|
||||
break;
|
||||
case 'start':
|
||||
if (t.angle > 0) {
|
||||
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
|
||||
}
|
||||
else {
|
||||
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
|
||||
}
|
||||
break;
|
||||
case 'middle':
|
||||
// if (t.angle > 0) {
|
||||
// shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
|
||||
// }
|
||||
// else {
|
||||
// shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
|
||||
// }
|
||||
shim = -t.getHeight()/2;
|
||||
break;
|
||||
default:
|
||||
shim = -t.getHeight()/2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
shim = -t.getHeight()/2;
|
||||
}
|
||||
|
||||
var val = this.u2p(t.value) + shim + 'px';
|
||||
t._elem.css('top', val);
|
||||
t.pack();
|
||||
}
|
||||
}
|
||||
|
||||
var labeledge=['left', 0];
|
||||
if (lshow) {
|
||||
var h = this._label._elem.outerHeight(true);
|
||||
this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
|
||||
if (this.name == 'yaxis') {
|
||||
this._label._elem.css('left', '0px');
|
||||
labeledge = ['left', this._label._elem.outerWidth(true)];
|
||||
}
|
||||
else {
|
||||
this._label._elem.css('right', '0px');
|
||||
labeledge = ['right', this._label._elem.outerWidth(true)];
|
||||
}
|
||||
this._label.pack();
|
||||
}
|
||||
|
||||
// draw the group labels, position top here, do left after label position.
|
||||
var step = parseInt(this._ticks.length/this.groups, 10) + 1; // step is one more than before as we don't want to have overlaps in loops
|
||||
for (i=0; i<this._groupLabels.length; i++) {
|
||||
var mid = 0;
|
||||
var count = 0;
|
||||
for (var j=i*step; j<(i+1)*step; j++) { // j must never reach (i+1)*step as we don't want to have overlap between loops
|
||||
if (j >= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
|
||||
if (this._ticks[j]._elem && this._ticks[j].label != " ") {
|
||||
var t = this._ticks[j]._elem;
|
||||
var p = t.position();
|
||||
mid += p.top + t.outerHeight()/2;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
mid = mid/count;
|
||||
this._groupLabels[i].css({'top':mid - this._groupLabels[i].outerHeight()/2});
|
||||
this._groupLabels[i].css(labeledge[0], labeledge[1]);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
})(jQuery);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,741 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
/**
|
||||
* Class: $.jqplot.DateAxisRenderer
|
||||
* A plugin for a jqPlot to render an axis as a series of date values.
|
||||
* This renderer has no options beyond those supplied by the <Axis> class.
|
||||
* It supplies its own tick formatter, so the tickOptions.formatter option
|
||||
* should not be overridden.
|
||||
*
|
||||
* Thanks to Ken Synder for his enhanced Date instance methods which are
|
||||
* included with this code <http://kendsnyder.com/sandbox/date/>.
|
||||
*
|
||||
* To use this renderer, include the plugin in your source
|
||||
* > <script type="text/javascript" language="javascript" src="plugins/jqplot.dateAxisRenderer.js"></script>
|
||||
*
|
||||
* and supply the appropriate options to your plot
|
||||
*
|
||||
* > {axes:{xaxis:{renderer:$.jqplot.DateAxisRenderer}}}
|
||||
*
|
||||
* Dates can be passed into the axis in almost any recognizable value and
|
||||
* will be parsed. They will be rendered on the axis in the format
|
||||
* specified by tickOptions.formatString. e.g. tickOptions.formatString = '%Y-%m-%d'.
|
||||
*
|
||||
* Accecptable format codes
|
||||
* are:
|
||||
*
|
||||
* > Code Result Description
|
||||
* > == Years ==
|
||||
* > %Y 2008 Four-digit year
|
||||
* > %y 08 Two-digit year
|
||||
* > == Months ==
|
||||
* > %m 09 Two-digit month
|
||||
* > %#m 9 One or two-digit month
|
||||
* > %B September Full month name
|
||||
* > %b Sep Abbreviated month name
|
||||
* > == Days ==
|
||||
* > %d 05 Two-digit day of month
|
||||
* > %#d 5 One or two-digit day of month
|
||||
* > %e 5 One or two-digit day of month
|
||||
* > %A Sunday Full name of the day of the week
|
||||
* > %a Sun Abbreviated name of the day of the week
|
||||
* > %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
|
||||
* > %o th The ordinal suffix string following the day of the month
|
||||
* > == Hours ==
|
||||
* > %H 23 Hours in 24-hour format (two digits)
|
||||
* > %#H 3 Hours in 24-hour integer format (one or two digits)
|
||||
* > %I 11 Hours in 12-hour format (two digits)
|
||||
* > %#I 3 Hours in 12-hour integer format (one or two digits)
|
||||
* > %p PM AM or PM
|
||||
* > == Minutes ==
|
||||
* > %M 09 Minutes (two digits)
|
||||
* > %#M 9 Minutes (one or two digits)
|
||||
* > == Seconds ==
|
||||
* > %S 02 Seconds (two digits)
|
||||
* > %#S 2 Seconds (one or two digits)
|
||||
* > %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
|
||||
* > == Milliseconds ==
|
||||
* > %N 008 Milliseconds (three digits)
|
||||
* > %#N 8 Milliseconds (one to three digits)
|
||||
* > == Timezone ==
|
||||
* > %O 360 difference in minutes between local time and GMT
|
||||
* > %Z Mountain Standard Time Name of timezone as reported by browser
|
||||
* > %G -06:00 Hours and minutes between GMT
|
||||
* > == Shortcuts ==
|
||||
* > %F 2008-03-26 %Y-%m-%d
|
||||
* > %T 05:06:30 %H:%M:%S
|
||||
* > %X 05:06:30 %H:%M:%S
|
||||
* > %x 03/26/08 %m/%d/%y
|
||||
* > %D 03/26/08 %m/%d/%y
|
||||
* > %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
|
||||
* > %v 3-Sep-2008 %e-%b-%Y
|
||||
* > %R 15:31 %H:%M
|
||||
* > %r 3:31:00 PM %I:%M:%S %p
|
||||
* > == Characters ==
|
||||
* > %n \n Newline
|
||||
* > %t \t Tab
|
||||
* > %% % Percent Symbol
|
||||
*/
|
||||
$.jqplot.DateAxisRenderer = function() {
|
||||
$.jqplot.LinearAxisRenderer.call(this);
|
||||
this.date = new $.jsDate();
|
||||
};
|
||||
|
||||
var second = 1000;
|
||||
var minute = 60 * second;
|
||||
var hour = 60 * minute;
|
||||
var day = 24 * hour;
|
||||
var week = 7 * day;
|
||||
|
||||
// these are less definitive
|
||||
var month = 30.4368499 * day;
|
||||
var year = 365.242199 * day;
|
||||
|
||||
var daysInMonths = [31,28,31,30,31,30,31,30,31,30,31,30];
|
||||
// array of consistent nice intervals. Longer intervals
|
||||
// will depend on days in month, days in year, etc.
|
||||
var niceFormatStrings = ['%M:%S.%#N', '%M:%S.%#N', '%M:%S.%#N', '%M:%S', '%M:%S', '%M:%S', '%M:%S', '%H:%M:%S', '%H:%M:%S', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%a %H:%M', '%a %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%v', '%v', '%v', '%v', '%v', '%v', '%v'];
|
||||
var niceIntervals = [0.1*second, 0.2*second, 0.5*second, second, 2*second, 5*second, 10*second, 15*second, 30*second, minute, 2*minute, 5*minute, 10*minute, 15*minute, 30*minute, hour, 2*hour, 4*hour, 6*hour, 8*hour, 12*hour, day, 2*day, 3*day, 4*day, 5*day, week, 2*week];
|
||||
|
||||
var niceMonthlyIntervals = [];
|
||||
|
||||
function bestDateInterval(min, max, titarget) {
|
||||
// iterate through niceIntervals to find one closest to titarget
|
||||
var badness = Number.MAX_VALUE;
|
||||
var temp, bestTi, bestfmt;
|
||||
for (var i=0, l=niceIntervals.length; i < l; i++) {
|
||||
temp = Math.abs(titarget - niceIntervals[i]);
|
||||
if (temp < badness) {
|
||||
badness = temp;
|
||||
bestTi = niceIntervals[i];
|
||||
bestfmt = niceFormatStrings[i];
|
||||
}
|
||||
}
|
||||
|
||||
return [bestTi, bestfmt];
|
||||
}
|
||||
|
||||
$.jqplot.DateAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
|
||||
$.jqplot.DateAxisRenderer.prototype.constructor = $.jqplot.DateAxisRenderer;
|
||||
|
||||
$.jqplot.DateTickFormatter = function(format, val) {
|
||||
if (!format) {
|
||||
format = '%Y/%m/%d';
|
||||
}
|
||||
return $.jsDate.strftime(val, format);
|
||||
};
|
||||
|
||||
$.jqplot.DateAxisRenderer.prototype.init = function(options){
|
||||
// prop: tickRenderer
|
||||
// A class of a rendering engine for creating the ticks labels displayed on the plot,
|
||||
// See <$.jqplot.AxisTickRenderer>.
|
||||
// this.tickRenderer = $.jqplot.AxisTickRenderer;
|
||||
// this.labelRenderer = $.jqplot.AxisLabelRenderer;
|
||||
this.tickOptions.formatter = $.jqplot.DateTickFormatter;
|
||||
// prop: tickInset
|
||||
// Controls the amount to inset the first and last ticks from
|
||||
// the edges of the grid, in multiples of the tick interval.
|
||||
// 0 is no inset, 0.5 is one half a tick interval, 1 is a full
|
||||
// tick interval, etc.
|
||||
this.tickInset = 0;
|
||||
// prop: drawBaseline
|
||||
// True to draw the axis baseline.
|
||||
this.drawBaseline = true;
|
||||
// prop: baselineWidth
|
||||
// width of the baseline in pixels.
|
||||
this.baselineWidth = null;
|
||||
// prop: baselineColor
|
||||
// CSS color spec for the baseline.
|
||||
this.baselineColor = null;
|
||||
this.daTickInterval = null;
|
||||
this._daTickInterval = null;
|
||||
|
||||
$.extend(true, this, options);
|
||||
|
||||
var db = this._dataBounds,
|
||||
stats,
|
||||
sum,
|
||||
s,
|
||||
d,
|
||||
pd,
|
||||
sd,
|
||||
intv;
|
||||
|
||||
// Go through all the series attached to this axis and find
|
||||
// the min/max bounds for this axis.
|
||||
for (var i=0; i<this._series.length; i++) {
|
||||
stats = {intervals:[], frequencies:{}, sortedIntervals:[], min:null, max:null, mean:null};
|
||||
sum = 0;
|
||||
s = this._series[i];
|
||||
d = s.data;
|
||||
pd = s._plotData;
|
||||
sd = s._stackData;
|
||||
intv = 0;
|
||||
|
||||
for (var j=0; j<d.length; j++) {
|
||||
if (this.name == 'xaxis' || this.name == 'x2axis') {
|
||||
d[j][0] = new $.jsDate(d[j][0]).getTime();
|
||||
pd[j][0] = new $.jsDate(d[j][0]).getTime();
|
||||
sd[j][0] = new $.jsDate(d[j][0]).getTime();
|
||||
if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
|
||||
db.min = d[j][0];
|
||||
}
|
||||
if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
|
||||
db.max = d[j][0];
|
||||
}
|
||||
if (j>0) {
|
||||
intv = Math.abs(d[j][0] - d[j-1][0]);
|
||||
stats.intervals.push(intv);
|
||||
if (stats.frequencies.hasOwnProperty(intv)) {
|
||||
stats.frequencies[intv] += 1;
|
||||
}
|
||||
else {
|
||||
stats.frequencies[intv] = 1;
|
||||
}
|
||||
}
|
||||
sum += intv;
|
||||
|
||||
}
|
||||
else {
|
||||
d[j][1] = new $.jsDate(d[j][1]).getTime();
|
||||
pd[j][1] = new $.jsDate(d[j][1]).getTime();
|
||||
sd[j][1] = new $.jsDate(d[j][1]).getTime();
|
||||
if ((d[j][1] != null && d[j][1] < db.min) || db.min == null) {
|
||||
db.min = d[j][1];
|
||||
}
|
||||
if ((d[j][1] != null && d[j][1] > db.max) || db.max == null) {
|
||||
db.max = d[j][1];
|
||||
}
|
||||
if (j>0) {
|
||||
intv = Math.abs(d[j][1] - d[j-1][1]);
|
||||
stats.intervals.push(intv);
|
||||
if (stats.frequencies.hasOwnProperty(intv)) {
|
||||
stats.frequencies[intv] += 1;
|
||||
}
|
||||
else {
|
||||
stats.frequencies[intv] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
sum += intv;
|
||||
}
|
||||
|
||||
if (s.renderer.bands) {
|
||||
if (s.renderer.bands.hiData.length) {
|
||||
var bd = s.renderer.bands.hiData;
|
||||
for (var j=0, l=bd.length; j < l; j++) {
|
||||
if (this.name === 'xaxis' || this.name === 'x2axis') {
|
||||
bd[j][0] = new $.jsDate(bd[j][0]).getTime();
|
||||
if ((bd[j][0] != null && bd[j][0] > db.max) || db.max == null) {
|
||||
db.max = bd[j][0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
bd[j][1] = new $.jsDate(bd[j][1]).getTime();
|
||||
if ((bd[j][1] != null && bd[j][1] > db.max) || db.max == null) {
|
||||
db.max = bd[j][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.renderer.bands.lowData.length) {
|
||||
var bd = s.renderer.bands.lowData;
|
||||
for (var j=0, l=bd.length; j < l; j++) {
|
||||
if (this.name === 'xaxis' || this.name === 'x2axis') {
|
||||
bd[j][0] = new $.jsDate(bd[j][0]).getTime();
|
||||
if ((bd[j][0] != null && bd[j][0] < db.min) || db.min == null) {
|
||||
db.min = bd[j][0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
bd[j][1] = new $.jsDate(bd[j][1]).getTime();
|
||||
if ((bd[j][1] != null && bd[j][1] < db.min) || db.min == null) {
|
||||
db.min = bd[j][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tempf = 0,
|
||||
tempn=0;
|
||||
for (var n in stats.frequencies) {
|
||||
stats.sortedIntervals.push({interval:n, frequency:stats.frequencies[n]});
|
||||
}
|
||||
stats.sortedIntervals.sort(function(a, b){
|
||||
return b.frequency - a.frequency;
|
||||
});
|
||||
|
||||
stats.min = $.jqplot.arrayMin(stats.intervals);
|
||||
stats.max = $.jqplot.arrayMax(stats.intervals);
|
||||
stats.mean = sum/d.length;
|
||||
this._intervalStats.push(stats);
|
||||
stats = sum = s = d = pd = sd = null;
|
||||
}
|
||||
db = null;
|
||||
|
||||
};
|
||||
|
||||
// called with scope of an axis
|
||||
$.jqplot.DateAxisRenderer.prototype.reset = function() {
|
||||
this.min = this._options.min;
|
||||
this.max = this._options.max;
|
||||
this.tickInterval = this._options.tickInterval;
|
||||
this.numberTicks = this._options.numberTicks;
|
||||
this._autoFormatString = '';
|
||||
if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
|
||||
this.tickOptions.formatString = '';
|
||||
}
|
||||
this.daTickInterval = this._daTickInterval;
|
||||
// this._ticks = this.__ticks;
|
||||
};
|
||||
|
||||
$.jqplot.DateAxisRenderer.prototype.createTicks = function(plot) {
|
||||
// we're are operating on an axis here
|
||||
var ticks = this._ticks;
|
||||
var userTicks = this.ticks;
|
||||
var name = this.name;
|
||||
// databounds were set on axis initialization.
|
||||
var db = this._dataBounds;
|
||||
var iv = this._intervalStats;
|
||||
var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
|
||||
var interval;
|
||||
var min, max;
|
||||
var pos1, pos2;
|
||||
var tt, i;
|
||||
var threshold = 30;
|
||||
var insetMult = 1;
|
||||
var daTickInterval = null;
|
||||
|
||||
// if user specified a tick interval, convert to usable.
|
||||
if (this.tickInterval != null)
|
||||
{
|
||||
// if interval is a number or can be converted to one, use it.
|
||||
// Assume it is in SECONDS!!!
|
||||
if (Number(this.tickInterval)) {
|
||||
daTickInterval = [Number(this.tickInterval), 'seconds'];
|
||||
}
|
||||
// else, parse out something we can build from.
|
||||
else if (typeof this.tickInterval == "string") {
|
||||
var parts = this.tickInterval.split(' ');
|
||||
if (parts.length == 1) {
|
||||
daTickInterval = [1, parts[0]];
|
||||
}
|
||||
else if (parts.length == 2) {
|
||||
daTickInterval = [parts[0], parts[1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tickInterval = this.tickInterval;
|
||||
|
||||
// if we already have ticks, use them.
|
||||
// ticks must be in order of increasing value.
|
||||
|
||||
min = new $.jsDate((this.min != null) ? this.min : db.min).getTime();
|
||||
max = new $.jsDate((this.max != null) ? this.max : db.max).getTime();
|
||||
|
||||
// see if we're zooming. if we are, don't use the min and max we're given,
|
||||
// but compute some nice ones. They will be reset later.
|
||||
|
||||
var cursor = plot.plugins.cursor;
|
||||
|
||||
if (cursor && cursor._zoom && cursor._zoom.zooming) {
|
||||
this.min = null;
|
||||
this.max = null;
|
||||
}
|
||||
|
||||
var range = max - min;
|
||||
|
||||
if (this.tickOptions == null || !this.tickOptions.formatString) {
|
||||
this._overrideFormatString = true;
|
||||
}
|
||||
|
||||
if (userTicks.length) {
|
||||
// ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
|
||||
for (i=0; i<userTicks.length; i++){
|
||||
var ut = userTicks[i];
|
||||
var t = new this.tickRenderer(this.tickOptions);
|
||||
if (ut.constructor == Array) {
|
||||
t.value = new $.jsDate(ut[0]).getTime();
|
||||
t.label = ut[1];
|
||||
if (!this.showTicks) {
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
}
|
||||
else if (!this.showTickMarks) {
|
||||
t.showMark = false;
|
||||
}
|
||||
t.setTick(t.value, this.name);
|
||||
this._ticks.push(t);
|
||||
}
|
||||
|
||||
else {
|
||||
t.value = new $.jsDate(ut).getTime();
|
||||
if (!this.showTicks) {
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
}
|
||||
else if (!this.showTickMarks) {
|
||||
t.showMark = false;
|
||||
}
|
||||
t.setTick(t.value, this.name);
|
||||
this._ticks.push(t);
|
||||
}
|
||||
}
|
||||
this.numberTicks = userTicks.length;
|
||||
this.min = this._ticks[0].value;
|
||||
this.max = this._ticks[this.numberTicks-1].value;
|
||||
this.daTickInterval = [(this.max - this.min) / (this.numberTicks - 1)/1000, 'seconds'];
|
||||
}
|
||||
|
||||
////////
|
||||
// We don't have any ticks yet, let's make some!
|
||||
////////
|
||||
|
||||
// special case when there is only one point, make three tick marks to center the point
|
||||
else if (this.min == null && this.max == null && db.min == db.max)
|
||||
{
|
||||
var onePointOpts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
|
||||
var delta = 300000;
|
||||
this.min = db.min - delta;
|
||||
this.max = db.max + delta;
|
||||
this.numberTicks = 3;
|
||||
|
||||
for(var i=this.min;i<=this.max;i+= delta)
|
||||
{
|
||||
onePointOpts.value = i;
|
||||
|
||||
var t = new this.tickRenderer(onePointOpts);
|
||||
|
||||
if (this._overrideFormatString && this._autoFormatString != '') {
|
||||
t.formatString = this._autoFormatString;
|
||||
}
|
||||
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
|
||||
this._ticks.push(t);
|
||||
}
|
||||
|
||||
if(this.showTicks) {
|
||||
this._ticks[1].showLabel = true;
|
||||
}
|
||||
if(this.showTickMarks) {
|
||||
this._ticks[1].showTickMarks = true;
|
||||
}
|
||||
}
|
||||
// if user specified min and max are null, we set those to make best ticks.
|
||||
else if (this.min == null && this.max == null) {
|
||||
|
||||
var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
|
||||
|
||||
// want to find a nice interval
|
||||
var nttarget,
|
||||
titarget;
|
||||
|
||||
// if no tickInterval or numberTicks options specified, make a good guess.
|
||||
if (!this.tickInterval && !this.numberTicks) {
|
||||
var tdim = Math.max(dim, threshold+1);
|
||||
// how many ticks to put on the axis?
|
||||
// date labels tend to be long. If ticks not rotated,
|
||||
// don't use too many and have a high spacing factor.
|
||||
// If we are rotating ticks, use a lower factor.
|
||||
var spacingFactor = 115;
|
||||
if (this.tickRenderer === $.jqplot.CanvasAxisTickRenderer && this.tickOptions.angle) {
|
||||
spacingFactor = 115 - 40 * Math.abs(Math.sin(this.tickOptions.angle/180*Math.PI));
|
||||
}
|
||||
|
||||
nttarget = Math.ceil((tdim-threshold)/spacingFactor + 1);
|
||||
titarget = (max - min) / (nttarget - 1);
|
||||
}
|
||||
|
||||
// If tickInterval is specified, we'll try to honor it.
|
||||
// Not guaranteed to get this interval, but we'll get as close as
|
||||
// we can.
|
||||
// tickInterval will be used before numberTicks, that is if
|
||||
// both are specified, numberTicks will be ignored.
|
||||
else if (this.tickInterval) {
|
||||
titarget = new $.jsDate(0).add(daTickInterval[0], daTickInterval[1]).getTime();
|
||||
}
|
||||
|
||||
// if numberTicks specified, try to honor it.
|
||||
// Not guaranteed, but will try to get close.
|
||||
else if (this.numberTicks) {
|
||||
nttarget = this.numberTicks;
|
||||
titarget = (max - min) / (nttarget - 1);
|
||||
}
|
||||
|
||||
// If we can use an interval of 2 weeks or less, pick best one
|
||||
if (titarget <= 19*day) {
|
||||
var ret = bestDateInterval(min, max, titarget);
|
||||
var tempti = ret[0];
|
||||
this._autoFormatString = ret[1];
|
||||
|
||||
min = new $.jsDate(min);
|
||||
min = Math.floor((min.getTime() - min.getUtcOffset())/tempti) * tempti + min.getUtcOffset();
|
||||
|
||||
nttarget = Math.ceil((max - min) / tempti) + 1;
|
||||
this.min = min;
|
||||
this.max = min + (nttarget - 1) * tempti;
|
||||
|
||||
// if max is less than max, add an interval
|
||||
if (this.max < max) {
|
||||
this.max += tempti;
|
||||
nttarget += 1;
|
||||
}
|
||||
this.tickInterval = tempti;
|
||||
this.numberTicks = nttarget;
|
||||
|
||||
for (var i=0; i<nttarget; i++) {
|
||||
opts.value = this.min + i * tempti;
|
||||
t = new this.tickRenderer(opts);
|
||||
|
||||
if (this._overrideFormatString && this._autoFormatString != '') {
|
||||
t.formatString = this._autoFormatString;
|
||||
}
|
||||
if (!this.showTicks) {
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
}
|
||||
else if (!this.showTickMarks) {
|
||||
t.showMark = false;
|
||||
}
|
||||
this._ticks.push(t);
|
||||
}
|
||||
|
||||
insetMult = this.tickInterval;
|
||||
}
|
||||
|
||||
// should we use a monthly interval?
|
||||
else if (titarget <= 9 * month) {
|
||||
|
||||
this._autoFormatString = '%v';
|
||||
|
||||
// how many months in an interval?
|
||||
var intv = Math.round(titarget/month);
|
||||
if (intv < 1) {
|
||||
intv = 1;
|
||||
}
|
||||
else if (intv > 6) {
|
||||
intv = 6;
|
||||
}
|
||||
|
||||
// figure out the starting month and ending month.
|
||||
var mstart = new $.jsDate(min).setDate(1).setHours(0,0,0,0);
|
||||
|
||||
// See if max ends exactly on a month
|
||||
var tempmend = new $.jsDate(max);
|
||||
var mend = new $.jsDate(max).setDate(1).setHours(0,0,0,0);
|
||||
|
||||
if (tempmend.getTime() !== mend.getTime()) {
|
||||
mend = mend.add(1, 'month');
|
||||
}
|
||||
|
||||
var nmonths = mend.diff(mstart, 'month');
|
||||
|
||||
nttarget = Math.ceil(nmonths/intv) + 1;
|
||||
|
||||
this.min = mstart.getTime();
|
||||
this.max = mstart.clone().add((nttarget - 1) * intv, 'month').getTime();
|
||||
this.numberTicks = nttarget;
|
||||
|
||||
for (var i=0; i<nttarget; i++) {
|
||||
if (i === 0) {
|
||||
opts.value = mstart.getTime();
|
||||
}
|
||||
else {
|
||||
opts.value = mstart.add(intv, 'month').getTime();
|
||||
}
|
||||
t = new this.tickRenderer(opts);
|
||||
|
||||
if (this._overrideFormatString && this._autoFormatString != '') {
|
||||
t.formatString = this._autoFormatString;
|
||||
}
|
||||
if (!this.showTicks) {
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
}
|
||||
else if (!this.showTickMarks) {
|
||||
t.showMark = false;
|
||||
}
|
||||
this._ticks.push(t);
|
||||
}
|
||||
|
||||
insetMult = intv * month;
|
||||
}
|
||||
|
||||
// use yearly intervals
|
||||
else {
|
||||
|
||||
this._autoFormatString = '%v';
|
||||
|
||||
// how many years in an interval?
|
||||
var intv = Math.round(titarget/year);
|
||||
if (intv < 1) {
|
||||
intv = 1;
|
||||
}
|
||||
|
||||
// figure out the starting and ending years.
|
||||
var mstart = new $.jsDate(min).setMonth(0, 1).setHours(0,0,0,0);
|
||||
var mend = new $.jsDate(max).add(1, 'year').setMonth(0, 1).setHours(0,0,0,0);
|
||||
|
||||
var nyears = mend.diff(mstart, 'year');
|
||||
|
||||
nttarget = Math.ceil(nyears/intv) + 1;
|
||||
|
||||
this.min = mstart.getTime();
|
||||
this.max = mstart.clone().add((nttarget - 1) * intv, 'year').getTime();
|
||||
this.numberTicks = nttarget;
|
||||
|
||||
for (var i=0; i<nttarget; i++) {
|
||||
if (i === 0) {
|
||||
opts.value = mstart.getTime();
|
||||
}
|
||||
else {
|
||||
opts.value = mstart.add(intv, 'year').getTime();
|
||||
}
|
||||
t = new this.tickRenderer(opts);
|
||||
|
||||
if (this._overrideFormatString && this._autoFormatString != '') {
|
||||
t.formatString = this._autoFormatString;
|
||||
}
|
||||
if (!this.showTicks) {
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
}
|
||||
else if (!this.showTickMarks) {
|
||||
t.showMark = false;
|
||||
}
|
||||
this._ticks.push(t);
|
||||
}
|
||||
|
||||
insetMult = intv * year;
|
||||
}
|
||||
}
|
||||
|
||||
////////
|
||||
// Some option(s) specified, work around that.
|
||||
////////
|
||||
|
||||
else {
|
||||
if (name == 'xaxis' || name == 'x2axis') {
|
||||
dim = this._plotDimensions.width;
|
||||
}
|
||||
else {
|
||||
dim = this._plotDimensions.height;
|
||||
}
|
||||
|
||||
// if min, max and number of ticks specified, user can't specify interval.
|
||||
if (this.min != null && this.max != null && this.numberTicks != null) {
|
||||
this.tickInterval = null;
|
||||
}
|
||||
|
||||
if (this.tickInterval != null && daTickInterval != null) {
|
||||
this.daTickInterval = daTickInterval;
|
||||
}
|
||||
|
||||
// if min and max are same, space them out a bit
|
||||
if (min == max) {
|
||||
var adj = 24*60*60*500; // 1/2 day
|
||||
min -= adj;
|
||||
max += adj;
|
||||
}
|
||||
|
||||
range = max - min;
|
||||
|
||||
var optNumTicks = 2 + parseInt(Math.max(0, dim-100)/100, 10);
|
||||
|
||||
|
||||
var rmin, rmax;
|
||||
|
||||
rmin = (this.min != null) ? new $.jsDate(this.min).getTime() : min - range/2*(this.padMin - 1);
|
||||
rmax = (this.max != null) ? new $.jsDate(this.max).getTime() : max + range/2*(this.padMax - 1);
|
||||
this.min = rmin;
|
||||
this.max = rmax;
|
||||
range = this.max - this.min;
|
||||
|
||||
if (this.numberTicks == null){
|
||||
// if tickInterval is specified by user, we will ignore computed maximum.
|
||||
// max will be equal or greater to fit even # of ticks.
|
||||
if (this.daTickInterval != null) {
|
||||
var nc = new $.jsDate(this.max).diff(this.min, this.daTickInterval[1], true);
|
||||
this.numberTicks = Math.ceil(nc/this.daTickInterval[0]) +1;
|
||||
// this.max = new $.jsDate(this.min).add(this.numberTicks-1, this.daTickInterval[1]).getTime();
|
||||
this.max = new $.jsDate(this.min).add((this.numberTicks-1) * this.daTickInterval[0], this.daTickInterval[1]).getTime();
|
||||
}
|
||||
else if (dim > 200) {
|
||||
this.numberTicks = parseInt(3+(dim-200)/100, 10);
|
||||
}
|
||||
else {
|
||||
this.numberTicks = 2;
|
||||
}
|
||||
}
|
||||
|
||||
insetMult = range / (this.numberTicks-1)/1000;
|
||||
|
||||
if (this.daTickInterval == null) {
|
||||
this.daTickInterval = [insetMult, 'seconds'];
|
||||
}
|
||||
|
||||
|
||||
for (var i=0; i<this.numberTicks; i++){
|
||||
var min = new $.jsDate(this.min);
|
||||
tt = min.add(i*this.daTickInterval[0], this.daTickInterval[1]).getTime();
|
||||
var t = new this.tickRenderer(this.tickOptions);
|
||||
// var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
|
||||
if (!this.showTicks) {
|
||||
t.showLabel = false;
|
||||
t.showMark = false;
|
||||
}
|
||||
else if (!this.showTickMarks) {
|
||||
t.showMark = false;
|
||||
}
|
||||
t.setTick(tt, this.name);
|
||||
this._ticks.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tickInset) {
|
||||
this.min = this.min - this.tickInset * insetMult;
|
||||
this.max = this.max + this.tickInset * insetMult;
|
||||
}
|
||||
|
||||
if (this._daTickInterval == null) {
|
||||
this._daTickInterval = this.daTickInterval;
|
||||
}
|
||||
|
||||
ticks = null;
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
|
|
@ -0,0 +1,465 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
|
||||
|
||||
/**
|
||||
* Class: $.jqplot.Highlighter
|
||||
* Plugin which will highlight data points when they are moused over.
|
||||
*
|
||||
* To use this plugin, include the js
|
||||
* file in your source:
|
||||
*
|
||||
* > <script type="text/javascript" src="plugins/jqplot.highlighter.js"></script>
|
||||
*
|
||||
* A tooltip providing information about the data point is enabled by default.
|
||||
* To disable the tooltip, set "showTooltip" to false.
|
||||
*
|
||||
* You can control what data is displayed in the tooltip with various
|
||||
* options. The "tooltipAxes" option controls whether the x, y or both
|
||||
* data values are displayed.
|
||||
*
|
||||
* Some chart types (e.g. hi-low-close) have more than one y value per
|
||||
* data point. To display the additional values in the tooltip, set the
|
||||
* "yvalues" option to the desired number of y values present (3 for a hlc chart).
|
||||
*
|
||||
* By default, data values will be formatted with the same formatting
|
||||
* specifiers as used to format the axis ticks. A custom format code
|
||||
* can be supplied with the tooltipFormatString option. This will apply
|
||||
* to all values in the tooltip.
|
||||
*
|
||||
* For more complete control, the "formatString" option can be set. This
|
||||
* Allows conplete control over tooltip formatting. Values are passed to
|
||||
* the format string in an order determined by the "tooltipAxes" and "yvalues"
|
||||
* options. So, if you have a hi-low-close chart and you just want to display
|
||||
* the hi-low-close values in the tooltip, you could set a formatString like:
|
||||
*
|
||||
* > highlighter: {
|
||||
* > tooltipAxes: 'y',
|
||||
* > yvalues: 3,
|
||||
* > formatString:'<table class="jqplot-highlighter">
|
||||
* > <tr><td>hi:</td><td>%s</td></tr>
|
||||
* > <tr><td>low:</td><td>%s</td></tr>
|
||||
* > <tr><td>close:</td><td>%s</td></tr></table>'
|
||||
* > }
|
||||
*
|
||||
*/
|
||||
$.jqplot.Highlighter = function(options) {
|
||||
// Group: Properties
|
||||
//
|
||||
//prop: show
|
||||
// true to show the highlight.
|
||||
this.show = $.jqplot.config.enablePlugins;
|
||||
// prop: markerRenderer
|
||||
// Renderer used to draw the marker of the highlighted point.
|
||||
// Renderer will assimilate attributes from the data point being highlighted,
|
||||
// so no attributes need set on the renderer directly.
|
||||
// Default is to turn off shadow drawing on the highlighted point.
|
||||
this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
|
||||
// prop: showMarker
|
||||
// true to show the marker
|
||||
this.showMarker = true;
|
||||
// prop: lineWidthAdjust
|
||||
// Pixels to add to the lineWidth of the highlight.
|
||||
this.lineWidthAdjust = 2.5;
|
||||
// prop: sizeAdjust
|
||||
// Pixels to add to the overall size of the highlight.
|
||||
this.sizeAdjust = 5;
|
||||
// prop: showTooltip
|
||||
// Show a tooltip with data point values.
|
||||
this.showTooltip = true;
|
||||
// prop: tooltipLocation
|
||||
// Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
|
||||
this.tooltipLocation = 'nw';
|
||||
// prop: fadeTooltip
|
||||
// true = fade in/out tooltip, flase = show/hide tooltip
|
||||
this.fadeTooltip = true;
|
||||
// prop: tooltipFadeSpeed
|
||||
// 'slow', 'def', 'fast', or number of milliseconds.
|
||||
this.tooltipFadeSpeed = "fast";
|
||||
// prop: tooltipOffset
|
||||
// Pixel offset of tooltip from the highlight.
|
||||
this.tooltipOffset = 2;
|
||||
// prop: tooltipAxes
|
||||
// Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
|
||||
// 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
|
||||
this.tooltipAxes = 'both';
|
||||
// prop; tooltipSeparator
|
||||
// String to use to separate x and y axes in tooltip.
|
||||
this.tooltipSeparator = ', ';
|
||||
// prop; tooltipContentEditor
|
||||
// Function used to edit/augment/replace the formatted tooltip contents.
|
||||
// Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
|
||||
// where str is the generated tooltip html and seriesIndex and pointIndex identify
|
||||
// the data point being highlighted. Should return the html for the tooltip contents.
|
||||
this.tooltipContentEditor = null;
|
||||
// prop: useAxesFormatters
|
||||
// Use the x and y axes formatters to format the text in the tooltip.
|
||||
this.useAxesFormatters = true;
|
||||
// prop: tooltipFormatString
|
||||
// sprintf format string for the tooltip.
|
||||
// Uses Ash Searle's javascript sprintf implementation
|
||||
// found here: http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
// See http://perldoc.perl.org/functions/sprintf.html for reference.
|
||||
// Additional "p" and "P" format specifiers added by Chris Leonello.
|
||||
this.tooltipFormatString = '%.5P';
|
||||
// prop: formatString
|
||||
// alternative to tooltipFormatString
|
||||
// will format the whole tooltip text, populating with x, y values as
|
||||
// indicated by tooltipAxes option. So, you could have a tooltip like:
|
||||
// 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
|
||||
// If useAxesFormatters is true, values will be formatted according to
|
||||
// Axes formatters and you can populate your tooltip string with
|
||||
// %s placeholders.
|
||||
this.formatString = null;
|
||||
// prop: yvalues
|
||||
// Number of y values to expect in the data point array.
|
||||
// Typically this is 1. Certain plots, like OHLC, will
|
||||
// have more y values in each data point array.
|
||||
this.yvalues = 1;
|
||||
// prop: bringSeriesToFront
|
||||
// This option requires jQuery 1.4+
|
||||
// True to bring the series of the highlighted point to the front
|
||||
// of other series.
|
||||
this.bringSeriesToFront = false;
|
||||
this._tooltipElem;
|
||||
this.isHighlighting = false;
|
||||
this.currentNeighbor = null;
|
||||
|
||||
$.extend(true, this, options);
|
||||
};
|
||||
|
||||
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
|
||||
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
|
||||
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
|
||||
|
||||
// axis.renderer.tickrenderer.formatter
|
||||
|
||||
// called with scope of plot
|
||||
$.jqplot.Highlighter.init = function (target, data, opts){
|
||||
var options = opts || {};
|
||||
// add a highlighter attribute to the plot
|
||||
this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
|
||||
};
|
||||
|
||||
// called within scope of series
|
||||
$.jqplot.Highlighter.parseOptions = function (defaults, options) {
|
||||
// Add a showHighlight option to the series
|
||||
// and set it to true by default.
|
||||
this.showHighlight = true;
|
||||
};
|
||||
|
||||
// called within context of plot
|
||||
// create a canvas which we can draw on.
|
||||
// insert it before the eventCanvas, so eventCanvas will still capture events.
|
||||
$.jqplot.Highlighter.postPlotDraw = function() {
|
||||
// Memory Leaks patch
|
||||
if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
|
||||
this.plugins.highlighter.highlightCanvas.resetCanvas();
|
||||
this.plugins.highlighter.highlightCanvas = null;
|
||||
}
|
||||
|
||||
if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
|
||||
this.plugins.highlighter._tooltipElem.emptyForce();
|
||||
this.plugins.highlighter._tooltipElem = null;
|
||||
}
|
||||
|
||||
this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
|
||||
|
||||
this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
|
||||
this.plugins.highlighter.highlightCanvas.setContext();
|
||||
|
||||
var elem = document.createElement('div');
|
||||
this.plugins.highlighter._tooltipElem = $(elem);
|
||||
elem = null;
|
||||
this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
|
||||
this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
|
||||
|
||||
this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
|
||||
};
|
||||
|
||||
$.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
|
||||
$.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
|
||||
$.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
|
||||
|
||||
function draw(plot, neighbor) {
|
||||
var hl = plot.plugins.highlighter;
|
||||
var s = plot.series[neighbor.seriesIndex];
|
||||
var smr = s.markerRenderer;
|
||||
var mr = hl.markerRenderer;
|
||||
mr.style = smr.style;
|
||||
mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
|
||||
mr.size = smr.size + hl.sizeAdjust;
|
||||
var rgba = $.jqplot.getColorComponents(smr.color);
|
||||
var newrgb = [rgba[0], rgba[1], rgba[2]];
|
||||
var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
|
||||
mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
|
||||
mr.init();
|
||||
mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx);
|
||||
}
|
||||
|
||||
function showTooltip(plot, series, neighbor) {
|
||||
// neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
|
||||
// gridData should be x,y pixel coords on the grid.
|
||||
// add the plot._gridPadding to that to get x,y in the target.
|
||||
var hl = plot.plugins.highlighter;
|
||||
var elem = hl._tooltipElem;
|
||||
var serieshl = series.highlighter || {};
|
||||
|
||||
var opts = $.extend(true, {}, hl, serieshl);
|
||||
|
||||
if (opts.useAxesFormatters) {
|
||||
var xf = series._xaxis._ticks[0].formatter;
|
||||
var yf = series._yaxis._ticks[0].formatter;
|
||||
var xfstr = series._xaxis._ticks[0].formatString;
|
||||
var yfstr = series._yaxis._ticks[0].formatString;
|
||||
var str;
|
||||
var xstr = xf(xfstr, neighbor.data[0]);
|
||||
var ystrs = [];
|
||||
for (var i=1; i<opts.yvalues+1; i++) {
|
||||
ystrs.push(yf(yfstr, neighbor.data[i]));
|
||||
}
|
||||
if (typeof opts.formatString === 'string') {
|
||||
switch (opts.tooltipAxes) {
|
||||
case 'both':
|
||||
case 'xy':
|
||||
ystrs.unshift(xstr);
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
case 'yx':
|
||||
ystrs.push(xstr);
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
case 'x':
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString, xstr]);
|
||||
break;
|
||||
case 'y':
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
default: // same as xy
|
||||
ystrs.unshift(xstr);
|
||||
ystrs.unshift(opts.formatString);
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (opts.tooltipAxes) {
|
||||
case 'both':
|
||||
case 'xy':
|
||||
str = xstr;
|
||||
for (var i=0; i<ystrs.length; i++) {
|
||||
str += opts.tooltipSeparator + ystrs[i];
|
||||
}
|
||||
break;
|
||||
case 'yx':
|
||||
str = '';
|
||||
for (var i=0; i<ystrs.length; i++) {
|
||||
str += ystrs[i] + opts.tooltipSeparator;
|
||||
}
|
||||
str += xstr;
|
||||
break;
|
||||
case 'x':
|
||||
str = xstr;
|
||||
break;
|
||||
case 'y':
|
||||
str = ystrs.join(opts.tooltipSeparator);
|
||||
break;
|
||||
default: // same as 'xy'
|
||||
str = xstr;
|
||||
for (var i=0; i<ystrs.length; i++) {
|
||||
str += opts.tooltipSeparator + ystrs[i];
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var str;
|
||||
if (typeof opts.formatString === 'string') {
|
||||
str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString].concat(neighbor.data));
|
||||
}
|
||||
|
||||
else {
|
||||
if (opts.tooltipAxes == 'both' || opts.tooltipAxes == 'xy') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
|
||||
}
|
||||
else if (opts.tooltipAxes == 'yx') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
|
||||
}
|
||||
else if (opts.tooltipAxes == 'x') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
|
||||
}
|
||||
else if (opts.tooltipAxes == 'y') {
|
||||
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($.isFunction(opts.tooltipContentEditor)) {
|
||||
// args str, seriesIndex, pointIndex are essential so the hook can look up
|
||||
// extra data for the point.
|
||||
str = opts.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot);
|
||||
}
|
||||
elem.html(str);
|
||||
var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]};
|
||||
var ms = 0;
|
||||
var fact = 0.707;
|
||||
if (series.markerRenderer.show == true) {
|
||||
ms = (series.markerRenderer.size + opts.sizeAdjust)/2;
|
||||
}
|
||||
|
||||
var loc = locations;
|
||||
if (series.fillToZero && series.fill && neighbor.data[1] < 0) {
|
||||
loc = oppositeLocations;
|
||||
}
|
||||
|
||||
switch (loc[locationIndicies[opts.tooltipLocation]]) {
|
||||
case 'nw':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
|
||||
break;
|
||||
case 'n':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - ms;
|
||||
break;
|
||||
case 'ne':
|
||||
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
|
||||
break;
|
||||
case 'e':
|
||||
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
|
||||
break;
|
||||
case 'se':
|
||||
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
|
||||
break;
|
||||
case 's':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
|
||||
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + ms;
|
||||
break;
|
||||
case 'sw':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
|
||||
break;
|
||||
case 'w':
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
|
||||
break;
|
||||
default: // same as 'nw'
|
||||
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
|
||||
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
|
||||
break;
|
||||
}
|
||||
elem.css('left', x);
|
||||
elem.css('top', y);
|
||||
if (opts.fadeTooltip) {
|
||||
// Fix for stacked up animations. Thnanks Trevor!
|
||||
elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
|
||||
}
|
||||
else {
|
||||
elem.show();
|
||||
}
|
||||
elem = null;
|
||||
|
||||
}
|
||||
|
||||
function handleMove(ev, gridpos, datapos, neighbor, plot) {
|
||||
var hl = plot.plugins.highlighter;
|
||||
var c = plot.plugins.cursor;
|
||||
if (hl.show) {
|
||||
if (neighbor == null && hl.isHighlighting) {
|
||||
var evt = jQuery.Event('jqplotHighlighterUnhighlight');
|
||||
plot.target.trigger(evt);
|
||||
|
||||
var ctx = hl.highlightCanvas._ctx;
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
if (hl.fadeTooltip) {
|
||||
hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed);
|
||||
}
|
||||
else {
|
||||
hl._tooltipElem.hide();
|
||||
}
|
||||
if (hl.bringSeriesToFront) {
|
||||
plot.restorePreviousSeriesOrder();
|
||||
}
|
||||
hl.isHighlighting = false;
|
||||
hl.currentNeighbor = null;
|
||||
ctx = null;
|
||||
}
|
||||
else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) {
|
||||
var evt = jQuery.Event('jqplotHighlighterHighlight');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data, plot];
|
||||
plot.target.trigger(evt, ins);
|
||||
|
||||
hl.isHighlighting = true;
|
||||
hl.currentNeighbor = neighbor;
|
||||
if (hl.showMarker) {
|
||||
draw(plot, neighbor);
|
||||
}
|
||||
if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
|
||||
showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
|
||||
}
|
||||
if (hl.bringSeriesToFront) {
|
||||
plot.moveSeriesToFront(neighbor.seriesIndex);
|
||||
}
|
||||
}
|
||||
// check to see if we're highlighting the wrong point.
|
||||
else if (neighbor != null && hl.isHighlighting && hl.currentNeighbor != neighbor) {
|
||||
// highlighting the wrong point.
|
||||
|
||||
// if new series allows highlighting, highlight new point.
|
||||
if (plot.series[neighbor.seriesIndex].showHighlight) {
|
||||
var ctx = hl.highlightCanvas._ctx;
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
hl.isHighlighting = true;
|
||||
hl.currentNeighbor = neighbor;
|
||||
if (hl.showMarker) {
|
||||
draw(plot, neighbor);
|
||||
}
|
||||
if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
|
||||
showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
|
||||
}
|
||||
if (hl.bringSeriesToFront) {
|
||||
plot.moveSeriesToFront(neighbor.seriesIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,904 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
/**
|
||||
* Class: $.jqplot.PieRenderer
|
||||
* Plugin renderer to draw a pie chart.
|
||||
* x values, if present, will be used as slice labels.
|
||||
* y values give slice size.
|
||||
*
|
||||
* To use this renderer, you need to include the
|
||||
* pie renderer plugin, for example:
|
||||
*
|
||||
* > <script type="text/javascript" src="plugins/jqplot.pieRenderer.js"></script>
|
||||
*
|
||||
* Properties described here are passed into the $.jqplot function
|
||||
* as options on the series renderer. For example:
|
||||
*
|
||||
* > plot2 = $.jqplot('chart2', [s1, s2], {
|
||||
* > seriesDefaults: {
|
||||
* > renderer:$.jqplot.PieRenderer,
|
||||
* > rendererOptions:{
|
||||
* > sliceMargin: 2,
|
||||
* > startAngle: -90
|
||||
* > }
|
||||
* > }
|
||||
* > });
|
||||
*
|
||||
* A pie plot will trigger events on the plot target
|
||||
* according to user interaction. All events return the event object,
|
||||
* the series index, the point (slice) index, and the point data for
|
||||
* the appropriate slice.
|
||||
*
|
||||
* 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
|
||||
* 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
|
||||
* if highlighting is enabled.
|
||||
* 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
|
||||
* a highlighted slice.
|
||||
* 'jqplotDataClick' - triggered when the user clicks on a slice.
|
||||
* 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
|
||||
* the "captureRightClick" option is set to true on the plot.
|
||||
*/
|
||||
$.jqplot.PieRenderer = function(){
|
||||
$.jqplot.LineRenderer.call(this);
|
||||
};
|
||||
|
||||
$.jqplot.PieRenderer.prototype = new $.jqplot.LineRenderer();
|
||||
$.jqplot.PieRenderer.prototype.constructor = $.jqplot.PieRenderer;
|
||||
|
||||
// called with scope of a series
|
||||
$.jqplot.PieRenderer.prototype.init = function(options, plot) {
|
||||
// Group: Properties
|
||||
//
|
||||
// prop: diameter
|
||||
// Outer diameter of the pie, auto computed by default
|
||||
this.diameter = null;
|
||||
// prop: padding
|
||||
// padding between the pie and plot edges, legend, etc.
|
||||
this.padding = 20;
|
||||
// prop: sliceMargin
|
||||
// angular spacing between pie slices in degrees.
|
||||
this.sliceMargin = 0;
|
||||
// prop: fill
|
||||
// true or false, whether to fil the slices.
|
||||
this.fill = true;
|
||||
// prop: shadowOffset
|
||||
// offset of the shadow from the slice and offset of
|
||||
// each succesive stroke of the shadow from the last.
|
||||
this.shadowOffset = 2;
|
||||
// prop: shadowAlpha
|
||||
// transparency of the shadow (0 = transparent, 1 = opaque)
|
||||
this.shadowAlpha = 0.07;
|
||||
// prop: shadowDepth
|
||||
// number of strokes to apply to the shadow,
|
||||
// each stroke offset shadowOffset from the last.
|
||||
this.shadowDepth = 5;
|
||||
// prop: highlightMouseOver
|
||||
// True to highlight slice when moused over.
|
||||
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
|
||||
this.highlightMouseOver = true;
|
||||
// prop: highlightMouseDown
|
||||
// True to highlight when a mouse button is pressed over a slice.
|
||||
// This will be disabled if highlightMouseOver is true.
|
||||
this.highlightMouseDown = false;
|
||||
// prop: highlightColors
|
||||
// an array of colors to use when highlighting a slice.
|
||||
this.highlightColors = [];
|
||||
// prop: dataLabels
|
||||
// Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
|
||||
// Defaults to percentage of each pie slice.
|
||||
this.dataLabels = 'percent';
|
||||
// prop: showDataLabels
|
||||
// true to show data labels on slices.
|
||||
this.showDataLabels = false;
|
||||
// prop: dataLabelFormatString
|
||||
// Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
|
||||
this.dataLabelFormatString = null;
|
||||
// prop: dataLabelThreshold
|
||||
// Threshhold in percentage (0-100) of pie area, below which no label will be displayed.
|
||||
// This applies to all label types, not just to percentage labels.
|
||||
this.dataLabelThreshold = 3;
|
||||
// prop: dataLabelPositionFactor
|
||||
// A Multiplier (0-1) of the pie radius which controls position of label on slice.
|
||||
// Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
|
||||
this.dataLabelPositionFactor = 0.52;
|
||||
// prop: dataLabelNudge
|
||||
// Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
|
||||
this.dataLabelNudge = 2;
|
||||
// prop: dataLabelCenterOn
|
||||
// True to center the data label at its position.
|
||||
// False to set the inside facing edge of the label at its position.
|
||||
this.dataLabelCenterOn = true;
|
||||
// prop: startAngle
|
||||
// Angle to start drawing pie in degrees.
|
||||
// According to orientation of canvas coordinate system:
|
||||
// 0 = on the positive x axis
|
||||
// -90 = on the positive y axis.
|
||||
// 90 = on the negaive y axis.
|
||||
// 180 or - 180 = on the negative x axis.
|
||||
this.startAngle = 0;
|
||||
this.tickRenderer = $.jqplot.PieTickRenderer;
|
||||
// Used as check for conditions where pie shouldn't be drawn.
|
||||
this._drawData = true;
|
||||
this._type = 'pie';
|
||||
|
||||
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
|
||||
if (options.highlightMouseDown && options.highlightMouseOver == null) {
|
||||
options.highlightMouseOver = false;
|
||||
}
|
||||
|
||||
$.extend(true, this, options);
|
||||
|
||||
if (this.sliceMargin < 0) {
|
||||
this.sliceMargin = 0;
|
||||
}
|
||||
|
||||
this._diameter = null;
|
||||
this._radius = null;
|
||||
// array of [start,end] angles arrays, one for each slice. In radians.
|
||||
this._sliceAngles = [];
|
||||
// index of the currenty highlighted point, if any
|
||||
this._highlightedPoint = null;
|
||||
|
||||
// set highlight colors if none provided
|
||||
if (this.highlightColors.length == 0) {
|
||||
for (var i=0; i<this.seriesColors.length; i++){
|
||||
var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
|
||||
var newrgb = [rgba[0], rgba[1], rgba[2]];
|
||||
var sum = newrgb[0] + newrgb[1] + newrgb[2];
|
||||
for (var j=0; j<3; j++) {
|
||||
// when darkening, lowest color component can be is 60.
|
||||
newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
|
||||
newrgb[j] = parseInt(newrgb[j], 10);
|
||||
}
|
||||
this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors);
|
||||
|
||||
plot.postParseOptionsHooks.addOnce(postParseOptions);
|
||||
plot.postInitHooks.addOnce(postInit);
|
||||
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
|
||||
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
|
||||
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
|
||||
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
|
||||
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
|
||||
plot.postDrawHooks.addOnce(postPlotDraw);
|
||||
};
|
||||
|
||||
$.jqplot.PieRenderer.prototype.setGridData = function(plot) {
|
||||
// set gridData property. This will hold angle in radians of each data point.
|
||||
var stack = [];
|
||||
var td = [];
|
||||
var sa = this.startAngle/180*Math.PI;
|
||||
var tot = 0;
|
||||
// don't know if we have any valid data yet, so set plot to not draw.
|
||||
this._drawData = false;
|
||||
for (var i=0; i<this.data.length; i++){
|
||||
if (this.data[i][1] != 0) {
|
||||
// we have data, O.K. to draw.
|
||||
this._drawData = true;
|
||||
}
|
||||
stack.push(this.data[i][1]);
|
||||
td.push([this.data[i][0]]);
|
||||
if (i>0) {
|
||||
stack[i] += stack[i-1];
|
||||
}
|
||||
tot += this.data[i][1];
|
||||
}
|
||||
var fact = Math.PI*2/stack[stack.length - 1];
|
||||
|
||||
for (var i=0; i<stack.length; i++) {
|
||||
td[i][1] = stack[i] * fact;
|
||||
td[i][2] = this.data[i][1]/tot;
|
||||
}
|
||||
this.gridData = td;
|
||||
};
|
||||
|
||||
$.jqplot.PieRenderer.prototype.makeGridData = function(data, plot) {
|
||||
var stack = [];
|
||||
var td = [];
|
||||
var tot = 0;
|
||||
var sa = this.startAngle/180*Math.PI;
|
||||
// don't know if we have any valid data yet, so set plot to not draw.
|
||||
this._drawData = false;
|
||||
for (var i=0; i<data.length; i++){
|
||||
if (this.data[i][1] != 0) {
|
||||
// we have data, O.K. to draw.
|
||||
this._drawData = true;
|
||||
}
|
||||
stack.push(data[i][1]);
|
||||
td.push([data[i][0]]);
|
||||
if (i>0) {
|
||||
stack[i] += stack[i-1];
|
||||
}
|
||||
tot += data[i][1];
|
||||
}
|
||||
var fact = Math.PI*2/stack[stack.length - 1];
|
||||
|
||||
for (var i=0; i<stack.length; i++) {
|
||||
td[i][1] = stack[i] * fact;
|
||||
td[i][2] = data[i][1]/tot;
|
||||
}
|
||||
return td;
|
||||
};
|
||||
|
||||
function calcRadiusAdjustment(ang) {
|
||||
return Math.sin((ang - (ang-Math.PI) / 8 / Math.PI )/2.0);
|
||||
}
|
||||
|
||||
function calcRPrime(ang1, ang2, sliceMargin, fill, lineWidth) {
|
||||
var rprime = 0;
|
||||
var ang = ang2 - ang1;
|
||||
var absang = Math.abs(ang);
|
||||
var sm = sliceMargin;
|
||||
if (fill == false) {
|
||||
sm += lineWidth;
|
||||
}
|
||||
|
||||
if (sm > 0 && absang > 0.01 && absang < 6.282) {
|
||||
rprime = parseFloat(sm) / 2.0 / calcRadiusAdjustment(ang);
|
||||
}
|
||||
|
||||
return rprime;
|
||||
}
|
||||
|
||||
$.jqplot.PieRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
|
||||
if (this._drawData) {
|
||||
var r = this._radius;
|
||||
var fill = this.fill;
|
||||
var lineWidth = this.lineWidth;
|
||||
var sm = this.sliceMargin;
|
||||
if (this.fill == false) {
|
||||
sm += this.lineWidth;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.translate(this._center[0], this._center[1]);
|
||||
|
||||
var rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
|
||||
|
||||
var transx = rprime * Math.cos((ang1 + ang2) / 2.0);
|
||||
var transy = rprime * Math.sin((ang1 + ang2) / 2.0);
|
||||
|
||||
if ((ang2 - ang1) <= Math.PI) {
|
||||
r -= rprime;
|
||||
}
|
||||
else {
|
||||
r += rprime;
|
||||
}
|
||||
|
||||
ctx.translate(transx, transy);
|
||||
|
||||
if (isShadow) {
|
||||
for (var i=0, l=this.shadowDepth; i<l; i++) {
|
||||
ctx.save();
|
||||
ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
|
||||
doDraw(r);
|
||||
}
|
||||
for (var i=0, l=this.shadowDepth; i<l; i++) {
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
doDraw(r);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function doDraw (rad) {
|
||||
// Fix for IE and Chrome that can't seem to draw circles correctly.
|
||||
// ang2 should always be <= 2 pi since that is the way the data is converted.
|
||||
// 2Pi = 6.2831853, Pi = 3.1415927
|
||||
if (ang2 > 6.282 + this.startAngle) {
|
||||
ang2 = 6.282 + this.startAngle;
|
||||
if (ang1 > ang2) {
|
||||
ang1 = 6.281 + this.startAngle;
|
||||
}
|
||||
}
|
||||
// Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
|
||||
// ugly line on unfilled pies.
|
||||
if (ang1 >= ang2) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = color;
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.arc(0, 0, rad, ang1, ang2, false);
|
||||
ctx.lineTo(0,0);
|
||||
ctx.closePath();
|
||||
|
||||
if (fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
else {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// called with scope of series
|
||||
$.jqplot.PieRenderer.prototype.draw = function (ctx, gd, options, plot) {
|
||||
var i;
|
||||
var opts = (options != undefined) ? options : {};
|
||||
// offset and direction of offset due to legend placement
|
||||
var offx = 0;
|
||||
var offy = 0;
|
||||
var trans = 1;
|
||||
var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
|
||||
if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
|
||||
var li = options.legendInfo;
|
||||
switch (li.location) {
|
||||
case 'nw':
|
||||
offx = li.width + li.xoffset;
|
||||
break;
|
||||
case 'w':
|
||||
offx = li.width + li.xoffset;
|
||||
break;
|
||||
case 'sw':
|
||||
offx = li.width + li.xoffset;
|
||||
break;
|
||||
case 'ne':
|
||||
offx = li.width + li.xoffset;
|
||||
trans = -1;
|
||||
break;
|
||||
case 'e':
|
||||
offx = li.width + li.xoffset;
|
||||
trans = -1;
|
||||
break;
|
||||
case 'se':
|
||||
offx = li.width + li.xoffset;
|
||||
trans = -1;
|
||||
break;
|
||||
case 'n':
|
||||
offy = li.height + li.yoffset;
|
||||
break;
|
||||
case 's':
|
||||
offy = li.height + li.yoffset;
|
||||
trans = -1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
|
||||
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
|
||||
var cw = ctx.canvas.width;
|
||||
var ch = ctx.canvas.height;
|
||||
var w = cw - offx - 2 * this.padding;
|
||||
var h = ch - offy - 2 * this.padding;
|
||||
var mindim = Math.min(w,h);
|
||||
var d = mindim;
|
||||
|
||||
// Fixes issue #272. Thanks hugwijst!
|
||||
// reset slice angles array.
|
||||
this._sliceAngles = [];
|
||||
|
||||
var sm = this.sliceMargin;
|
||||
if (this.fill == false) {
|
||||
sm += this.lineWidth;
|
||||
}
|
||||
|
||||
var rprime;
|
||||
var maxrprime = 0;
|
||||
|
||||
var ang, ang1, ang2, shadowColor;
|
||||
var sa = this.startAngle / 180 * Math.PI;
|
||||
|
||||
// have to pre-draw shadows, so loop throgh here and calculate some values also.
|
||||
for (var i=0, l=gd.length; i<l; i++) {
|
||||
ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
|
||||
ang2 = gd[i][1] + sa;
|
||||
|
||||
this._sliceAngles.push([ang1, ang2]);
|
||||
|
||||
rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
|
||||
|
||||
if (Math.abs(ang2-ang1) > Math.PI) {
|
||||
maxrprime = Math.max(rprime, maxrprime);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.diameter != null && this.diameter > 0) {
|
||||
this._diameter = this.diameter - 2*maxrprime;
|
||||
}
|
||||
else {
|
||||
this._diameter = d - 2*maxrprime;
|
||||
}
|
||||
|
||||
// Need to check for undersized pie. This can happen if
|
||||
// plot area too small and legend is too big.
|
||||
if (this._diameter < 6) {
|
||||
$.jqplot.log('Diameter of pie too small, not rendering.');
|
||||
return;
|
||||
}
|
||||
|
||||
var r = this._radius = this._diameter/2;
|
||||
|
||||
this._center = [(cw - trans * offx)/2 + trans * offx + maxrprime * Math.cos(sa), (ch - trans*offy)/2 + trans * offy + maxrprime * Math.sin(sa)];
|
||||
|
||||
if (this.shadow) {
|
||||
for (var i=0, l=gd.length; i<l; i++) {
|
||||
shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
|
||||
this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], shadowColor, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i=0; i<gd.length; i++) {
|
||||
|
||||
this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], colorGenerator.next(), false);
|
||||
|
||||
if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
|
||||
var fstr, avgang = (this._sliceAngles[i][0] + this._sliceAngles[i][1])/2, label;
|
||||
|
||||
if (this.dataLabels == 'label') {
|
||||
fstr = this.dataLabelFormatString || '%s';
|
||||
label = $.jqplot.sprintf(fstr, gd[i][0]);
|
||||
}
|
||||
else if (this.dataLabels == 'value') {
|
||||
fstr = this.dataLabelFormatString || '%d';
|
||||
label = $.jqplot.sprintf(fstr, this.data[i][1]);
|
||||
}
|
||||
else if (this.dataLabels == 'percent') {
|
||||
fstr = this.dataLabelFormatString || '%d%%';
|
||||
label = $.jqplot.sprintf(fstr, gd[i][2]*100);
|
||||
}
|
||||
else if (this.dataLabels.constructor == Array) {
|
||||
fstr = this.dataLabelFormatString || '%s';
|
||||
label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
|
||||
}
|
||||
|
||||
var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
|
||||
|
||||
var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
|
||||
var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
|
||||
|
||||
var labelelem = $('<div class="jqplot-pie-series jqplot-data-label" style="position:absolute;">' + label + '</div>').insertBefore(plot.eventCanvas._elem);
|
||||
if (this.dataLabelCenterOn) {
|
||||
x -= labelelem.width()/2;
|
||||
y -= labelelem.height()/2;
|
||||
}
|
||||
else {
|
||||
x -= labelelem.width() * Math.sin(avgang/2);
|
||||
y -= labelelem.height()/2;
|
||||
}
|
||||
x = Math.round(x);
|
||||
y = Math.round(y);
|
||||
labelelem.css({left: x, top: y});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.PieAxisRenderer = function() {
|
||||
$.jqplot.LinearAxisRenderer.call(this);
|
||||
};
|
||||
|
||||
$.jqplot.PieAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
|
||||
$.jqplot.PieAxisRenderer.prototype.constructor = $.jqplot.PieAxisRenderer;
|
||||
|
||||
|
||||
// There are no traditional axes on a pie chart. We just need to provide
|
||||
// dummy objects with properties so the plot will render.
|
||||
// called with scope of axis object.
|
||||
$.jqplot.PieAxisRenderer.prototype.init = function(options){
|
||||
//
|
||||
this.tickRenderer = $.jqplot.PieTickRenderer;
|
||||
$.extend(true, this, options);
|
||||
// I don't think I'm going to need _dataBounds here.
|
||||
// have to go Axis scaling in a way to fit chart onto plot area
|
||||
// and provide u2p and p2u functionality for mouse cursor, etc.
|
||||
// for convienence set _dataBounds to 0 and 100 and
|
||||
// set min/max to 0 and 100.
|
||||
this._dataBounds = {min:0, max:100};
|
||||
this.min = 0;
|
||||
this.max = 100;
|
||||
this.showTicks = false;
|
||||
this.ticks = [];
|
||||
this.showMark = false;
|
||||
this.show = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
$.jqplot.PieLegendRenderer = function(){
|
||||
$.jqplot.TableLegendRenderer.call(this);
|
||||
};
|
||||
|
||||
$.jqplot.PieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
|
||||
$.jqplot.PieLegendRenderer.prototype.constructor = $.jqplot.PieLegendRenderer;
|
||||
|
||||
/**
|
||||
* Class: $.jqplot.PieLegendRenderer
|
||||
* Legend Renderer specific to pie plots. Set by default
|
||||
* when user creates a pie plot.
|
||||
*/
|
||||
$.jqplot.PieLegendRenderer.prototype.init = function(options) {
|
||||
// Group: Properties
|
||||
//
|
||||
// prop: numberRows
|
||||
// Maximum number of rows in the legend. 0 or null for unlimited.
|
||||
this.numberRows = null;
|
||||
// prop: numberColumns
|
||||
// Maximum number of columns in the legend. 0 or null for unlimited.
|
||||
this.numberColumns = null;
|
||||
$.extend(true, this, options);
|
||||
};
|
||||
|
||||
// called with context of legend
|
||||
$.jqplot.PieLegendRenderer.prototype.draw = function() {
|
||||
var legend = this;
|
||||
if (this.show) {
|
||||
var series = this._series;
|
||||
|
||||
|
||||
this._elem = $(document.createElement('table'));
|
||||
this._elem.addClass('jqplot-table-legend');
|
||||
|
||||
var ss = {position:'absolute'};
|
||||
if (this.background) {
|
||||
ss['background'] = this.background;
|
||||
}
|
||||
if (this.border) {
|
||||
ss['border'] = this.border;
|
||||
}
|
||||
if (this.fontSize) {
|
||||
ss['fontSize'] = this.fontSize;
|
||||
}
|
||||
if (this.fontFamily) {
|
||||
ss['fontFamily'] = this.fontFamily;
|
||||
}
|
||||
if (this.textColor) {
|
||||
ss['textColor'] = this.textColor;
|
||||
}
|
||||
if (this.marginTop != null) {
|
||||
ss['marginTop'] = this.marginTop;
|
||||
}
|
||||
if (this.marginBottom != null) {
|
||||
ss['marginBottom'] = this.marginBottom;
|
||||
}
|
||||
if (this.marginLeft != null) {
|
||||
ss['marginLeft'] = this.marginLeft;
|
||||
}
|
||||
if (this.marginRight != null) {
|
||||
ss['marginRight'] = this.marginRight;
|
||||
}
|
||||
|
||||
this._elem.css(ss);
|
||||
|
||||
// Pie charts legends don't go by number of series, but by number of data points
|
||||
// in the series. Refactor things here for that.
|
||||
|
||||
var pad = false,
|
||||
reverse = false,
|
||||
nr,
|
||||
nc;
|
||||
var s = series[0];
|
||||
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
|
||||
|
||||
if (s.show) {
|
||||
var pd = s.data;
|
||||
if (this.numberRows) {
|
||||
nr = this.numberRows;
|
||||
if (!this.numberColumns){
|
||||
nc = Math.ceil(pd.length/nr);
|
||||
}
|
||||
else{
|
||||
nc = this.numberColumns;
|
||||
}
|
||||
}
|
||||
else if (this.numberColumns) {
|
||||
nc = this.numberColumns;
|
||||
nr = Math.ceil(pd.length/this.numberColumns);
|
||||
}
|
||||
else {
|
||||
nr = pd.length;
|
||||
nc = 1;
|
||||
}
|
||||
|
||||
var i, j;
|
||||
var tr, td1, td2;
|
||||
var lt, rs, color;
|
||||
var idx = 0;
|
||||
var div0, div1;
|
||||
|
||||
for (i=0; i<nr; i++) {
|
||||
tr = $(document.createElement('tr'));
|
||||
tr.addClass('jqplot-table-legend');
|
||||
|
||||
if (reverse){
|
||||
tr.prependTo(this._elem);
|
||||
}
|
||||
|
||||
else{
|
||||
tr.appendTo(this._elem);
|
||||
}
|
||||
|
||||
for (j=0; j<nc; j++) {
|
||||
if (idx < pd.length){
|
||||
lt = this.labels[idx] || pd[idx][0].toString();
|
||||
color = colorGenerator.next();
|
||||
if (!reverse){
|
||||
if (i>0){
|
||||
pad = true;
|
||||
}
|
||||
else{
|
||||
pad = false;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (i == nr -1){
|
||||
pad = false;
|
||||
}
|
||||
else{
|
||||
pad = true;
|
||||
}
|
||||
}
|
||||
rs = (pad) ? this.rowSpacing : '0';
|
||||
|
||||
|
||||
|
||||
td1 = $(document.createElement('td'));
|
||||
td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
|
||||
td1.css({textAlign: 'center', paddingTop: rs});
|
||||
|
||||
div0 = $(document.createElement('div'));
|
||||
div0.addClass('jqplot-table-legend-swatch-outline');
|
||||
div1 = $(document.createElement('div'));
|
||||
div1.addClass('jqplot-table-legend-swatch');
|
||||
div1.css({backgroundColor: color, borderColor: color});
|
||||
td1.append(div0.append(div1));
|
||||
|
||||
td2 = $(document.createElement('td'));
|
||||
td2.addClass('jqplot-table-legend jqplot-table-legend-label');
|
||||
td2.css('paddingTop', rs);
|
||||
|
||||
if (this.escapeHtml){
|
||||
td2.text(lt);
|
||||
}
|
||||
else {
|
||||
td2.html(lt);
|
||||
}
|
||||
if (reverse) {
|
||||
td2.prependTo(tr);
|
||||
td1.prependTo(tr);
|
||||
}
|
||||
else {
|
||||
td1.appendTo(tr);
|
||||
td2.appendTo(tr);
|
||||
}
|
||||
pad = true;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._elem;
|
||||
};
|
||||
|
||||
$.jqplot.PieRenderer.prototype.handleMove = function(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
plot.target.trigger('jqplotDataMouseOver', ins);
|
||||
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
|
||||
plot.target.trigger('jqplotDataHighlight', ins);
|
||||
highlight (plot, ins[0], ins[1]);
|
||||
}
|
||||
}
|
||||
else if (neighbor == null) {
|
||||
unhighlight (plot);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
|
||||
|
||||
// setup default renderers for axes and legend so user doesn't have to
|
||||
// called with scope of plot
|
||||
function preInit(target, data, options) {
|
||||
options = options || {};
|
||||
options.axesDefaults = options.axesDefaults || {};
|
||||
options.legend = options.legend || {};
|
||||
options.seriesDefaults = options.seriesDefaults || {};
|
||||
// only set these if there is a pie series
|
||||
var setopts = false;
|
||||
if (options.seriesDefaults.renderer == $.jqplot.PieRenderer) {
|
||||
setopts = true;
|
||||
}
|
||||
else if (options.series) {
|
||||
for (var i=0; i < options.series.length; i++) {
|
||||
if (options.series[i].renderer == $.jqplot.PieRenderer) {
|
||||
setopts = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setopts) {
|
||||
options.axesDefaults.renderer = $.jqplot.PieAxisRenderer;
|
||||
options.legend.renderer = $.jqplot.PieLegendRenderer;
|
||||
options.legend.preDraw = true;
|
||||
options.seriesDefaults.pointLabels = {show: false};
|
||||
}
|
||||
}
|
||||
|
||||
function postInit(target, data, options) {
|
||||
for (var i=0; i<this.series.length; i++) {
|
||||
if (this.series[i].renderer.constructor == $.jqplot.PieRenderer) {
|
||||
// don't allow mouseover and mousedown at same time.
|
||||
if (this.series[i].highlightMouseOver) {
|
||||
this.series[i].highlightMouseDown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called with scope of plot
|
||||
function postParseOptions(options) {
|
||||
for (var i=0; i<this.series.length; i++) {
|
||||
this.series[i].seriesColors = this.seriesColors;
|
||||
this.series[i].colorGenerator = $.jqplot.colorGenerator;
|
||||
}
|
||||
}
|
||||
|
||||
function highlight (plot, sidx, pidx) {
|
||||
var s = plot.series[sidx];
|
||||
var canvas = plot.plugins.pieRenderer.highlightCanvas;
|
||||
canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
|
||||
s._highlightedPoint = pidx;
|
||||
plot.plugins.pieRenderer.highlightedSeriesIndex = sidx;
|
||||
s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColorGenerator.get(pidx), false);
|
||||
}
|
||||
|
||||
function unhighlight (plot) {
|
||||
var canvas = plot.plugins.pieRenderer.highlightCanvas;
|
||||
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
|
||||
for (var i=0; i<plot.series.length; i++) {
|
||||
plot.series[i]._highlightedPoint = null;
|
||||
}
|
||||
plot.plugins.pieRenderer.highlightedSeriesIndex = null;
|
||||
plot.target.trigger('jqplotDataUnhighlight');
|
||||
}
|
||||
|
||||
function handleMove(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
var evt1 = jQuery.Event('jqplotDataMouseOver');
|
||||
evt1.pageX = ev.pageX;
|
||||
evt1.pageY = ev.pageY;
|
||||
plot.target.trigger(evt1, ins);
|
||||
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
|
||||
var evt = jQuery.Event('jqplotDataHighlight');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
highlight (plot, ins[0], ins[1]);
|
||||
}
|
||||
}
|
||||
else if (neighbor == null) {
|
||||
unhighlight (plot);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
|
||||
var evt = jQuery.Event('jqplotDataHighlight');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
highlight (plot, ins[0], ins[1]);
|
||||
}
|
||||
}
|
||||
else if (neighbor == null) {
|
||||
unhighlight (plot);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
|
||||
var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
|
||||
if (idx != null && plot.series[idx].highlightMouseDown) {
|
||||
unhighlight(plot);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
var evt = jQuery.Event('jqplotDataClick');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
|
||||
if (neighbor) {
|
||||
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
|
||||
var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
|
||||
if (idx != null && plot.series[idx].highlightMouseDown) {
|
||||
unhighlight(plot);
|
||||
}
|
||||
var evt = jQuery.Event('jqplotDataRightClick');
|
||||
evt.which = ev.which;
|
||||
evt.pageX = ev.pageX;
|
||||
evt.pageY = ev.pageY;
|
||||
plot.target.trigger(evt, ins);
|
||||
}
|
||||
}
|
||||
|
||||
// called within context of plot
|
||||
// create a canvas which we can draw on.
|
||||
// insert it before the eventCanvas, so eventCanvas will still capture events.
|
||||
function postPlotDraw() {
|
||||
// Memory Leaks patch
|
||||
if (this.plugins.pieRenderer && this.plugins.pieRenderer.highlightCanvas) {
|
||||
this.plugins.pieRenderer.highlightCanvas.resetCanvas();
|
||||
this.plugins.pieRenderer.highlightCanvas = null;
|
||||
}
|
||||
|
||||
this.plugins.pieRenderer = {highlightedSeriesIndex:null};
|
||||
this.plugins.pieRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
|
||||
|
||||
// do we have any data labels? if so, put highlight canvas before those
|
||||
var labels = $(this.targetId+' .jqplot-data-label');
|
||||
if (labels.length) {
|
||||
$(labels[0]).before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
|
||||
}
|
||||
// else put highlight canvas before event canvas.
|
||||
else {
|
||||
this.eventCanvas._elem.before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
|
||||
}
|
||||
|
||||
var hctx = this.plugins.pieRenderer.highlightCanvas.setContext();
|
||||
this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
|
||||
}
|
||||
|
||||
$.jqplot.preInitHooks.push(preInit);
|
||||
|
||||
$.jqplot.PieTickRenderer = function() {
|
||||
$.jqplot.AxisTickRenderer.call(this);
|
||||
};
|
||||
|
||||
$.jqplot.PieTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
|
||||
$.jqplot.PieTickRenderer.prototype.constructor = $.jqplot.PieTickRenderer;
|
||||
|
||||
})(jQuery);
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
/**
|
||||
* jqPlot
|
||||
* Pure JavaScript plotting plugin using jQuery
|
||||
*
|
||||
* Version: 1.0.8
|
||||
* Revision: 1250
|
||||
*
|
||||
* Copyright (c) 2009-2013 Chris Leonello
|
||||
* jqPlot is currently available for use in all personal or commercial projects
|
||||
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
|
||||
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
|
||||
* choose the license that best suits your project and use it accordingly.
|
||||
*
|
||||
* Although not required, the author would appreciate an email letting him
|
||||
* know of any substantial use of jqPlot. You can reach the author at:
|
||||
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
|
||||
*
|
||||
* If you are feeling kind and generous, consider supporting the project by
|
||||
* making a donation at: http://www.jqplot.com/donate.php .
|
||||
*
|
||||
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
|
||||
*
|
||||
* version 2007.04.27
|
||||
* author Ash Searle
|
||||
* http://hexmen.com/blog/2007/03/printf-sprintf/
|
||||
* http://hexmen.com/js/sprintf.js
|
||||
* The author (Ash Searle) has placed this code in the public domain:
|
||||
* "This code is unrestricted: you are free to use it however you like."
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Class: $.jqplot.PointLabels
|
||||
* Plugin for putting labels at the data points.
|
||||
*
|
||||
* To use this plugin, include the js
|
||||
* file in your source:
|
||||
*
|
||||
* > <script type="text/javascript" src="plugins/jqplot.pointLabels.js"></script>
|
||||
*
|
||||
* By default, the last value in the data ponit array in the data series is used
|
||||
* for the label. For most series renderers, extra data can be added to the
|
||||
* data point arrays and the last value will be used as the label.
|
||||
*
|
||||
* For instance,
|
||||
* this series:
|
||||
*
|
||||
* > [[1,4], [3,5], [7,2]]
|
||||
*
|
||||
* Would, by default, use the y values in the labels.
|
||||
* Extra data can be added to the series like so:
|
||||
*
|
||||
* > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']]
|
||||
*
|
||||
* And now the point labels would be 'mid', 'low', and 'hi'.
|
||||
*
|
||||
* Options to the point labels and a custom labels array can be passed into the
|
||||
* "pointLabels" option on the series option like so:
|
||||
*
|
||||
* > series:[{pointLabels:{
|
||||
* > labels:['mid', 'hi', 'low'],
|
||||
* > location:'se',
|
||||
* > ypadding: 12
|
||||
* > }
|
||||
* > }]
|
||||
*
|
||||
* A custom labels array in the options takes precendence over any labels
|
||||
* in the series data. If you have a custom labels array in the options,
|
||||
* but still want to use values from the series array as labels, set the
|
||||
* "labelsFromSeries" option to true.
|
||||
*
|
||||
* By default, html entities (<, >, etc.) are escaped in point labels.
|
||||
* If you want to include actual html markup in the labels,
|
||||
* set the "escapeHTML" option to false.
|
||||
*
|
||||
*/
|
||||
$.jqplot.PointLabels = function(options) {
|
||||
// Group: Properties
|
||||
//
|
||||
// prop: show
|
||||
// show the labels or not.
|
||||
this.show = $.jqplot.config.enablePlugins;
|
||||
// prop: location
|
||||
// compass location where to position the label around the point.
|
||||
// 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
|
||||
this.location = 'n';
|
||||
// prop: labelsFromSeries
|
||||
// true to use labels within data point arrays.
|
||||
this.labelsFromSeries = false;
|
||||
// prop: seriesLabelIndex
|
||||
// array index for location of labels within data point arrays.
|
||||
// if null, will use the last element of the data point array.
|
||||
this.seriesLabelIndex = null;
|
||||
// prop: labels
|
||||
// array of arrays of labels, one array for each series.
|
||||
this.labels = [];
|
||||
// actual labels that will get displayed.
|
||||
// needed to preserve user specified labels in labels array.
|
||||
this._labels = [];
|
||||
// prop: stackedValue
|
||||
// true to display value as stacked in a stacked plot.
|
||||
// no effect if labels is specified.
|
||||
this.stackedValue = false;
|
||||
// prop: ypadding
|
||||
// vertical padding in pixels between point and label
|
||||
this.ypadding = 6;
|
||||
// prop: xpadding
|
||||
// horizontal padding in pixels between point and label
|
||||
this.xpadding = 6;
|
||||
// prop: escapeHTML
|
||||
// true to escape html entities in the labels.
|
||||
// If you want to include markup in the labels, set to false.
|
||||
this.escapeHTML = true;
|
||||
// prop: edgeTolerance
|
||||
// Number of pixels that the label must be away from an axis
|
||||
// boundary in order to be drawn. Negative values will allow overlap
|
||||
// with the grid boundaries.
|
||||
this.edgeTolerance = -5;
|
||||
// prop: formatter
|
||||
// A class of a formatter for the tick text. sprintf by default.
|
||||
this.formatter = $.jqplot.DefaultTickFormatter;
|
||||
// prop: formatString
|
||||
// string passed to the formatter.
|
||||
this.formatString = '';
|
||||
// prop: hideZeros
|
||||
// true to not show a label for a value which is 0.
|
||||
this.hideZeros = false;
|
||||
this._elems = [];
|
||||
|
||||
$.extend(true, this, options);
|
||||
};
|
||||
|
||||
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
|
||||
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
|
||||
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
|
||||
|
||||
// called with scope of a series
|
||||
$.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){
|
||||
var options = $.extend(true, {}, seriesDefaults, opts);
|
||||
options.pointLabels = options.pointLabels || {};
|
||||
if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) {
|
||||
options.pointLabels.location = 'e';
|
||||
}
|
||||
// add a pointLabels attribute to the series plugins
|
||||
this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels);
|
||||
this.plugins.pointLabels.setLabels.call(this);
|
||||
};
|
||||
|
||||
// called with scope of series
|
||||
$.jqplot.PointLabels.prototype.setLabels = function() {
|
||||
var p = this.plugins.pointLabels;
|
||||
var labelIdx;
|
||||
if (p.seriesLabelIndex != null) {
|
||||
labelIdx = p.seriesLabelIndex;
|
||||
}
|
||||
else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') {
|
||||
labelIdx = (this._plotData[0].length < 3) ? 0 : this._plotData[0].length -1;
|
||||
}
|
||||
else {
|
||||
labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1;
|
||||
}
|
||||
p._labels = [];
|
||||
if (p.labels.length === 0 || p.labelsFromSeries) {
|
||||
if (p.stackedValue) {
|
||||
if (this._plotData.length && this._plotData[0].length){
|
||||
// var idx = p.seriesLabelIndex || this._plotData[0].length -1;
|
||||
for (var i=0; i<this._plotData.length; i++) {
|
||||
p._labels.push(this._plotData[i][labelIdx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// var d = this._plotData;
|
||||
var d = this.data;
|
||||
if (this.renderer.constructor === $.jqplot.BarRenderer && this.waterfall) {
|
||||
d = this._data;
|
||||
}
|
||||
if (d.length && d[0].length) {
|
||||
// var idx = p.seriesLabelIndex || d[0].length -1;
|
||||
for (var i=0; i<d.length; i++) {
|
||||
p._labels.push(d[i][labelIdx]);
|
||||
}
|
||||
}
|
||||
d = null;
|
||||
}
|
||||
}
|
||||
else if (p.labels.length){
|
||||
p._labels = p.labels;
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.PointLabels.prototype.xOffset = function(elem, location, padding) {
|
||||
location = location || this.location;
|
||||
padding = padding || this.xpadding;
|
||||
var offset;
|
||||
|
||||
switch (location) {
|
||||
case 'nw':
|
||||
offset = -elem.outerWidth(true) - this.xpadding;
|
||||
break;
|
||||
case 'n':
|
||||
offset = -elem.outerWidth(true)/2;
|
||||
break;
|
||||
case 'ne':
|
||||
offset = this.xpadding;
|
||||
break;
|
||||
case 'e':
|
||||
offset = this.xpadding;
|
||||
break;
|
||||
case 'se':
|
||||
offset = this.xpadding;
|
||||
break;
|
||||
case 's':
|
||||
offset = -elem.outerWidth(true)/2;
|
||||
break;
|
||||
case 'sw':
|
||||
offset = -elem.outerWidth(true) - this.xpadding;
|
||||
break;
|
||||
case 'w':
|
||||
offset = -elem.outerWidth(true) - this.xpadding;
|
||||
break;
|
||||
default: // same as 'nw'
|
||||
offset = -elem.outerWidth(true) - this.xpadding;
|
||||
break;
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
$.jqplot.PointLabels.prototype.yOffset = function(elem, location, padding) {
|
||||
location = location || this.location;
|
||||
padding = padding || this.xpadding;
|
||||
var offset;
|
||||
|
||||
switch (location) {
|
||||
case 'nw':
|
||||
offset = -elem.outerHeight(true) - this.ypadding;
|
||||
break;
|
||||
case 'n':
|
||||
offset = -elem.outerHeight(true) - this.ypadding;
|
||||
break;
|
||||
case 'ne':
|
||||
offset = -elem.outerHeight(true) - this.ypadding;
|
||||
break;
|
||||
case 'e':
|
||||
offset = -elem.outerHeight(true)/2;
|
||||
break;
|
||||
case 'se':
|
||||
offset = this.ypadding;
|
||||
break;
|
||||
case 's':
|
||||
offset = this.ypadding;
|
||||
break;
|
||||
case 'sw':
|
||||
offset = this.ypadding;
|
||||
break;
|
||||
case 'w':
|
||||
offset = -elem.outerHeight(true)/2;
|
||||
break;
|
||||
default: // same as 'nw'
|
||||
offset = -elem.outerHeight(true) - this.ypadding;
|
||||
break;
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
// called with scope of series
|
||||
$.jqplot.PointLabels.draw = function (sctx, options, plot) {
|
||||
var p = this.plugins.pointLabels;
|
||||
// set labels again in case they have changed.
|
||||
p.setLabels.call(this);
|
||||
// remove any previous labels
|
||||
for (var i=0; i<p._elems.length; i++) {
|
||||
// Memory Leaks patch
|
||||
// p._elems[i].remove();
|
||||
p._elems[i].emptyForce();
|
||||
}
|
||||
p._elems.splice(0, p._elems.length);
|
||||
|
||||
if (p.show) {
|
||||
var ax = '_'+this._stackAxis+'axis';
|
||||
|
||||
if (!p.formatString) {
|
||||
p.formatString = this[ax]._ticks[0].formatString;
|
||||
p.formatter = this[ax]._ticks[0].formatter;
|
||||
}
|
||||
|
||||
var pd = this._plotData;
|
||||
var ppd = this._prevPlotData;
|
||||
var xax = this._xaxis;
|
||||
var yax = this._yaxis;
|
||||
var elem, helem;
|
||||
|
||||
for (var i=0, l=p._labels.length; i < l; i++) {
|
||||
var label = p._labels[i];
|
||||
|
||||
if (label == null || (p.hideZeros && parseInt(label, 10) == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
label = p.formatter(p.formatString, label);
|
||||
|
||||
helem = document.createElement('div');
|
||||
p._elems[i] = $(helem);
|
||||
|
||||
elem = p._elems[i];
|
||||
|
||||
|
||||
elem.addClass('jqplot-point-label jqplot-series-'+this.index+' jqplot-point-'+i);
|
||||
elem.css('position', 'absolute');
|
||||
elem.insertAfter(sctx.canvas);
|
||||
|
||||
if (p.escapeHTML) {
|
||||
elem.text(label);
|
||||
}
|
||||
else {
|
||||
elem.html(label);
|
||||
}
|
||||
var location = p.location;
|
||||
if ((this.fillToZero && pd[i][1] < 0) || (this.fillToZero && this._type === 'bar' && this.barDirection === 'horizontal' && pd[i][0] < 0) || (this.waterfall && parseInt(label, 10)) < 0) {
|
||||
location = oppositeLocations[locationIndicies[location]];
|
||||
}
|
||||
|
||||
|
||||
var ell = xax.u2p(pd[i][0]) + p.xOffset(elem, location);
|
||||
var elt = yax.u2p(pd[i][1]) + p.yOffset(elem, location);
|
||||
|
||||
// we have stacked chart but are not showing stacked values,
|
||||
// place labels in center.
|
||||
if (this._stack && !p.stackedValue) {
|
||||
if (this.barDirection === "vertical") {
|
||||
elt = (this._barPoints[i][0][1] + this._barPoints[i][1][1]) / 2 + plot._gridPadding.top - 0.5 * elem.outerHeight(true);
|
||||
}
|
||||
else {
|
||||
ell = (this._barPoints[i][2][0] + this._barPoints[i][0][0]) / 2 + plot._gridPadding.left - 0.5 * elem.outerWidth(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.renderer.constructor == $.jqplot.BarRenderer) {
|
||||
if (this.barDirection == "vertical") {
|
||||
ell += this._barNudge;
|
||||
}
|
||||
else {
|
||||
elt -= this._barNudge;
|
||||
}
|
||||
}
|
||||
elem.css('left', ell);
|
||||
elem.css('top', elt);
|
||||
var elr = ell + elem.width();
|
||||
var elb = elt + elem.height();
|
||||
var et = p.edgeTolerance;
|
||||
var scl = $(sctx.canvas).position().left;
|
||||
var sct = $(sctx.canvas).position().top;
|
||||
var scr = sctx.canvas.width + scl;
|
||||
var scb = sctx.canvas.height + sct;
|
||||
// if label is outside of allowed area, remove it
|
||||
if (ell - et < scl || elt - et < sct || elr + et > scr || elb + et > scb) {
|
||||
elem.remove();
|
||||
}
|
||||
|
||||
elem = null;
|
||||
helem = null;
|
||||
}
|
||||
|
||||
// finally, animate them if the series is animated
|
||||
// if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) {
|
||||
// var sel = '.jqplot-point-label.jqplot-series-'+this.index;
|
||||
// $(sel).hide();
|
||||
// $(sel).fadeIn(1000);
|
||||
// }
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
$.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init);
|
||||
$.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw);
|
||||
})(jQuery);
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/
|
||||
.jqplot-target {
|
||||
position: relative;
|
||||
color: #666666;
|
||||
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
/* height: 300px;
|
||||
width: 400px;*/
|
||||
}
|
||||
|
||||
/*rules applied to all axes*/
|
||||
.jqplot-axis {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.jqplot-xaxis {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.jqplot-x2axis {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.jqplot-yaxis {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/*rules applied to all axis tick divs*/
|
||||
.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
.jqplot-xaxis-tick {
|
||||
top: 0px;
|
||||
/* initial position untill tick is drawn in proper place */
|
||||
left: 15px;
|
||||
/* padding-top: 10px;*/
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jqplot-x2axis-tick {
|
||||
bottom: 0px;
|
||||
/* initial position untill tick is drawn in proper place */
|
||||
left: 15px;
|
||||
/* padding-bottom: 10px;*/
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.jqplot-yaxis-tick {
|
||||
right: 0px;
|
||||
/* initial position untill tick is drawn in proper place */
|
||||
top: 15px;
|
||||
/* padding-right: 10px;*/
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.jqplot-yaxis-tick.jqplot-breakTick {
|
||||
right: -20px;
|
||||
margin-right: 0px;
|
||||
padding:1px 5px 1px 5px;
|
||||
/*background-color: white;*/
|
||||
z-index: 2;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
|
||||
left: 0px;
|
||||
/* initial position untill tick is drawn in proper place */
|
||||
top: 15px;
|
||||
/* padding-left: 10px;*/
|
||||
/* padding-right: 15px;*/
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.jqplot-yMidAxis-tick {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.jqplot-xaxis-label {
|
||||
margin-top: 10px;
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-x2axis-label {
|
||||
margin-bottom: 10px;
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-yaxis-label {
|
||||
margin-right: 10px;
|
||||
/* text-align: center;*/
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-yMidAxis-label {
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
|
||||
/* text-align: center;*/
|
||||
font-size: 11pt;
|
||||
margin-left: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jqplot-meterGauge-tick {
|
||||
font-size: 0.75em;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.jqplot-meterGauge-label {
|
||||
font-size: 1em;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
table.jqplot-table-legend {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
table.jqplot-table-legend, table.jqplot-cursor-legend {
|
||||
background-color: rgba(255,255,255,0.6);
|
||||
border: 1px solid #cccccc;
|
||||
position: absolute;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
td.jqplot-table-legend {
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
/*
|
||||
These rules could be used instead of assigning
|
||||
element styles and relying on js object properties.
|
||||
*/
|
||||
|
||||
/*
|
||||
td.jqplot-table-legend-swatch {
|
||||
padding-top: 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
|
||||
padding-top: 0px;
|
||||
}
|
||||
*/
|
||||
|
||||
td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jqplot-table-legend .jqplot-series-hidden {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
div.jqplot-table-legend-swatch-outline {
|
||||
border: 1px solid #cccccc;
|
||||
padding:1px;
|
||||
}
|
||||
|
||||
div.jqplot-table-legend-swatch {
|
||||
width:0px;
|
||||
height:0px;
|
||||
border-top-width: 5px;
|
||||
border-bottom-width: 5px;
|
||||
border-left-width: 6px;
|
||||
border-right-width: 6px;
|
||||
border-top-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-left-style: solid;
|
||||
border-right-style: solid;
|
||||
}
|
||||
|
||||
.jqplot-title {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
padding-bottom: 0.5em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
table.jqplot-cursor-tooltip {
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
|
||||
.jqplot-cursor-tooltip {
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 0.75em;
|
||||
white-space: nowrap;
|
||||
background: rgba(208,208,208,0.5);
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
|
||||
border: 1px solid #cccccc;
|
||||
font-size: 0.75em;
|
||||
white-space: nowrap;
|
||||
background: rgba(208,208,208,0.5);
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.jqplot-point-label {
|
||||
font-size: 0.75em;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
td.jqplot-cursor-legend-swatch {
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.jqplot-cursor-legend-swatch {
|
||||
width: 1.2em;
|
||||
height: 0.7em;
|
||||
}
|
||||
|
||||
.jqplot-error {
|
||||
/* Styles added to the plot target container when there is an error go here.*/
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jqplot-error-message {
|
||||
/* Styling of the custom error message div goes here.*/
|
||||
position: relative;
|
||||
top: 46%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div.jqplot-bubble-label {
|
||||
font-size: 0.8em;
|
||||
/* background: rgba(90%, 90%, 90%, 0.15);*/
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
color: rgb(20%, 20%, 20%);
|
||||
}
|
||||
|
||||
div.jqplot-bubble-label.jqplot-bubble-label-highlight {
|
||||
background: rgba(90%, 90%, 90%, 0.7);
|
||||
}
|
||||
|
||||
div.jqplot-noData-container {
|
||||
text-align: center;
|
||||
background-color: rgba(96%, 96%, 96%, 0.3);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
|||
--- resources/jquery.jqplot.js.orig 2016-08-30 18:50:53.056335603 +0200
|
||||
+++ resources/jquery.jqplot.js 2016-08-30 18:51:33.211372314 +0200
|
||||
@@ -9139,7 +9139,7 @@
|
||||
|
||||
for (var i=0; i<wl; i++) {
|
||||
w += words[i];
|
||||
- if (context.measureText(w).width > tagwidth) {
|
||||
+ if (context.measureText(w).width > tagwidth && w.length > words[i].length) {
|
||||
breaks.push(i);
|
||||
w = '';
|
||||
i--;
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
/*Art is code*/
|
||||
body {
|
||||
background-color:#cdd5da;
|
||||
background: -webkit-linear-gradient( bottom, #cdd5da, #e8e7e7);
|
||||
background: -moz-linear-gradient( bottom, #cdd5da, #e8e7e7);
|
||||
background: -ms-linear-gradient( bottom, #cdd5da, #e8e7e7);
|
||||
background: -o-linear-gradient( bottom, #cdd5da, #e8e7e7);
|
||||
background: linear-gradient( to bottom, #cdd5da, #e8e7e7);
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown:hover ul.dropdown-menu{
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown ul.dropdown-menu{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#pgbadger-brand {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul#slides li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
h3, h3 small {
|
||||
font-size: 1.1em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
}
|
||||
|
||||
h3 small {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.analysis-item {
|
||||
background: #fff;
|
||||
margin-bottom:2em;
|
||||
padding-bottom:15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #5f5555;
|
||||
}
|
||||
|
||||
h1.page-header {
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0;
|
||||
color: #5f5555;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color:#5f5555;
|
||||
}
|
||||
|
||||
.nav-pills, .nav-tabs {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.col-md-8 .tabbable {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#global-stats .tab-content {
|
||||
margin: 2em 0 3em 0;
|
||||
}
|
||||
|
||||
#global-stats .tab-content li {
|
||||
display: block;
|
||||
width:14%; /*160/960*/
|
||||
float:left;
|
||||
margin-left: 3%;
|
||||
}
|
||||
|
||||
#global-stats .tab-content li.first {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.well {
|
||||
background: #f9f9f9;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.key-figures ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.key-figures li {
|
||||
list-style-type: none;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.figure {
|
||||
font-weight: bold;
|
||||
font-size: 1.4em;
|
||||
color:#2e8aa5;
|
||||
}
|
||||
|
||||
.figure-label {
|
||||
display: block;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.mfigure {
|
||||
font-weight: bold;
|
||||
font-size: 1.4em;
|
||||
color:#d26115;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.smfigure {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
color:#d26115;
|
||||
}
|
||||
|
||||
.mfigure small {
|
||||
font-weight: bold;
|
||||
font-size: 0.6em;
|
||||
color:#ffffff;
|
||||
}
|
||||
|
||||
.smfigure small {
|
||||
font-weight: bold;
|
||||
font-size: 0.6em;
|
||||
color:#ffffff;
|
||||
}
|
||||
|
||||
.hfigure {
|
||||
font-weight: bold;
|
||||
font-size: 1.0em;
|
||||
color:#8dbd0f;
|
||||
}
|
||||
|
||||
.hfigure small {
|
||||
font-weight: bold;
|
||||
font-size: 0.6em;
|
||||
color:#ffffff;
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
background: #5f5555;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.navbar-inverse .brand, .navbar-inverse .nav > li > a {
|
||||
color:#eee;
|
||||
}
|
||||
|
||||
.linegraph {
|
||||
width : 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.piegraph {
|
||||
width : 100%;
|
||||
height: 400px;
|
||||
}
|
||||
.histo-graph {
|
||||
width : 100%;
|
||||
height: 140px;
|
||||
}
|
||||
.duration-histo-graph {
|
||||
width : 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
@media (min-width:969px) {
|
||||
#show-hide-menu {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.navbar ul.collapse {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:968px) {
|
||||
#show-hide-menu {
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
#global-stats .tab-content li {
|
||||
display: block;
|
||||
width:auto; /*160/960*/
|
||||
float:none;
|
||||
margin-left: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
ul#slides {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul#slides li div div {
|
||||
padding:0 1.5em;
|
||||
}
|
||||
|
||||
.linegraph {
|
||||
width : 94.5%;
|
||||
}
|
||||
|
||||
.piegraph {
|
||||
width : 94.5%;
|
||||
}
|
||||
|
||||
.histo-graph {
|
||||
width : 84.5%;
|
||||
}
|
||||
|
||||
.duration-histo-graph {
|
||||
width : 94.5%;
|
||||
}
|
||||
|
||||
.key-figures ul {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.navbar .nav {
|
||||
margin: 0 3em 2em 3em;
|
||||
}
|
||||
|
||||
.navbar .nav > li {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.navbar ul.collapse:hover {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
div#littleToc { display:none; }
|
||||
html>body div#littleToc { display:block; background-color:white; color:black; position:fixed; bottom:10px; right:10px; width:50px; font-size:11px; text-align:left; border:0px; }
|
||||
div#littleToc div#littleTocTitle { font-weight:bold; text-align:center;padding:2px; }
|
||||
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; }
|
||||
|
||||
.jqplot-graph { color: #ac1316; z-index: 99;}
|
||||
.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; }
|
||||
.sql-smallsize {width: 450px; }
|
||||
.sql-mediumsize {width: 600px;max-height:200px;overflow:auto;}
|
||||
.sql-largesize {width: 950px;max-height:200px;overflow:auto;}
|
||||
.pre-smallfont {font-size:10px;}
|
||||
|
||||
.jqplot-target {
|
||||
position: relative;
|
||||
color: #333;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.jqplot-highlighter-tooltip {
|
||||
border: 0px;
|
||||
font-size: 1.0em;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
color:#ffffff;
|
||||
background: #5f5555;
|
||||
border:1px solid #5f5555;
|
||||
border:1px solid rgba(0, 0, 0, 0.2);
|
||||
-webkit-border-radius:6px;
|
||||
-moz-border-radius:6px;
|
||||
border-radius:6px;
|
||||
-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.jqplot-title {
|
||||
font-size: 1.1em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #5f5555;
|
||||
}
|
||||
|
||||
#pgbadgerModal .modal-dialog
|
||||
{
|
||||
width: 850px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#pgbadgerModal .modal-body
|
||||
{
|
||||
height: 100%;
|
||||
width: 830px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.error-pre {
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
line-height: 1.42857143;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
|
||||
/* <![CDATA[ */
|
||||
|
||||
function create_download_button (buttonid, cssclass) {
|
||||
if (cssclass == undefined) {
|
||||
cssclass ='btn btn-info';
|
||||
}
|
||||
document.writeln('<button type="button" id="download'+buttonid+'" class="'+cssclass+'">Download</button>');
|
||||
}
|
||||
|
||||
function add_download_button_event (buttonid, divid) {
|
||||
|
||||
jQuery('#download'+buttonid).click( function() {
|
||||
$('#pgbadgerModal img').attr('src', $('#'+divid).jqplotToImageStr({}));
|
||||
$('#pgbadgerModal').modal('toggle');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function create_linegraph (divid, charttitle, ylabel, arr_series, lineseries) {
|
||||
return $.jqplot(divid, lineseries, {
|
||||
seriesColors: ['#6e9dc9','#f4ab3a','#ac7fa8','#8dbd0f',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc"],
|
||||
seriesDefaults: { markerOptions: {show: false}, lineWidth:1 },
|
||||
grid: { borderWidth: 1, background: '#ffffff'},
|
||||
title: charttitle,
|
||||
series: arr_series,
|
||||
axes: {
|
||||
xaxis: {
|
||||
renderer:$.jqplot.DateAxisRenderer,
|
||||
tickOptions:{ angle: -30, textColor: '#333' },
|
||||
},
|
||||
yaxis: {
|
||||
renderer: $.jqplot.LogAxisRenderer,
|
||||
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
|
||||
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
|
||||
tickOptions: {
|
||||
textColor: '#333',
|
||||
formatter: function(format, value) { return pretty_print_number(value, 0, ylabel); }
|
||||
},
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
location: 'nw',
|
||||
},
|
||||
cursor:{
|
||||
show: true,
|
||||
zoom: true,
|
||||
showTooltip:false,
|
||||
looseZoom: true,
|
||||
followMouse: true,
|
||||
},
|
||||
highlighter: {
|
||||
show: true,
|
||||
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
|
||||
var dateToDisplay = new Date(plot.data[seriesIndex][pointIndex][0]);
|
||||
var textToShow = '<div>On '+dateToDisplay.toString();
|
||||
for (var i=0; i<plot.data.length;i++) {
|
||||
textToShow += '<br><span class="mfigure">'+pretty_print_number(plot.data[i][pointIndex][1], 2, plot.series[i].label)+' <small>'+plot.series[i].label+'</small></span>';
|
||||
}
|
||||
textToShow += '</div>';
|
||||
return textToShow;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function create_piechart(divid, title, data) {
|
||||
return $.jqplot(divid, [data],
|
||||
{
|
||||
seriesColors: ['#6e9dc9','#f4ab3a','#ac7fa8','#8dbd0f',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc","#4bb2c5", "#c5b47f", "#EAA228", "#579575", "#839557","#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621","#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381","#959E5C", "#C7AF7B", "#478396", "#907294"],
|
||||
grid: { borderWidth: 1, background: '#ffffff'},
|
||||
title: title,
|
||||
seriesDefaults: {
|
||||
renderer: $.jqplot.PieRenderer,
|
||||
rendererOptions: {
|
||||
showDataLabels: true
|
||||
}
|
||||
},
|
||||
legend: { show:true, location: 'e' },
|
||||
highlighter: {
|
||||
show: true,
|
||||
tooltipLocation:'sw',
|
||||
useAxesFormatters:false,
|
||||
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
|
||||
var textToShow = '<div>';
|
||||
//textToShow += '<span class="mfigure">'+pretty_print_number(plot.data[0][pointIndex][1], 2)+' <small>'+plot.data[0][pointIndex][0]+'</small></span>';
|
||||
textToShow += '<span class="mfigure">'+format_number(plot.data[0][pointIndex][1])+' <small>'+plot.data[0][pointIndex][0]+'</small></span>';
|
||||
textToShow += '</div>';
|
||||
return textToShow;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function create_bargraph (divid, title, ytitle, data, y2title) {
|
||||
return $.jqplot(divid, data, {
|
||||
grid: { borderWidth: 1, background: '#ffffff'},
|
||||
title: title,
|
||||
seriesDefaults: {
|
||||
rendererOptions: {
|
||||
barPadding: 4,
|
||||
barMargin: 5,
|
||||
}
|
||||
},
|
||||
seriesColors: ['#6e9dc9','#8dbd0f','#f4ab3a','#ac7fa8',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc"],
|
||||
series:[
|
||||
{ renderer:$.jqplot.BarRenderer, label: ytitle},
|
||||
{ yaxis:'y2axis', label: y2title, markerOptions: {show: false}, lineWidth:1 }
|
||||
],
|
||||
axes: {
|
||||
xaxis: {
|
||||
renderer: $.jqplot.CategoryAxisRenderer,
|
||||
drawMajorGridlines: false,
|
||||
drawMajorTickMarks: false,
|
||||
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
|
||||
tickOptions: {
|
||||
angle: -30,
|
||||
textColor: '#333',
|
||||
formatString:'%H:%M',
|
||||
fontFamily:'Helvetica',
|
||||
fontSize: '8pt'
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
autoscale:true,
|
||||
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
|
||||
tickOptions:{ textColor: '#333' },
|
||||
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
|
||||
tickOptions: {
|
||||
textColor: '#333',
|
||||
formatter: function(format, value) { return pretty_print_number(value, 1, ytitle); },
|
||||
fontFamily:'Helvetica',
|
||||
fontSize: '8pt'
|
||||
},
|
||||
label: ytitle
|
||||
},
|
||||
y2axis: {
|
||||
autoscale:true,
|
||||
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
|
||||
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
|
||||
tickOptions: {
|
||||
textColor: '#8dbd0f',
|
||||
formatter: function(format, value) { return pretty_print_number(value, 1, y2title); },
|
||||
fontFamily:'Helvetica',
|
||||
fontSize: '8pt'
|
||||
},
|
||||
rendererOptions: {
|
||||
alignTicks: true,
|
||||
},
|
||||
label: y2title,
|
||||
|
||||
}
|
||||
},
|
||||
highlighter: {
|
||||
show: true,
|
||||
tooltipLocation:'ne',
|
||||
useAxesFormatters:false,
|
||||
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
|
||||
var textToShow = '<div style="z-index: 99;">At '+plot.data[0][pointIndex][0];
|
||||
for (var i=0; i<plot.data.length;i++) {
|
||||
textToShow += '<br><span class="mfigure">'+pretty_print_number(plot.data[i][pointIndex][1], 2, plot.series[i].label)+' <small>'+plot.series[i].label+'</small></span>';
|
||||
}
|
||||
textToShow += '</div>';
|
||||
return textToShow;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pretty_print_number(val, scale, type)
|
||||
{
|
||||
|
||||
if ( (scale == undefined) || ((scale == 0) && (val != 0)) ) {
|
||||
scale = 1;
|
||||
};
|
||||
|
||||
if (type != undefined) {
|
||||
type = type.toLowerCase();
|
||||
|
||||
if (type.search('size') >= 0) {
|
||||
if (Math.abs(val) >= 1125899906842624) {
|
||||
val = (val / 1125899906842624);
|
||||
return val.toFixed(scale) + " PiB";
|
||||
} else if (Math.abs(val) >= 1099511627776) {
|
||||
val = (val / 1099511627776);
|
||||
return val.toFixed(scale) + " TiB";
|
||||
} else if (Math.abs(val) >= 1073741824) {
|
||||
val = (val / 1073741824);
|
||||
return val.toFixed(scale) + " GiB";
|
||||
} else if (Math.abs(val) >= 1048576) {
|
||||
val = (val / 1048576);
|
||||
return val.toFixed(scale) + " MiB";
|
||||
} else if (Math.abs(val) >= 1024) {
|
||||
val = (val / 1024);
|
||||
return val.toFixed(scale) + " KiB";
|
||||
} else {
|
||||
return val + " B";
|
||||
}
|
||||
} else if (type.search('duration') >= 0) {
|
||||
if (Math.abs(val) >= 1000) {
|
||||
val = (val / 1000);
|
||||
return val.toFixed(scale) + " sec";
|
||||
} else {
|
||||
return val.toFixed(scale) + " ms";
|
||||
}
|
||||
} else {
|
||||
if (Math.abs(val) >= 1000000000000000) {
|
||||
val = (val / 1000000000000000);
|
||||
return val.toFixed(scale) + " P";
|
||||
} else if (Math.abs(val) >= 1000000000000) {
|
||||
val = (val / 1000000000000);
|
||||
return val.toFixed(scale) + " T";
|
||||
} else if (Math.abs(val) >= 1000000000) {
|
||||
val = (val / 1000000000);
|
||||
return val.toFixed(scale) + " G";
|
||||
} else if (Math.abs(val) >= 1000000) {
|
||||
val = (val / 1000000);
|
||||
return val.toFixed(scale) + " M";
|
||||
} else if (Math.abs(val) >= 1000) {
|
||||
val = (val / 1000);
|
||||
return val.toFixed(scale) + " K";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return val.toFixed(scale);
|
||||
}
|
||||
|
||||
function format_number(val) {
|
||||
var decimal = 2;
|
||||
var msep = ',';
|
||||
var deci = Math.round( Math.pow(10,decimal)*(Math.abs(val)-Math.floor(Math.abs(val)))) ;
|
||||
val = Math.floor(Math.abs(val));
|
||||
if ((decimal==0)||(deci==Math.pow(10,decimal))) {deci=0;}
|
||||
var val_format=val+"";
|
||||
var nb=val_format.length;
|
||||
for (var i=1;i<4;i++) {
|
||||
if (val>=Math.pow(10,(3*i))) {
|
||||
val_format=val_format.substring(0,nb-(3*i))+msep+val_format.substring(nb-(3*i));
|
||||
}
|
||||
}
|
||||
if (decimal>0) {
|
||||
var decim="";
|
||||
for (var j=0;j<(decimal-deci.toString().length);j++) {decim+="0";}
|
||||
deci=decim+deci.toString();
|
||||
if (deci > 0) {
|
||||
val_format=val_format+"."+deci;
|
||||
}
|
||||
}
|
||||
if (parseFloat(val)<0) {val_format="-"+val_format;}
|
||||
return val_format;
|
||||
}
|
||||
|
||||
|
||||
function draw_charts() {
|
||||
window.charts = $.grep(window.charts, function(chart) {
|
||||
var chart_copy = chart.slice();
|
||||
var type = chart_copy.shift();
|
||||
var divid = chart[1];
|
||||
if ($('#' + divid).is(':visible')) {
|
||||
window['create_' + type].apply(null, chart_copy);
|
||||
// chart loaded, don't keep it in the list
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
jQuery(function (){
|
||||
jQuery('#pgbadger-brand').tooltip();
|
||||
|
||||
jQuery("#pop-infos").popover('hide');
|
||||
jQuery('.slide').hide();
|
||||
jQuery('.active-slide').show();
|
||||
|
||||
/* Go to specific slide and report from external link call */
|
||||
var activeMenu = location.hash;
|
||||
if (activeMenu) {
|
||||
activeMenu = activeMenu.substring(1);
|
||||
var lkobj = document.getElementById(activeMenu);
|
||||
var liId = jQuery(lkobj).parent().attr("id");
|
||||
if (liId != undefined) {
|
||||
var slideId = '#'+liId;
|
||||
jQuery('#main-container li.slide').removeClass('active-slide').hide();
|
||||
jQuery(slideId).addClass("active-slide").fadeIn();
|
||||
window.location.hash = '#'+activeMenu;
|
||||
}
|
||||
}
|
||||
|
||||
jQuery('.navbar li.dropdown').click(function() {
|
||||
var id = jQuery(this).attr("id");
|
||||
id = id.substring(5);
|
||||
var slideId = '#'+id+'-slide';
|
||||
var currentSlide = jQuery('#main-container .active-slide').attr("id");
|
||||
currentSlide = '#'+currentSlide;
|
||||
|
||||
if(slideId != currentSlide) {
|
||||
jQuery('#main-container li.slide').removeClass('active-slide').hide();
|
||||
jQuery(slideId).addClass("active-slide").fadeIn();
|
||||
}
|
||||
scrollTo(0,0);
|
||||
draw_charts();
|
||||
});
|
||||
|
||||
jQuery('.navbar li ul li').click(function() {
|
||||
var liId = jQuery(this).parent().parent().attr("id");
|
||||
var id = liId.substring(5);
|
||||
var slideId = '#'+id+'-slide';
|
||||
jQuery('#main-container li.slide').removeClass('active-slide').hide();
|
||||
jQuery(slideId).addClass("active-slide").fadeIn();
|
||||
draw_charts();
|
||||
});
|
||||
|
||||
draw_charts();
|
||||
});
|
||||
|
||||
jQuery(document).ready(function () {
|
||||
jQuery('.sql').dblclick(function () {
|
||||
if (this.style == undefined || this.style.whiteSpace == 'pre') {
|
||||
this.style.whiteSpace ='normal';
|
||||
} else {
|
||||
this.style.whiteSpace = 'pre';
|
||||
}
|
||||
});
|
||||
jQuery('.icon-copy').click(function () {
|
||||
var obj = $(this).parent()[0];
|
||||
if (window.getSelection) {
|
||||
var sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(obj);
|
||||
sel.addRange(range);
|
||||
} else if (document.selection) {
|
||||
var textRange = document.body.createTextRange();
|
||||
textRange.moveToElementText(obj);
|
||||
textRange.select();
|
||||
}
|
||||
});
|
||||
|
||||
function shiftWindow() {
|
||||
scrollBy(0, -50);
|
||||
}
|
||||
|
||||
if (location.hash) {
|
||||
shiftWindow();
|
||||
}
|
||||
window.addEventListener("hashchange",shiftWindow);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,5 @@
|
|||
select user from list where id = dong or 1=1 0.998
|
||||
select * from data where uname='a' and 1=1 and 'a'='a' 0.953
|
||||
select * from user_name where id = 'admin' and password='1234';drop table user;-- 0.908
|
||||
select * from user where id='1111' and password='1234' and convert(char, no) -- 0.863
|
||||
select * from user where id='1111' union select * from member where id='admin'--' and password='1234' 0.818
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
use Test::Simple tests => 2;
|
||||
|
||||
my $ret = `perl -wc pgbadger 2>&1`;
|
||||
ok( $? == 0, "PERL syntax check");
|
||||
|
||||
$ret = `podchecker doc/*.pod 2>&1`;
|
||||
ok( $? == 0, "pod syntax check");
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
use Test::Simple tests => 10;
|
||||
|
||||
my $LOG = 't/fixtures/light.postgres.log.bz2';
|
||||
my $SYSLOG = 't/fixtures/pg-syslog.1.bz2';
|
||||
my $BIN = 't/fixtures/light.postgres.bin';
|
||||
my $JSON = 't/out.json';
|
||||
my $TEXT = 't/out.txt';
|
||||
|
||||
my $ret = `perl pgbadger --help`;
|
||||
ok( $? == 0, "Inline help");
|
||||
|
||||
$ret = `perl pgbadger -q -o - $LOG`;
|
||||
ok( $? == 0 && length($ret) > 0, "Light log report to stdout");
|
||||
|
||||
`rm -f out.html`;
|
||||
$ret = `perl pgbadger -q --outdir '.' $LOG`;
|
||||
ok( $? == 0 && -e "out.html", "Light log report to HTML");
|
||||
|
||||
$ret = `perl pgbadger -q -o $BIN $LOG`;
|
||||
ok( $? == 0 && -e "$BIN", "Light log to binary");
|
||||
|
||||
`rm -f $JSON`;
|
||||
$ret = `perl pgbadger -q -o $JSON --format binary $BIN`;
|
||||
$ret = `cat $JSON | perl -pe 's/.*"SELECT":(\\d+),.*/\$1/'`;
|
||||
ok( $? == 0 && $ret > 0, "From binary to JSON");
|
||||
|
||||
`mkdir t/test_incr/`;
|
||||
$ret = `perl pgbadger -q -O t/test_incr -I --extra-files $LOG`;
|
||||
ok( $? == 0 && -e "t/test_incr/2017/09/06/index.html"
|
||||
&& -e "t/test_incr/2017/week-37/index.html", "Incremental mode report");
|
||||
$ret = `grep 'src="../../../.*/bootstrap.min.js"' t/test_incr/2017/09/06/index.html`;
|
||||
ok( $? == 0 && substr($ret, 32, 14) eq 'src="../../../', "Ressources files in incremental mode");
|
||||
|
||||
`rm -f $JSON`;
|
||||
$ret = `bunzip2 -c $LOG | perl pgbadger -q -o $JSON -`;
|
||||
$ret = `cat $JSON | perl -pe 's/.*"SELECT":(\\d+),.*/\$1/'`;
|
||||
ok( $? == 0 && $ret > 0, "Light log from STDIN");
|
||||
|
||||
$ret = `perl pgbadger -q --outdir '.' -o $TEXT -o $JSON -o - -x json $LOG > t/ret.json`;
|
||||
my $ret2 = `stat --printf='%s' t/ret.json $TEXT $JSON`;
|
||||
chomp($ret);
|
||||
ok( $? == 0 && $ret2 eq '13276116001132761', "Multiple output format '$ret2' = '13276116001132761'");
|
||||
|
||||
$ret = `perl pgbadger -q -o - $SYSLOG`;
|
||||
ok( $? == 0 && (length($ret) >= 24060), "syslog report to stdout");
|
||||
|
||||
`rm -f out.html`;
|
||||
# Remove files generated during the tests
|
||||
`rm -f out.html`;
|
||||
`rm -r $JSON`;
|
||||
`rm -r $TEXT`;
|
||||
`rm -f $BIN`;
|
||||
`rm -rf t/test_incr/`;
|
||||
`rm t/ret.json`
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
use Test::Simple tests => 8;
|
||||
use JSON::XS;
|
||||
|
||||
my $json = new JSON::XS;
|
||||
|
||||
my $LOG = 't/fixtures/light.postgres.log.bz2 t/fixtures/pgbouncer.log.gz';
|
||||
my $HLOG = 't/fixtures/logplex.gz';
|
||||
my $BIN = 'out.bin';
|
||||
my $OUT = 'out.json';
|
||||
|
||||
my $ret = `perl pgbadger -q -o $BIN $LOG`;
|
||||
ok( $? == 0, "Generate intermediate binary file from log");
|
||||
|
||||
$ret = `perl pgbadger -q -o $OUT --format binary $BIN`;
|
||||
ok( $? == 0, "Generate json report from binary file");
|
||||
|
||||
`rm -f $BIN`;
|
||||
|
||||
my $json_ref = $json->decode(`cat $OUT`);
|
||||
|
||||
#
|
||||
# Assert that analyzing json file provide the right results
|
||||
#
|
||||
ok( $json_ref->{database_info}{postgres}{postgres}{count} == 629, "Consistent count");
|
||||
|
||||
ok( $json_ref->{overall_stat}{postgres}{histogram}{query_total} == 629, "Consistent query_total");
|
||||
|
||||
ok( $json_ref->{overall_stat}{postgres}{peak}{"2017-09-06 08:48:45"}{write} == 1, "Consistent peak write");
|
||||
|
||||
ok( $json_ref->{pgb_session_info}{chronos}{20180912}{16}{count} == 63943, "pgBouncer connections");
|
||||
|
||||
`rm -f $OUT`;
|
||||
|
||||
$ret = `perl pgbadger -q -o $OUT $HLOG`;
|
||||
ok( $? == 0, "Generate json report for heroku log file");
|
||||
$json_ref = $json->decode(`cat $OUT`);
|
||||
ok( $json_ref->{database_info}{postgres}{GREEN}{"CTE|duration"} eq "21761.546", "Consistent CTE duration");
|
||||
|
||||
`rm -f $OUT`;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,91 @@
|
|||
------------------------------------------------------------------------------
|
||||
pgbadger_tools - Tools based on pgBadger binary files
|
||||
|
||||
This program is open source, licensed under the PostgreSQL Licence.
|
||||
For license terms, see the LICENSE file.
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
This program is first used to demonstrate how to deal with pgBadger binary
|
||||
files to build your own tool. If you don't want to rewrite all, you can
|
||||
post pull request of this tool with your own option so that pgbadger_tools
|
||||
will do what your need.
|
||||
|
||||
All statistics and data gathered by pgBadger binary file is load into memory
|
||||
using method load_stat(). To generate a binary file you simply have to set
|
||||
the output file extension to .bin. Note that if you use incremental mode with
|
||||
pgbadger it already generate binary file in daily directories.
|
||||
|
||||
example: pgbadger -o out.bin /var/log/postgresql/postgresql.log
|
||||
|
||||
PGBADGER_TOOLS OPTIONS AND TOOLS
|
||||
--------------------------------
|
||||
|
||||
**Auto explain**
|
||||
|
||||
The first option added is --explain-slowest that dump top slowest queries in
|
||||
an explain analyze statement, ready to be executed. An extended version could
|
||||
be created to automatically execute those explain statement on the database.
|
||||
|
||||
./pgbadger_tools --explain-slowest out.bin
|
||||
|
||||
You can use several binary file as input, for example from an incremental
|
||||
output directory:
|
||||
|
||||
./pgbadger_tools --explain-slowest /var/www/pgbadger/2014/09/03/*.bin
|
||||
|
||||
The explain tool is base on an original work of Rodolphe Quiedeville, that was
|
||||
first sent as a pull request on pgbadger. But we need to keep pgBadger simple
|
||||
so the place of this kind of addons are more in peripheral tools. It is possible
|
||||
that in future, the tsung and json output will be removed from pgbadger and put
|
||||
in pgbadger_tools
|
||||
|
||||
If you want to chain tools and HTML report, you can proceed as follow:
|
||||
|
||||
pgbadger -o out.bin /var/log/postgresql/postgresql.log
|
||||
./pgbadger_tools --explain-slowest out.bin > explain_top_slowest.sql
|
||||
pgbadger -o report.html out.bin
|
||||
|
||||
**CSV output**
|
||||
_
|
||||
When using the following option, pgbadger_tools will export top queries section
|
||||
results in csv format, for example for later analysis. This is an original work
|
||||
of bricklen.
|
||||
|
||||
Here are the supported options, only one of the following is mandatory:
|
||||
|
||||
--csv-time-consuming : generate a CSV file with top time consuming queries
|
||||
--csv-slowest : generate a CSV file with top slowest queries
|
||||
--csv-normalized : generate a CSV file with top normalized queries
|
||||
|
||||
./pgbadger_tools --csv-time-consuming out.bin
|
||||
|
||||
Those options can not be used together. By default the output file is named
|
||||
out.csv, you can use the --csv-filename option to renamed this file. Ex:
|
||||
|
||||
./pgbadger_tools --csv-slowest --csv-filename slowest.csv out.bin
|
||||
|
||||
Option to limit top queries to minimum duration:
|
||||
|
||||
--max-duration MS : set the number of milliseconds above which queries
|
||||
will not be reported. Use it if you want to auto
|
||||
execute explain statements.
|
||||
|
||||
If you want to chain tools and HTML report, you can proceed as follow:
|
||||
|
||||
pgbadger -o out.bin /var/log/postgresql/postgresql.log
|
||||
./pgbadger_tools --csv-slowest out.bin --csv-filename top_slowest.csv
|
||||
pgbadger -o report.html out.bin
|
||||
|
||||
|
||||
CONTRIBUTION:
|
||||
-------------
|
||||
|
||||
Feel free to extend pgbadger_tools of new features. To see how to integrate
|
||||
you Perl code in this program, search for "Add your own" string in the code
|
||||
and look at the example searching on explain_slowest.
|
||||
|
||||
Regards,
|
||||
|
||||
--
|
||||
Gilles Darold
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
------------------------------------------------------------------------------
|
||||
updt_embedded_rsc.pl - script to update all embedded javascript and css
|
||||
snippets into the pgbadger Perl script.
|
||||
|
||||
This program is open source, licensed under the PostgreSQL Licence.
|
||||
For license terms, see the LICENSE file.
|
||||
------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env perl
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Script used to update javascript and css files embedded into pgbadger.
|
||||
# It will replace all content after the __DATA__ in the right order with
|
||||
# the files content stored into the resources/min/ directory.
|
||||
#
|
||||
# This script must be executed from the main source repository as follow:
|
||||
#
|
||||
# This script must be executed from the main source repository as follow:
|
||||
# ./tools/updt_embedded_rsc.pl
|
||||
#
|
||||
# The fontawesome.css must also embedded the TrueType font, this is done
|
||||
#-----------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
my $RSC_DIR = 'resources';
|
||||
my $PGBADGER_PROG = 'pgbadger';
|
||||
my $DEST_TMP_FILE = 'pgbadger.new';
|
||||
|
||||
# Ordered resources files list
|
||||
my @rsc_list = qw(jquery.jqplot.css jquery.js jquery.jqplot.js jqplot.pieRenderer.js jqplot.barRenderer.js jqplot.dateAxisRenderer.js jqplot.canvasTextRenderer.js jqplot.categoryAxisRenderer.js jqplot.canvasAxisTickRenderer.js jqplot.highlighter.js jqplot.highlighter.js jqplot.cursor.js jqplot.pointLabels.js bean.js underscore.js bootstrap.css fontawesome.css bootstrap.js pgbadger_slide.js pgbadger.css pgbadger.js);
|
||||
my @min_rsc_list = ();
|
||||
|
||||
if (!-d $RSC_DIR) {
|
||||
die "FATAL: can't find directory: $RSC_DIR.\n";
|
||||
}
|
||||
|
||||
# Apply patch on jquery.jqplot.js to fix infinite loop
|
||||
# May be removed with next jqplot release update
|
||||
`patch -r - -s -N resources/jquery.jqplot.js -i resources/patch-jquery.jqplot.js`;
|
||||
|
||||
# Generate all minified resources files
|
||||
mkdir "$RSC_DIR/min";
|
||||
foreach my $f (@rsc_list) {
|
||||
my $dest = $f;
|
||||
$dest =~ s/\.(js|css)$/.min.$1/;
|
||||
push(@min_rsc_list, $dest);
|
||||
# minify resources files
|
||||
`yui-compressor $RSC_DIR/$f -o $RSC_DIR/min/$dest`;
|
||||
}
|
||||
|
||||
# Embedded fontawesome webfont into the CSS file as base64 data
|
||||
print `base64 -w 0 $RSC_DIR/font/FontAwesome.otf > $RSC_DIR/font/FontAwesome.otf.b64`;
|
||||
open(IN, "$RSC_DIR/font/FontAwesome.otf.b64") or die "FATAL: can't open file $RSC_DIR/font/FontAwesome.otf.b64, $!\n";
|
||||
my $b64_font = <IN>;
|
||||
close(IN);
|
||||
|
||||
# Update minimized fontawesome.css file
|
||||
open(IN, "$RSC_DIR/min/fontawesome.min.css") or die "FATAL: can't open file $RSC_DIR/min/fontawesome.min.css\n";
|
||||
my @content = <IN>;
|
||||
close(IN);
|
||||
open(OUT, ">$RSC_DIR/min/fontawesome.min.css") or die "FATAL: can't write to file $RSC_DIR/min/fontawesome.min.css\n";
|
||||
foreach my $l (@content) {
|
||||
$l =~ s|;src:url.* format.* format\('svg'\);|;src: url('data:font\/opentype;charset=utf-8;base64,$b64_font') format('truetype');|;
|
||||
print OUT $l;
|
||||
}
|
||||
close(OUT);
|
||||
|
||||
if (!-e $PGBADGER_PROG) {
|
||||
die "FATAL: can't find pgbadger script: $PGBADGER_PROG\n";
|
||||
}
|
||||
|
||||
# Extract content of pgbadger script until __DATA__ is found
|
||||
my $content = '';
|
||||
open(IN, $PGBADGER_PROG) or die "FATAL: can't open file $PGBADGER_PROG, $!\n";
|
||||
while (<IN>) {
|
||||
last if (/^__DATA__$/);
|
||||
$content .= $_;
|
||||
}
|
||||
close(IN);
|
||||
|
||||
# Write script base to destination file
|
||||
open(OUT, ">$DEST_TMP_FILE") or die "FATAL: can't write to file $DEST_TMP_FILE, $!\n";
|
||||
print OUT $content;
|
||||
print OUT "__DATA__\n";
|
||||
|
||||
# Append each minified resources file
|
||||
foreach my $f (@min_rsc_list) {
|
||||
print OUT "\nWRFILE: $f\n\n";
|
||||
open(IN, "$RSC_DIR/min/$f") or die "FATAL: can't open file $RSC_DIR/min/$f, $!\n";
|
||||
my @tmp = <IN>;
|
||||
close(IN);
|
||||
print OUT @tmp;
|
||||
}
|
||||
close(OUT);
|
||||
|
||||
# Clobber original pgbadger file.
|
||||
rename($DEST_TMP_FILE, $PGBADGER_PROG) or die "FATAL: can't rename $DEST_TMP_FILE into $PGBADGER_PROG\n";
|
||||
|
||||
exit 0;
|
||||
Binary file not shown.
|
|
@ -0,0 +1,170 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on Thu Aug 18 21:10:36 2022
|
||||
|
||||
@author: LKJ
|
||||
"""
|
||||
import numpy as np
|
||||
from torchtext.vocab import vocab
|
||||
from collections import Counter, OrderedDict
|
||||
from torch.utils.data import Dataset, DataLoader
|
||||
from torchtext.transforms import VocabTransform # 注意:torchtext版本0.12+
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
def get_text():
|
||||
# sentence_list = [ # 假设这是全部的训练语料
|
||||
# "nlp drives computer programs that translate text from one language to another",
|
||||
# "nlp combines computational linguistics rule based modeling of human language with statistical",
|
||||
# "nlp model respond to text or voice data and respond with text",
|
||||
# ]
|
||||
p_sample = []
|
||||
f = open("data\\p_sample.txt",encoding='utf-8') #读取预训练的正样本
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
line = line.split('\t')[1]
|
||||
p_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
n_sample = []
|
||||
f = open("data\\n_sample.txt",encoding='utf-8') #读取预训练的负样本
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
line = line.split('\t')[1]
|
||||
n_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
sentence_list = p_sample + n_sample
|
||||
return sentence_list
|
||||
|
||||
|
||||
class CbowDataSet(Dataset):
|
||||
def __init__(self, text_list, side_window=2):
|
||||
"""
|
||||
构造Word2vec的CBOW采样Dataset
|
||||
:param text_list: 语料
|
||||
:param side_window: 单侧正例(构造背景词)采样数,总正例是:2 * side_window
|
||||
"""
|
||||
super(CbowDataSet, self).__init__()
|
||||
self.side_window = side_window
|
||||
text_vocab, vocab_transform = self.reform_vocab(text_list)
|
||||
self.text_list = text_list # 原始文本
|
||||
self.text_vocab = text_vocab # torchtext的vocab
|
||||
self.vocab_transform = vocab_transform # torchtext的vocab_transform
|
||||
self.cbow_data = self.generate_cbow()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.cbow_data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data_row = self.cbow_data[idx]
|
||||
return data_row[0], data_row[1]
|
||||
|
||||
def reform_vocab(self, text_list):
|
||||
"""根据语料构造torchtext的vocab"""
|
||||
total_word_list = []
|
||||
for _ in text_list: # 将嵌套的列表([[xx,xx],[xx,xx]...])拉平 ([xx,xx,xx...])
|
||||
total_word_list += _.split("#")
|
||||
# total_word_list += _.split(" ")
|
||||
counter = Counter(total_word_list) # 统计计数
|
||||
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 构造成可接受的格式:[(单词,num), ...]
|
||||
ordered_dict = OrderedDict(sorted_by_freq_tuples)
|
||||
# 开始构造 vocab
|
||||
special_token = ["<UNK>", "<SEP>"] # 特殊字符
|
||||
text_vocab = vocab(ordered_dict, specials=special_token) # 单词转token,specials里是特殊字符,可以为空
|
||||
text_vocab.set_default_index(0)
|
||||
vocab_transform = VocabTransform(text_vocab)
|
||||
return text_vocab, vocab_transform
|
||||
|
||||
def generate_cbow(self):
|
||||
"""生成CBOW的训练数据"""
|
||||
cbow_data = []
|
||||
for sentence in self.text_list:
|
||||
sentence_id_list = np.array(self.vocab_transform(sentence.split('#')))
|
||||
for center_index in range(
|
||||
self.side_window, len(sentence_id_list) - self.side_window): # 防止前面或后面取不到足够的值,这是取index的上下界
|
||||
pos_index = list(range(center_index - self.side_window, center_index + self.side_window + 1))
|
||||
del pos_index[self.side_window]
|
||||
cbow_data.append([sentence_id_list[center_index], sentence_id_list[pos_index]])
|
||||
return cbow_data
|
||||
|
||||
def get_vocab_transform(self):
|
||||
return self.vocab_transform
|
||||
|
||||
def get_vocab_size(self):
|
||||
return len(self.text_vocab)
|
||||
|
||||
|
||||
class Word2VecModel(nn.Module):
|
||||
def __init__(self, vocab_size, batch_size, word_embedding_size=32, hidden=64):
|
||||
"""
|
||||
Word2vec模型CBOW实现
|
||||
:param vocab_size: 单词个数
|
||||
:param word_embedding_size: 每个词的词向量维度
|
||||
:param hidden: 隐层维度
|
||||
"""
|
||||
super(Word2VecModel, self).__init__()
|
||||
self.vocab_size = vocab_size
|
||||
self.word_embedding_size = word_embedding_size
|
||||
self.hidden = hidden
|
||||
self.batch_size = batch_size
|
||||
self.word_embedding = nn.Embedding(self.vocab_size, self.word_embedding_size) # token对应的embedding
|
||||
# 建模
|
||||
self.linear_in = nn.Linear(self.word_embedding_size, self.hidden)
|
||||
self.linear_out = nn.Linear(self.hidden, self.vocab_size)
|
||||
|
||||
def forward(self, input_labels):
|
||||
around_embedding = self.word_embedding(input_labels)
|
||||
avg_around_embedding = torch.mean(around_embedding, dim=1) # 1. 输入的词向量对应位置求平均
|
||||
in_emb = F.relu(self.linear_in(avg_around_embedding)) # 2. 过第一个linear,使用relu激活函数
|
||||
out_emb = F.log_softmax(self.linear_out(in_emb)) # 3. 过第二个linear,得到维度是:[batch_size, 单词总数]
|
||||
return out_emb
|
||||
|
||||
def get_embedding(self, token_list: list):
|
||||
return self.word_embedding(torch.Tensor(token_list).long())
|
||||
|
||||
|
||||
|
||||
batch_size = 1
|
||||
sentence_list = get_text()
|
||||
cbow_data_set = CbowDataSet(sentence_list) # 构造 DataSet
|
||||
data_loader = DataLoader(cbow_data_set, batch_size=batch_size, drop_last=True) # 将DataSet封装成DataLoader
|
||||
# 开始训练
|
||||
model = Word2VecModel(cbow_data_set.get_vocab_size(), batch_size)
|
||||
optimizer = torch.optim.Adam(model.parameters())
|
||||
criterion = nn.CrossEntropyLoss()
|
||||
for _epoch_i in range(10):
|
||||
loss_list = []
|
||||
for center_token, back_token in data_loader:
|
||||
# 开始训练
|
||||
optimizer.zero_grad()
|
||||
model_out = model(back_token)
|
||||
center_token= torch.Tensor(center_token).long()
|
||||
loss = criterion(model_out, center_token)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
loss_list.append(loss.item())
|
||||
|
||||
print("训练中:", _epoch_i, "Loss:", np.sum(loss_list))
|
||||
|
||||
#保存预训练好的word2vec模型
|
||||
torch.save(model.state_dict(), 'word2vec_model_final.pth')
|
||||
'''
|
||||
# 最后测试一下
|
||||
# 得到: nlp can translate text from one language to another 的词向量
|
||||
sentence = "nlp can translate text from one language to another"
|
||||
vocab_transform = cbow_data_set.get_vocab_transform()
|
||||
sentence_ids = vocab_transform(sentence.split(' '))
|
||||
sentence_embedding = model.get_embedding(sentence_ids)
|
||||
print("这个是句向量的维度:", sentence_embedding.shape)
|
||||
|
||||
'''
|
||||
# W = model.word_embedding.weight.cpu().detach().numpy()
|
||||
# v = cbow_data_set.get_vocab_transform()
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-379 union select concat_ws(0x7c,user(),database(),version()),2,3/* 379#union#select#concat_ws#(#0x7c#(#)#,#database#(#)#,#version#(#)#,#2#/*#3
|
||||
37 and 1=0 37#and#1#0
|
||||
37' aNd 1=0 UNION SELECT 1,2,3,4,version() -- 37#aNd#1#UNION#SELECT#1#,#version#(#)#--
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,5 @@
|
|||
select user from list where id = dong or 1=1 0.5135439
|
||||
select * from data where uname='a' and 1=1 and 'a'='a' 0.5954708
|
||||
select * from user_name where id = 'admin' and password='1234';drop table user;-- 0.67799324
|
||||
select * from user where id='1111' and password='1234' and convert(char, no) -- 0.81503713
|
||||
select * from user where id='1111' union select * from member where id='admin'--' and password='1234' 0.5274527
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
select user from list where id = dong or 1=1
|
||||
select * from data where uname='a' and 1=1 and 'a'='a'
|
||||
select * from user_name where id = 'admin' and password='1234';drop table user;--
|
||||
select * from user where id='1111' and password='1234' and convert(char, no) --
|
||||
select * from user where id='1111' union select * from member where id='admin'--' and password='1234'
|
||||
Binary file not shown.
|
|
@ -0,0 +1,28 @@
|
|||
# SQLIDetection - Dataprepocess
|
||||
##### gsbadger工具中SQL注入检测功能的数据预处理部分的实现,基于LibInjection-java开源项目二次开发。
|
||||
|
||||
|
||||
|
||||
##### SQL语句预处理流程大致如下:
|
||||
|
||||
首先如果sqli经过unicode编码过,则进行unicode解码,再将原始sql语句进行tokenize分词。tokenize分词过程大概分为大小写统一、泛化处理、分词(按空格、操作符和括号等特殊符号拆分)这几个步骤。
|
||||
|
||||
|
||||
|
||||
以下为是使用范例:
|
||||
|
||||
```Java
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
/* test a sqli string */
|
||||
Libinjection a = new Libinjection();
|
||||
a.libinjection_sqli("admin' OR 1=1--");
|
||||
System.out.println(a.getOutput());
|
||||
|
||||
/* test a file and output its tokenizing results to another file, with options to urldecode and time (in milliseconds) */
|
||||
Test t = new Test();
|
||||
t.testfile("data/sqli.txt", "data/sqli-tokenize.txt", true, false);
|
||||
}
|
||||
}
|
||||
```
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,9 @@
|
|||
(select 99999999 from pg_sleep(15))
|
||||
9999'||(select 99999999 from pg_sleep(15))||'9999
|
||||
9999"||(select 99999999 from pg_sleep(15))||"9999
|
||||
ascii(substring(version() from 1 for 1))
|
||||
length((select length(version())))
|
||||
ascii(substring(length(version()),1,1))
|
||||
(select id from (select id,@v:=@v+1 as pos from articles y join (select @v:=0) k limit 255) x where pos=1)
|
||||
(select id from (select id,@v:=@v+1 as pos from articles y join (select @v:=0) k limit 255) x where pos=ascii(substring(version() from 1 for 1)))
|
||||
vulnerable_site' and 1=5 or title=() #'
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
(select 99999999 from pg_sleep(15)) select#99999999#from#pg_sleep#(#15#)
|
||||
9999'||(select 99999999 from pg_sleep(15))||'9999 9999#||#(#select#99999999#from#pg_sleep#(#15#)#||#9999
|
||||
9999"||(select 99999999 from pg_sleep(15))||"9999 9999#||#(#select#99999999#from#pg_sleep#(#15#)#||#9999
|
||||
ascii(substring(version() from 1 for 1)) ascii#(#substring#(#version#(#)#from#1#for#1#)
|
||||
length((select length(version()))) length#(#select#length#(#version#(#)
|
||||
ascii(substring(length(version()),1,1)) ascii#(#substring#(#length#(#version#(#)#,#1#)
|
||||
(select id from (select id,@v:=@v 1 as pos from articles y join (select @v:=0) k limit 255) x where pos=1) select#id#from#(#select#id#:=#v#1#as#pos#from#articles#y#join#(#select#v#)#k#limit#255#)#x#where#pos#)
|
||||
(select id from (select id,@v:=@v 1 as pos from articles y join (select @v:=0) k limit 255) x where pos=ascii(substring(version() from 1 for 1))) select#id#from#(#select#id#:=#v#1#as#pos#from#articles#y#join#(#select#v#)#k#limit#255#)#x#where#pos#=#ascii#(#substring#(#version#(
|
||||
vulnerable_site' and 1=5 or title=() #' vulnerable_site#and#title#=#(#)##'
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.csg</groupId>
|
||||
<artifactId>datapreprocess</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<build>
|
||||
<!-- 最终打包的名字 -->
|
||||
<finalName>datapreprocess</finalName>
|
||||
|
||||
<!-- 手动指定文件夹为resources,加载java下所有的xml文件-->
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<includes>
|
||||
<include>**/*.*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/*.*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- java编译插件 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<!-- 生成的jar中,包含pom.xml和pom.properties这两个文件 -->
|
||||
<addMavenDescriptor>true</addMavenDescriptor>
|
||||
<!-- 生成MANIFEST.MF的设置 -->
|
||||
<manifest>
|
||||
<!--这个属性特别关键,如果没有这个属性,有时候我们引用的包maven库
|
||||
下面可能会有多个包,并且只有一个是正确的,其余的可能是带时间戳的,
|
||||
此时会在classpath下面把那个带时间戳的给添加上去,然后我们
|
||||
在依赖打包的时候,打的是正确的,所以两头会对不上,报错。-->
|
||||
<useUniqueVersions>false</useUniqueVersions>
|
||||
<!-- 为依赖包添加路径, 这些路径会写在MANIFEST文件的Class-Path下 -->
|
||||
<addClasspath>true</addClasspath>
|
||||
<!-- 这个jar所依赖的jar包添加classPath的时候的前缀,如果这个
|
||||
jar本身和依赖包在同一级目录,则不需要添加-->
|
||||
<classpathPrefix>lib/</classpathPrefix>
|
||||
<!-- jar启动入口类-->
|
||||
<mainClass>Main</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<!-- 在Class-Path下添加配置文件的路径 -->
|
||||
<Class-Path>../config/</Class-Path>
|
||||
<!-- 假如这个项目可能要引入一些外部资源,但是你打包的时候并不想把
|
||||
这些资源文件打进包里面,这个时候你必须在这边额外指定一些这些资源
|
||||
文件的路径,这个位置指定的话,要根据你预期的这些位置去设置,我这边
|
||||
所有jar都在lib下,资源文件都在config下,lib和config是同级的 -->
|
||||
<!-- 同时还需要注意另外一个问题,假如你的pom文件里面配置了
|
||||
<scope>system</scope>,就是你依赖是你本地的资源,这个时候使用
|
||||
这个插件,classPath里面是不会添加,所以你得手动把这个依赖添加进
|
||||
这个地方,用空格隔开就行-->
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<!-- jar包的位置-->
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<includes>
|
||||
<!-- 打jar包时,只打包class文件 -->
|
||||
<!-- 有时候可能需要一些其他文件,这边可以配置,包括剔除的文件等等-->
|
||||
<include>**/*.*</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import java.io.BufferedInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Keyword {
|
||||
HashMap<String, Character> keywordMap = new HashMap<String, Character>();
|
||||
|
||||
Keyword() {
|
||||
String word;
|
||||
char type;
|
||||
Pattern wordpattern, typepattern;
|
||||
Matcher matchedword, matchedtype;
|
||||
|
||||
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Keywords.txt");
|
||||
Scanner in = new Scanner(new BufferedInputStream(inputStream));
|
||||
String line;
|
||||
|
||||
while (in.hasNextLine()) {
|
||||
line = in.nextLine();
|
||||
wordpattern = Pattern.compile("\\{\"(.*)\"");
|
||||
typepattern = Pattern.compile("\'(.*)\'");
|
||||
matchedword = wordpattern.matcher(line);
|
||||
matchedtype = typepattern.matcher(line);
|
||||
|
||||
while (matchedword.find() && matchedtype.find()) {
|
||||
word = matchedword.group(1);
|
||||
type = matchedtype.group(1).charAt(0);
|
||||
keywordMap.put(word, type);
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
}
|
||||
|
||||
void printKeywordMap() {
|
||||
for (String keyword : keywordMap.keySet()) {
|
||||
String keytype = keywordMap.get(keyword).toString();
|
||||
System.out.println("word: " + keyword + " type: " + keytype);
|
||||
}
|
||||
System.out.println("table size: " + keywordMap.size());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,71 @@
|
|||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws UnsupportedEncodingException {
|
||||
/* test a string */
|
||||
Libinjection a = new Libinjection();
|
||||
|
||||
//personal change
|
||||
String sql = "9999'||(select 99999999 from pg_sleep(15))||'9999";
|
||||
|
||||
/*
|
||||
* urldecode
|
||||
*/
|
||||
try {
|
||||
sql = URLDecoder.decode(sql, "UTF-8");
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
boolean issqli = a.libinjection_sqli(sql);
|
||||
// System.out.println(a.getOutput());
|
||||
// System.out.println(issqli);
|
||||
|
||||
/* test a file and output its results, with options to urldecode and time */
|
||||
Test t = new Test();
|
||||
// t.testfile("data/sqli.txt", "data/sqli_tokenize.txt", true, false);
|
||||
if(args.length<2){
|
||||
throw new RuntimeException("lack of parameters");
|
||||
}
|
||||
String queries_file = args[0];
|
||||
String tokenize_file = args[1];
|
||||
t.testfile(queries_file, tokenize_file, true, false);
|
||||
|
||||
/* performance test */
|
||||
// Libinjection b = new Libinjection();
|
||||
// String[] test = {
|
||||
// "123 LIKE -1234.5678E+2;",
|
||||
// "APPLE 19.123 'FOO' \"BAR\"",
|
||||
// "/* BAR */ UNION ALL SELECT (2,3,4)",
|
||||
// "1 || COS(+0X04) --FOOBAR",
|
||||
// "dog apple @cat banana bar",
|
||||
// "dog apple cat \"banana \'bar",
|
||||
// "102 TABLE CLOTH",
|
||||
// "(1001-'1') union select 1,2,3,4 from credit_cards"
|
||||
// };
|
||||
//
|
||||
// /* print output for above inputs */
|
||||
// for (int i = 0 ; i < test.length; i++) {
|
||||
// b.libinjection_sqli(test[i]);
|
||||
// System.out.println(b.getOutput());
|
||||
// }
|
||||
//
|
||||
// /* let jvm optimize for 100000 iterations */
|
||||
// for (int c = 0; c < 100000; c++) {
|
||||
// b.libinjection_sqli(test[c % 8]);
|
||||
// }
|
||||
//
|
||||
// /* time */
|
||||
// double start = System.currentTimeMillis();
|
||||
// for (int c = 0; c < 1000000; c++) {
|
||||
// b.libinjection_sqli(test[c % 8]);
|
||||
// }
|
||||
// double end = System.currentTimeMillis();
|
||||
//
|
||||
// double total = (end - start) / 1000.0;
|
||||
// double tps = 1000000.0 / total;
|
||||
// System.out.println("iterations: " + 10000000 + "\ntotal time: " + total +" sec");
|
||||
// System.out.println((int) tps + " / sec");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
class State {
|
||||
|
||||
String s; /* input string */
|
||||
int slen; /* length of input */
|
||||
int fplen; /* length of fingerprint */
|
||||
int flags; /* flag to indicate which mode we're running in: example.) flag_quote_none AND flag_sql_ansi */
|
||||
int pos; /* index in string during tokenization */
|
||||
int current; /* current position in tokenvec*/
|
||||
int stats_comment_ddw;
|
||||
int stats_comment_ddx;
|
||||
int stats_comment_c; /* c-style comments found /x .. x/ */
|
||||
int stats_comment_hash; /* '#' operators or MySQL EOL comments found */
|
||||
int stats_folds;
|
||||
int stats_tokens;
|
||||
//personal change
|
||||
Token[] tokenvec = new Token[35];//original 8
|
||||
String fingerprint;
|
||||
|
||||
|
||||
State(String s, int len, int flags) {
|
||||
if (flags == 0) {
|
||||
flags = Libinjection.FLAG_QUOTE_NONE | Libinjection.FLAG_SQL_ANSI;
|
||||
}
|
||||
this.s = s;
|
||||
this.slen = len;
|
||||
this.flags = flags;
|
||||
this.current = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void printState() {
|
||||
System.out.printf("Input: %s\n", s);
|
||||
System.out.printf("Length: %d\n", slen);
|
||||
System.out.printf("Flags: %d\n", flags);
|
||||
System.out.printf("Position in input string: %d\n", pos);
|
||||
System.out.printf("Current token index: %d\n", current);
|
||||
System.out.println("Tokenvec:");
|
||||
if (tokenvec != null) {
|
||||
int i = 0;
|
||||
while (tokenvec[i] != null) {
|
||||
System.out.printf("[%s ", tokenvec[i].type);
|
||||
System.out.printf("%s] \n", tokenvec[i].val);
|
||||
i++;
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Scanner;
|
||||
|
||||
|
||||
public class Test {
|
||||
public long testfile(String inputfile, String outputfile, boolean decode, boolean time) {
|
||||
long startTime;
|
||||
long endTime;
|
||||
long sum = 0;
|
||||
int count = 0;
|
||||
|
||||
Scanner in = null;
|
||||
PrintWriter out = null;
|
||||
|
||||
try {
|
||||
in = new Scanner(new FileReader(inputfile));
|
||||
out = new PrintWriter(outputfile, "UTF-8");
|
||||
Libinjection libinjection = new Libinjection();
|
||||
|
||||
while (in.hasNextLine()) {
|
||||
String line = in.nextLine();
|
||||
|
||||
/*
|
||||
* urldecode
|
||||
*/
|
||||
if (decode) {
|
||||
try {
|
||||
line = URLDecoder.decode(line, "UTF-8");
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* test and add result to outputfile
|
||||
*/
|
||||
if (time) {
|
||||
startTime = System.currentTimeMillis();
|
||||
libinjection.libinjection_sqli(line);
|
||||
endTime = System.currentTimeMillis();
|
||||
sum += (endTime - startTime);
|
||||
count++;
|
||||
} else {
|
||||
libinjection.libinjection_sqli(line);
|
||||
}
|
||||
|
||||
out.println(libinjection.getOutput());
|
||||
}
|
||||
if (time) {
|
||||
System.out.println("Total: " + sum + " Average: " + sum/count);
|
||||
}
|
||||
return sum;
|
||||
|
||||
} catch (FileNotFoundException | UnsupportedEncodingException ex) {
|
||||
System.out.println("file not found or unsupported encoding");
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
in = null;
|
||||
} catch(Exception ex3) { ex3.printStackTrace(); }
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
out = null;
|
||||
} catch(Exception ex3) { ex3.printStackTrace(); }
|
||||
}
|
||||
|
||||
}
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
class Token {
|
||||
char type;
|
||||
char str_open;
|
||||
char str_close;
|
||||
String val;
|
||||
int count;
|
||||
// position and length of input in original string
|
||||
int pos;
|
||||
int len;
|
||||
|
||||
Token(int stype, int pos, int len, String val) {
|
||||
this.type = (char) stype;
|
||||
this.pos = pos;
|
||||
this.len = len;
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
void printToken() {
|
||||
System.out.printf("Token represents %s\n", val);
|
||||
System.out.printf("Type: %c\n", type);
|
||||
System.out.printf("Position: %d\n", pos);
|
||||
System.out.printf("Length: %d\n", len);
|
||||
System.out.printf("Number of '@': %d\n", count);
|
||||
System.out.printf("String open: %c\n", str_open);
|
||||
System.out.printf("String close: %c\n", str_close);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -0,0 +1,446 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on Fri Aug 19 08:39:20 2022
|
||||
|
||||
@author: LKJ
|
||||
"""
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
import torch.utils.model_zoo as model_zoo
|
||||
from torch.nn import functional as F
|
||||
from torchtext.vocab import vocab
|
||||
from collections import Counter, OrderedDict
|
||||
from torch.utils.data import Dataset, DataLoader
|
||||
from torchtext.transforms import VocabTransform # 注意:torchtext版本0.12+
|
||||
import torch.optim as optim
|
||||
import itertools
|
||||
import random
|
||||
import numpy as np
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
############################第1部分,CBOW模型,加载sql语句得到对应的句向量###############################
|
||||
def get_text():
|
||||
# sentence_list = [ # 假设这是全部的训练语料
|
||||
# "nlp drives computer programs that translate text from one language to another",
|
||||
# "nlp combines computational linguistics rule based modeling of human language with statistical",
|
||||
# "nlp model respond to text or voice data and respond with text",
|
||||
# ]
|
||||
p_sample = []
|
||||
f = open("data\\p_sample.txt",encoding='utf-8')
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
line = line.split('\t')[1]
|
||||
p_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
n_sample = []
|
||||
f = open("data\\n_sample.txt",encoding='utf-8')
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
line = line.split('\t')[1]
|
||||
n_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
sentence_list = p_sample + n_sample
|
||||
return sentence_list
|
||||
|
||||
|
||||
class CbowDataSet(Dataset):
|
||||
def __init__(self, text_list, side_window=3):
|
||||
"""
|
||||
构造Word2vec的CBOW采样Dataset
|
||||
:param text_list: 语料
|
||||
:param side_window: 单侧正例(构造背景词)采样数,总正例是:2 * side_window
|
||||
"""
|
||||
super(CbowDataSet, self).__init__()
|
||||
self.side_window = side_window
|
||||
text_vocab, vocab_transform = self.reform_vocab(text_list)
|
||||
self.text_list = text_list # 原始文本
|
||||
self.text_vocab = text_vocab # torchtext的vocab
|
||||
self.vocab_transform = vocab_transform # torchtext的vocab_transform
|
||||
self.cbow_data = self.generate_cbow()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.cbow_data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data_row = self.cbow_data[idx]
|
||||
return data_row[0], data_row[1]
|
||||
|
||||
def reform_vocab(self, text_list):
|
||||
"""根据语料构造torchtext的vocab"""
|
||||
total_word_list = []
|
||||
for _ in text_list: # 将嵌套的列表([[xx,xx],[xx,xx]...])拉平 ([xx,xx,xx...])
|
||||
total_word_list += _.split("#")
|
||||
counter = Counter(total_word_list) # 统计计数
|
||||
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 构造成可接受的格式:[(单词,num), ...]
|
||||
ordered_dict = OrderedDict(sorted_by_freq_tuples)
|
||||
# 开始构造 vocab
|
||||
special_token = ["<UNK>", "<SEP>"] # 特殊字符
|
||||
text_vocab = vocab(ordered_dict, specials=special_token) # 单词转token,specials里是特殊字符,可以为空
|
||||
text_vocab.set_default_index(0)
|
||||
vocab_transform = VocabTransform(text_vocab)
|
||||
return text_vocab, vocab_transform
|
||||
|
||||
def generate_cbow(self):
|
||||
"""生成CBOW的训练数据"""
|
||||
cbow_data = []
|
||||
for sentence in self.text_list:
|
||||
sentence_id_list = np.array(self.vocab_transform(sentence.split('#')))
|
||||
for center_index in range(
|
||||
self.side_window, len(sentence_id_list) - self.side_window): # 防止前面或后面取不到足够的值,这是取index的上下界
|
||||
pos_index = list(range(center_index - self.side_window, center_index + self.side_window + 1))
|
||||
del pos_index[self.side_window]
|
||||
cbow_data.append([sentence_id_list[center_index], sentence_id_list[pos_index]])
|
||||
return cbow_data
|
||||
|
||||
def get_vocab_transform(self):
|
||||
return self.vocab_transform
|
||||
|
||||
def get_vocab_size(self):
|
||||
return len(self.text_vocab)
|
||||
|
||||
|
||||
class Word2VecModel(nn.Module):
|
||||
def __init__(self, vocab_size, batch_size, word_embedding_size=32, hidden=64):
|
||||
"""
|
||||
Word2vec模型CBOW实现
|
||||
:param vocab_size: 单词个数
|
||||
:param word_embedding_size: 每个词的词向量维度
|
||||
:param hidden: 隐层维度
|
||||
"""
|
||||
super(Word2VecModel, self).__init__()
|
||||
self.vocab_size = vocab_size
|
||||
self.word_embedding_size = word_embedding_size
|
||||
self.hidden = hidden
|
||||
self.batch_size = batch_size
|
||||
self.word_embedding = nn.Embedding(self.vocab_size, self.word_embedding_size) # token对应的embedding
|
||||
# 建模
|
||||
self.linear_in = nn.Linear(self.word_embedding_size, self.hidden)
|
||||
self.linear_out = nn.Linear(self.hidden, self.vocab_size)
|
||||
|
||||
def forward(self, input_labels):
|
||||
around_embedding = self.word_embedding(input_labels)
|
||||
avg_around_embedding = torch.mean(around_embedding, dim=1) # 1. 输入的词向量对应位置求平均
|
||||
in_emb = F.relu(self.linear_in(avg_around_embedding)) # 2. 过第一个linear,使用relu激活函数
|
||||
out_emb = F.log_softmax(self.linear_out(in_emb)) # 3. 过第二个linear,得到维度是:[batch_size, 单词总数]
|
||||
return out_emb
|
||||
|
||||
def get_embedding(self, token_list: list):
|
||||
return self.word_embedding(torch.Tensor(token_list).long())
|
||||
|
||||
|
||||
#######################################第2部分,resnet模型,进行句向量的分类####################################
|
||||
def conv3x3(in_planes, out_planes, stride=1):
|
||||
"3x3 convolution with padding"
|
||||
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
||||
padding=1, bias=False)
|
||||
|
||||
class ChannelAttention(nn.Module):
|
||||
def __init__(self, in_planes, ratio=16):
|
||||
super(ChannelAttention, self).__init__()
|
||||
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
||||
self.max_pool = nn.AdaptiveMaxPool2d(1)
|
||||
|
||||
self.fc1 = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
|
||||
self.relu1 = nn.ReLU()
|
||||
self.fc2 = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)
|
||||
|
||||
self.sigmoid = nn.Sigmoid()
|
||||
|
||||
def forward(self, x):
|
||||
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
|
||||
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
|
||||
out = avg_out + max_out
|
||||
return self.sigmoid(out)
|
||||
|
||||
class SpatialAttention(nn.Module):
|
||||
def __init__(self, kernel_size=7):
|
||||
super(SpatialAttention, self).__init__()
|
||||
|
||||
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
|
||||
padding = 3 if kernel_size == 7 else 1
|
||||
|
||||
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
|
||||
self.sigmoid = nn.Sigmoid()
|
||||
|
||||
def forward(self, x):
|
||||
avg_out = torch.mean(x, dim=1, keepdim=True)
|
||||
max_out, _ = torch.max(x, dim=1, keepdim=True)
|
||||
x = torch.cat([avg_out, max_out], dim=1)
|
||||
x = self.conv1(x)
|
||||
return self.sigmoid(x)
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
expansion = 1
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, downsample=None):
|
||||
super(BasicBlock, self).__init__()
|
||||
self.conv1 = conv3x3(inplanes, planes, stride)
|
||||
self.bn1 = nn.BatchNorm2d(planes)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.conv2 = conv3x3(planes, planes)
|
||||
self.bn2 = nn.BatchNorm2d(planes)
|
||||
|
||||
self.ca = ChannelAttention(planes)
|
||||
self.sa = SpatialAttention()
|
||||
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
residual = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
|
||||
out = self.ca(out) * out
|
||||
out = self.sa(out) * out
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(x)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
expansion = 4
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, downsample=None):
|
||||
super(Bottleneck, self).__init__()
|
||||
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(planes)
|
||||
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
|
||||
padding=1, bias=False)
|
||||
self.bn2 = nn.BatchNorm2d(planes)
|
||||
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
|
||||
self.bn3 = nn.BatchNorm2d(planes * 4)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
self.ca = ChannelAttention(planes * 4)
|
||||
self.sa = SpatialAttention()
|
||||
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
residual = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.bn3(out)
|
||||
|
||||
out = self.ca(out) * out
|
||||
out = self.sa(out) * out
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(x)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNet(nn.Module):
|
||||
|
||||
def __init__(self, block, layers, num_classes=2):
|
||||
self.inplanes = 32
|
||||
super(ResNet, self).__init__()
|
||||
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=3,
|
||||
bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(64)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
self.layer1 = self._make_layer(block, 16, layers[0])
|
||||
self.layer2 = self._make_layer(block, 16, layers[1], stride=2)
|
||||
self.layer3 = self._make_layer(block, 16, layers[2], stride=2)
|
||||
self.layer4 = self._make_layer(block, 16, layers[3], stride=2)
|
||||
self.avgpool = nn.AvgPool2d(7, stride=1)
|
||||
self.fc = nn.Linear(400 * block.expansion, num_classes)
|
||||
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
||||
m.weight.data.normal_(0, math.sqrt(2. / n))
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
m.weight.data.fill_(1)
|
||||
m.bias.data.zero_()
|
||||
|
||||
def _make_layer(self, block, planes, blocks, stride=1):
|
||||
downsample = None
|
||||
if stride != 1 or self.inplanes != planes * block.expansion:
|
||||
downsample = nn.Sequential(
|
||||
nn.Conv2d(self.inplanes, planes * block.expansion,
|
||||
kernel_size=1, stride=stride, bias=False),
|
||||
nn.BatchNorm2d(planes * block.expansion),
|
||||
)
|
||||
|
||||
layers = []
|
||||
layers.append(block(self.inplanes, planes, stride, downsample))
|
||||
self.inplanes = planes * block.expansion
|
||||
for i in range(1, blocks):
|
||||
layers.append(block(self.inplanes, planes))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
# x = self.bn1(x)
|
||||
x = self.relu(x)
|
||||
# x = self.maxpool(x)
|
||||
|
||||
x = self.layer1(x)
|
||||
x = self.layer2(x)
|
||||
x = self.layer3(x)
|
||||
x = self.layer4(x)
|
||||
|
||||
# x = self.avgpool(x)
|
||||
x = x.view(x.size(0), -1)
|
||||
x = self.fc(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def resnet18_cbam(pretrained=False, **kwargs):
|
||||
"""Constructs a ResNet-18 model.
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
"""
|
||||
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
|
||||
|
||||
return model
|
||||
|
||||
########################################第三部分,执行分类####################################################
|
||||
batch_size = 1
|
||||
sentence_list = get_text()
|
||||
cbow_data_set = CbowDataSet(sentence_list) # 构造 DataSet
|
||||
word2vec_model = Word2VecModel(cbow_data_set.get_vocab_size(), batch_size)
|
||||
word2vec_model.load_state_dict(torch.load('word2vec_model_final.pth')) #加载预训练好的词向量映射模型
|
||||
|
||||
#读取正负样本
|
||||
p_sample_label = []
|
||||
f = open("data\\p_sample.txt",encoding='utf-8') #加载正样本
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
line = line.split('\t')[1]
|
||||
label_c = [1]
|
||||
sample_label = []
|
||||
sample_label.append(line)
|
||||
sample_label.append(label_c)
|
||||
p_sample_label.append(sample_label)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
n_sample_label = []
|
||||
f = open("data\\n_sample.txt",encoding='utf-8') #加载负样本
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
line = line.split('\t')[1]
|
||||
label_c = [0]
|
||||
sample_label = []
|
||||
sample_label.append(line)
|
||||
sample_label.append(label_c)
|
||||
n_sample_label.append(sample_label)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
|
||||
t_sample_label = p_sample_label + n_sample_label
|
||||
|
||||
|
||||
# 定义是否使用GPU
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
mymodel = resnet18_cbam().to(device)
|
||||
|
||||
total = 0
|
||||
correct = 0
|
||||
EPOCH = 10
|
||||
criterion = nn.CrossEntropyLoss() #损失函数为交叉熵,多用于多分类问题
|
||||
#开始训练
|
||||
for epoch in range(EPOCH):
|
||||
|
||||
fc_loss_all = 0.0
|
||||
optimizer = optim.Adam(itertools.chain(mymodel.parameters(),), lr=0.001, weight_decay=2e-4)
|
||||
|
||||
|
||||
sum_loss = 0.0
|
||||
correct = 0.0
|
||||
total = 0.0
|
||||
acc = 0
|
||||
|
||||
random.shuffle(t_sample_label)
|
||||
|
||||
for i in range(len(t_sample_label)):
|
||||
sentence = t_sample_label[i][0]
|
||||
label = torch.tensor(t_sample_label[i][1]).reshape(1)
|
||||
vocab_transform = cbow_data_set.get_vocab_transform()
|
||||
sentence_ids = vocab_transform(sentence.split('#'))
|
||||
sentence_embedding = word2vec_model.get_embedding(sentence_ids)
|
||||
|
||||
w,h = sentence_embedding.shape
|
||||
if w < 32:
|
||||
pad = nn.ZeroPad2d(padding=(0, 0, 0, 32-w))
|
||||
sentence_embedding = pad(sentence_embedding)
|
||||
sentence_embedding = sentence_embedding.reshape(1,1,32,32)
|
||||
if w > 32:
|
||||
sentence_embedding = sentence_embedding.reshape(1,1,w,h)
|
||||
sentence_embedding = F.interpolate(sentence_embedding, size=[32, 32])
|
||||
|
||||
|
||||
out = mymodel(sentence_embedding)
|
||||
optimizer.zero_grad()
|
||||
|
||||
loss = criterion(out, label)
|
||||
loss.backward() #反向传播
|
||||
optimizer.step()
|
||||
|
||||
print('\nEpoch: %d finish' % (epoch + 1))
|
||||
|
||||
torch.save(mymodel.state_dict(), 'resnet18.pth') #保持预训练好的resnet模型
|
||||
|
||||
|
||||
|
||||
|
||||
# sentence = "se"
|
||||
# vocab_transform = cbow_data_set.get_vocab_transform()
|
||||
# sentence_ids = vocab_transform(sentence.split('#'))
|
||||
# sentence_embedding = word2vec_model.get_embedding(sentence_ids)
|
||||
|
||||
# a = sentence_embedding
|
||||
# pad = nn.ZeroPad2d(padding=(0, 0, 0, 23))
|
||||
# a = pad(a)
|
||||
# a = a.reshape(1,1,32,32)
|
||||
# out = mymodel(a)
|
||||
|
||||
# _, predicted = torch.max(out, 1)
|
||||
# labels = 0
|
||||
# total += 1
|
||||
# correct += (predicted.numpy() == labels).sum()
|
||||
# # a = torch.rand(1,1,64,32)
|
||||
# # a = F.interpolate(a, size=[32, 32])
|
||||
|
||||
# print("准确率为:")
|
||||
# print(correct / total)
|
||||
|
|
@ -0,0 +1,446 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on Fri Aug 19 08:39:20 2022
|
||||
|
||||
@author: LKJ
|
||||
"""
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
import torch.utils.model_zoo as model_zoo
|
||||
from torch.nn import functional as F
|
||||
from torchtext.vocab import vocab
|
||||
from collections import Counter, OrderedDict
|
||||
from torch.utils.data import Dataset, DataLoader
|
||||
from torchtext.transforms import VocabTransform # 注意:torchtext版本0.12+
|
||||
import torch.optim as optim
|
||||
import itertools
|
||||
import random
|
||||
import numpy as np
|
||||
import warnings
|
||||
import sys
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
############################第1部分,CBOW模型,加载sql语句得到对应的句向量###############################
|
||||
def get_text():
|
||||
# sentence_list = [ # 假设这是全部的训练语料
|
||||
# "nlp drives computer programs that translate text from one language to another",
|
||||
# "nlp combines computational linguistics rule based modeling of human language with statistical",
|
||||
# "nlp model respond to text or voice data and respond with text",
|
||||
# ]
|
||||
p_sample = []
|
||||
f = open("data/p_sample.txt",encoding='utf-8')
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
split = line.split('\t')
|
||||
if len(split)==2 :
|
||||
line = split[1]
|
||||
p_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
n_sample = []
|
||||
f = open("data/n_sample.txt",encoding='utf-8')
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
split = line.split('\t')
|
||||
if len(split)==2 :
|
||||
line = split[1]
|
||||
n_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
sentence_list = p_sample + n_sample
|
||||
return sentence_list
|
||||
|
||||
|
||||
class CbowDataSet(Dataset):
|
||||
def __init__(self, text_list, side_window=3):
|
||||
"""
|
||||
构造Word2vec的CBOW采样Dataset
|
||||
:param text_list: 语料
|
||||
:param side_window: 单侧正例(构造背景词)采样数,总正例是:2 * side_window
|
||||
"""
|
||||
super(CbowDataSet, self).__init__()
|
||||
self.side_window = side_window
|
||||
text_vocab, vocab_transform = self.reform_vocab(text_list)
|
||||
self.text_list = text_list # 原始文本
|
||||
self.text_vocab = text_vocab # torchtext的vocab
|
||||
self.vocab_transform = vocab_transform # torchtext的vocab_transform
|
||||
self.cbow_data = self.generate_cbow()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.cbow_data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data_row = self.cbow_data[idx]
|
||||
return data_row[0], data_row[1]
|
||||
|
||||
def reform_vocab(self, text_list):
|
||||
"""根据语料构造torchtext的vocab"""
|
||||
total_word_list = []
|
||||
for _ in text_list: # 将嵌套的列表([[xx,xx],[xx,xx]...])拉平 ([xx,xx,xx...])
|
||||
total_word_list += _.split("#")
|
||||
counter = Counter(total_word_list) # 统计计数
|
||||
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 构造成可接受的格式:[(单词,num), ...]
|
||||
ordered_dict = OrderedDict(sorted_by_freq_tuples)
|
||||
# 开始构造 vocab
|
||||
special_token = ["<UNK>", "<SEP>"] # 特殊字符
|
||||
text_vocab = vocab(ordered_dict, specials=special_token) # 单词转token,specials里是特殊字符,可以为空
|
||||
text_vocab.set_default_index(0)
|
||||
vocab_transform = VocabTransform(text_vocab)
|
||||
return text_vocab, vocab_transform
|
||||
|
||||
def generate_cbow(self):
|
||||
"""生成CBOW的训练数据"""
|
||||
cbow_data = []
|
||||
for sentence in self.text_list:
|
||||
sentence_id_list = np.array(self.vocab_transform(sentence.split('#')))
|
||||
for center_index in range(
|
||||
self.side_window, len(sentence_id_list) - self.side_window): # 防止前面或后面取不到足够的值,这是取index的上下界
|
||||
pos_index = list(range(center_index - self.side_window, center_index + self.side_window + 1))
|
||||
del pos_index[self.side_window]
|
||||
cbow_data.append([sentence_id_list[center_index], sentence_id_list[pos_index]])
|
||||
return cbow_data
|
||||
|
||||
def get_vocab_transform(self):
|
||||
return self.vocab_transform
|
||||
|
||||
def get_vocab_size(self):
|
||||
return len(self.text_vocab)
|
||||
|
||||
|
||||
class Word2VecModel(nn.Module):
|
||||
def __init__(self, vocab_size, batch_size, word_embedding_size=32, hidden=64):
|
||||
"""
|
||||
Word2vec模型CBOW实现
|
||||
:param vocab_size: 单词个数
|
||||
:param word_embedding_size: 每个词的词向量维度
|
||||
:param hidden: 隐层维度
|
||||
"""
|
||||
super(Word2VecModel, self).__init__()
|
||||
self.vocab_size = vocab_size
|
||||
self.word_embedding_size = word_embedding_size
|
||||
self.hidden = hidden
|
||||
self.batch_size = batch_size
|
||||
self.word_embedding = nn.Embedding(self.vocab_size, self.word_embedding_size) # token对应的embedding
|
||||
# 建模
|
||||
self.linear_in = nn.Linear(self.word_embedding_size, self.hidden)
|
||||
self.linear_out = nn.Linear(self.hidden, self.vocab_size)
|
||||
|
||||
def forward(self, input_labels):
|
||||
around_embedding = self.word_embedding(input_labels)
|
||||
avg_around_embedding = torch.mean(around_embedding, dim=1) # 1. 输入的词向量对应位置求平均
|
||||
in_emb = F.relu(self.linear_in(avg_around_embedding)) # 2. 过第一个linear,使用relu激活函数
|
||||
out_emb = F.log_softmax(self.linear_out(in_emb)) # 3. 过第二个linear,得到维度是:[batch_size, 单词总数]
|
||||
return out_emb
|
||||
|
||||
def get_embedding(self, token_list: list):
|
||||
return self.word_embedding(torch.Tensor(token_list).long())
|
||||
|
||||
|
||||
#######################################第2部分,resnet模型,进行句向量的分类####################################
|
||||
def conv3x3(in_planes, out_planes, stride=1):
|
||||
"3x3 convolution with padding"
|
||||
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
||||
padding=1, bias=False)
|
||||
|
||||
class ChannelAttention(nn.Module):
|
||||
def __init__(self, in_planes, ratio=16):
|
||||
super(ChannelAttention, self).__init__()
|
||||
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
||||
self.max_pool = nn.AdaptiveMaxPool2d(1)
|
||||
|
||||
self.fc1 = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
|
||||
self.relu1 = nn.ReLU()
|
||||
self.fc2 = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)
|
||||
|
||||
self.sigmoid = nn.Sigmoid()
|
||||
|
||||
def forward(self, x):
|
||||
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
|
||||
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
|
||||
out = avg_out + max_out
|
||||
return self.sigmoid(out)
|
||||
|
||||
class SpatialAttention(nn.Module):
|
||||
def __init__(self, kernel_size=7):
|
||||
super(SpatialAttention, self).__init__()
|
||||
|
||||
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
|
||||
padding = 3 if kernel_size == 7 else 1
|
||||
|
||||
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
|
||||
self.sigmoid = nn.Sigmoid()
|
||||
|
||||
def forward(self, x):
|
||||
avg_out = torch.mean(x, dim=1, keepdim=True)
|
||||
max_out, _ = torch.max(x, dim=1, keepdim=True)
|
||||
x = torch.cat([avg_out, max_out], dim=1)
|
||||
x = self.conv1(x)
|
||||
return self.sigmoid(x)
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
expansion = 1
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, downsample=None):
|
||||
super(BasicBlock, self).__init__()
|
||||
self.conv1 = conv3x3(inplanes, planes, stride)
|
||||
self.bn1 = nn.BatchNorm2d(planes)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.conv2 = conv3x3(planes, planes)
|
||||
self.bn2 = nn.BatchNorm2d(planes)
|
||||
|
||||
self.ca = ChannelAttention(planes)
|
||||
self.sa = SpatialAttention()
|
||||
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
residual = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
|
||||
out = self.ca(out) * out
|
||||
out = self.sa(out) * out
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(x)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
expansion = 4
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, downsample=None):
|
||||
super(Bottleneck, self).__init__()
|
||||
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(planes)
|
||||
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
|
||||
padding=1, bias=False)
|
||||
self.bn2 = nn.BatchNorm2d(planes)
|
||||
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
|
||||
self.bn3 = nn.BatchNorm2d(planes * 4)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
self.ca = ChannelAttention(planes * 4)
|
||||
self.sa = SpatialAttention()
|
||||
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
residual = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.bn3(out)
|
||||
|
||||
out = self.ca(out) * out
|
||||
out = self.sa(out) * out
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(x)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNet(nn.Module):
|
||||
|
||||
def __init__(self, block, layers, num_classes=2):
|
||||
self.inplanes = 32
|
||||
super(ResNet, self).__init__()
|
||||
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=3,
|
||||
bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(64)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
self.layer1 = self._make_layer(block, 16, layers[0])
|
||||
self.layer2 = self._make_layer(block, 16, layers[1], stride=2)
|
||||
self.layer3 = self._make_layer(block, 16, layers[2], stride=2)
|
||||
self.layer4 = self._make_layer(block, 16, layers[3], stride=2)
|
||||
self.avgpool = nn.AvgPool2d(7, stride=1)
|
||||
self.fc = nn.Linear(400 * block.expansion, num_classes)
|
||||
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
||||
m.weight.data.normal_(0, math.sqrt(2. / n))
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
m.weight.data.fill_(1)
|
||||
m.bias.data.zero_()
|
||||
|
||||
def _make_layer(self, block, planes, blocks, stride=1):
|
||||
downsample = None
|
||||
if stride != 1 or self.inplanes != planes * block.expansion:
|
||||
downsample = nn.Sequential(
|
||||
nn.Conv2d(self.inplanes, planes * block.expansion,
|
||||
kernel_size=1, stride=stride, bias=False),
|
||||
nn.BatchNorm2d(planes * block.expansion),
|
||||
)
|
||||
|
||||
layers = []
|
||||
layers.append(block(self.inplanes, planes, stride, downsample))
|
||||
self.inplanes = planes * block.expansion
|
||||
for i in range(1, blocks):
|
||||
layers.append(block(self.inplanes, planes))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
# x = self.bn1(x)
|
||||
x = self.relu(x)
|
||||
# x = self.maxpool(x)
|
||||
|
||||
x = self.layer1(x)
|
||||
x = self.layer2(x)
|
||||
x = self.layer3(x)
|
||||
x = self.layer4(x)
|
||||
|
||||
# x = self.avgpool(x)
|
||||
x = x.view(x.size(0), -1)
|
||||
x = self.fc(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def resnet18_cbam(pretrained=False, **kwargs):
|
||||
"""Constructs a ResNet-18 model.
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
"""
|
||||
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
|
||||
|
||||
return model
|
||||
|
||||
########################################第三部分,执行分类####################################################
|
||||
batch_size = 1
|
||||
sentence_list = get_text()
|
||||
cbow_data_set = CbowDataSet(sentence_list) # 构造 DataSet
|
||||
word2vec_model = Word2VecModel(cbow_data_set.get_vocab_size(), batch_size)
|
||||
word2vec_model.load_state_dict(torch.load('word2vec_model_final.pth')) #加载预训练好的词向量映射模型
|
||||
|
||||
load_filename = sys.argv[1]
|
||||
save_filename = sys.argv[2]
|
||||
|
||||
|
||||
|
||||
#读取样本
|
||||
test_sample = []
|
||||
# f = open("data\\test_sample.txt",encoding='utf-8') #加载正样本
|
||||
f = open(load_filename,encoding='utf-8') #加载正样本
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line:
|
||||
# line = line.split('\t')[1]
|
||||
test_sample.append(line)
|
||||
else:
|
||||
break
|
||||
f.close()
|
||||
|
||||
|
||||
|
||||
|
||||
# 定义是否使用GPU
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
mymodel = resnet18_cbam().to(device)
|
||||
|
||||
total = 0
|
||||
correct = 0
|
||||
|
||||
|
||||
|
||||
#开始训练
|
||||
for epoch in range(1):
|
||||
|
||||
fc_loss_all = 0.0
|
||||
|
||||
|
||||
|
||||
sum_loss = 0.0
|
||||
correct = 0.0
|
||||
total = 0.0
|
||||
acc = 0
|
||||
|
||||
l=[]
|
||||
for i in range(len(test_sample)):
|
||||
sentence = test_sample[i].split('\t')[1]
|
||||
sentence = sentence.split('\n')[0]
|
||||
vocab_transform = cbow_data_set.get_vocab_transform()
|
||||
sentence_ids = vocab_transform(sentence.split('#'))
|
||||
sentence_embedding = word2vec_model.get_embedding(sentence_ids)
|
||||
|
||||
w,h = sentence_embedding.shape
|
||||
if w < 32:
|
||||
pad = nn.ZeroPad2d(padding=(0, 0, 0, 32-w))
|
||||
sentence_embedding = pad(sentence_embedding)
|
||||
sentence_embedding = sentence_embedding.reshape(1,1,32,32)
|
||||
if w > 32:
|
||||
sentence_embedding = sentence_embedding.reshape(1,1,w,h)
|
||||
sentence_embedding = F.interpolate(sentence_embedding, size=[32, 32])
|
||||
|
||||
|
||||
out = mymodel(sentence_embedding)
|
||||
_, predicted = torch.max(out.data, 1)
|
||||
out = F.softmax(out, dim=1)
|
||||
p_value = out[0][predicted.detach().numpy()[0]].detach().numpy()
|
||||
|
||||
|
||||
l.append(test_sample[i].split('\t')[0] + '\t' + str(p_value))
|
||||
|
||||
# f=open("data\\sqli_result.txt","w") #将预测结果存放到txt文件中
|
||||
f=open(save_filename,"w") #将预测结果存放到txt文件中
|
||||
|
||||
for line in l:
|
||||
f.write(line+'\n')
|
||||
f.close()
|
||||
|
||||
print('finish testing')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# sentence = "se"
|
||||
# vocab_transform = cbow_data_set.get_vocab_transform()
|
||||
# sentence_ids = vocab_transform(sentence.split('#'))
|
||||
# sentence_embedding = word2vec_model.get_embedding(sentence_ids)
|
||||
|
||||
# a = sentence_embedding
|
||||
# pad = nn.ZeroPad2d(padding=(0, 0, 0, 23))
|
||||
# a = pad(a)
|
||||
# a = a.reshape(1,1,32,32)
|
||||
# out = mymodel(a)
|
||||
|
||||
# _, predicted = torch.max(out, 1)
|
||||
# labels = 0
|
||||
# total += 1
|
||||
# correct += (predicted.numpy() == labels).sum()
|
||||
# # a = torch.rand(1,1,64,32)
|
||||
# # a = F.interpolate(a, size=[32, 32])
|
||||
|
||||
# print("准确率为:")
|
||||
# print(correct / total)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,10 @@
|
|||
/target/
|
||||
nbactions.xml
|
||||
lib
|
||||
build
|
||||
.settings
|
||||
.classpath
|
||||
.project
|
||||
dist
|
||||
.idea/
|
||||
*.iml
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: cz.startnet.utils.pgdiff.Main
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# openGauss Diff Tool (aka gsdiff)
|
||||
|
||||
openGauss Diff Tool is free openGauss diff tool that is useful for
|
||||
comparison/diffing of database schemas.
|
||||
|
||||
To use:
|
||||
1 $cd /out/artifacts/gsdiff
|
||||
2 $java -jar gsdiff.jar database_1.dump database_2.dump
|
||||
or
|
||||
$java -jar gsdiff.jar database_1.dump database_2.dump > filename
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/target/
|
||||
nbactions.xml
|
||||
lib
|
||||
build
|
||||
.settings
|
||||
.classpath
|
||||
.project
|
||||
dist
|
||||
.idea/
|
||||
*.iml
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: cz.startnet.utils.pgdiff.Main
|
||||
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
# Another PostgreSQL Diff Tool (aka apgdiff)
|
||||
|
||||
Another PostgreSQL Diff Tool is free PostgreSQL diff tool that is useful for
|
||||
comparison/diffing of database schemas. You can find more information at its
|
||||
website at http://www.apgdiff.com/. If you found an issue in apgdiff, please
|
||||
file it at https://github.com/fordfrog/apgdiff/issues. If you for some reason
|
||||
need to contact author of this application, you can email him at
|
||||
fordfrog@fordfrog.com.
|
||||
|
||||
This repo is mainly unmaintained. But if you found a bug and create a pull request chances are good that it will be merged.
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 2.7.0
|
||||
|
||||
#### New Features
|
||||
* Initial Support to PARTITION BY on Create Table jalissonmello
|
||||
* Initial suport to identity column. #277 jalissonmello
|
||||
* Support Postgresql 12 -Add support for FUNCTION in CREATE TRIGGER #273 alexander-smyslov
|
||||
* Add supports for CREATE SEQUENCE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS. UltramanJack
|
||||
#### Fixes
|
||||
* Fix incorrect disable trigger parsing of quoted identifiers. Bugfixes #271 marcus-kempe
|
||||
* fix for null pointer exception #270 marco44
|
||||
* Fix issue where rule parsing was throwing # 269-marcus-kempe
|
||||
* fixed some crashes #257 d1maxa
|
||||
* Error on disable/enable trigger on trigger that dont exists on old schema.
|
||||
* Drop and Add constraint if table exists, issue #252
|
||||
|
||||
### Version 2.6
|
||||
|
||||
#### New Features
|
||||
* PostgresSQL 10 Support
|
||||
* Update dependencies to development with Java 11
|
||||
* Add security barrier and another options to Views
|
||||
* Add support to Create/Drop Rule
|
||||
* Triggers: Rerencing,Enable,Disable
|
||||
* use using column::type to cast the type of column
|
||||
|
||||
#### Fixes
|
||||
* Fix GRANT SEQUENCE, ALTER VIEW OWNER, GRANT(cols) errors
|
||||
|
||||
#### Tasks Done
|
||||
* Remove SourceForge repository
|
||||
|
||||
### Version 2.5
|
||||
|
||||
#### New Features
|
||||
* MATERIALIZED VIEW support in PostgreSQL 9.3 (Marti Raudsepp)
|
||||
* Better support for inherited tables (Daniel Watson)
|
||||
* Added support for CREATE UNLOGGED TABLE (Anatoliy Basov)
|
||||
* Added support for /**/ comments (yulei)
|
||||
* Support of triggers for views + clause 'INSTEAD OF' (Sergej Bonich)
|
||||
* Add support for GRANT and REVOKE on objects: table, view, sequence, column (serge-pouliquen-itf)
|
||||
* Add support for ALTER TABLE ... OWNER TO (serge-pouliquen-itf)
|
||||
* Add support for CREATE TYPE (Karol Rybak)
|
||||
* Add support for CREATE EXTENSION (Átila Camurça Alves)
|
||||
* Add basic support for CREATE FOREIGN TABLE (Bruno Almeida)
|
||||
|
||||
#### Fixes
|
||||
* Added hint to use "CREATE TABLE ... CONSTRAINT name PRIMARY KEY/UNIQUE ..."
|
||||
instead of "CREATE TABLE ... PRIMARY KEY/UNIQUE ..." because apgdiff cannot
|
||||
easily support unnamed constraints.
|
||||
* Fixed issue with incorrect end of expression detection because of ignored [
|
||||
and ] brackets. This caused issues for example in statements like
|
||||
"... DEFAULT ARRAY[1, 2, 3], ..." where end of expression was detected at
|
||||
first comma (and not the third one) which then resulted in parser exception.
|
||||
* Fixed issue when outputting unsupported command information and the
|
||||
unsupported command string is shorter than 20 characters. (Linas Valiukas)
|
||||
* Added Spanish translation. (Sebastian Ortiz)
|
||||
* Fitted English help to 80 characters in width. (Dave Jarvis)
|
||||
* View query changes are now correctly detected even if it has declared
|
||||
columns that didn't change. (Marti Raudsepp)
|
||||
* Fixed issue with the $ sign in the object name (Anatoliy Basov)
|
||||
* Added French translation. (Jeremy Passeron)
|
||||
* Native for OS line endings in resulting diff (Sergej Bonich)
|
||||
* Add support for new Postgres schema dump format
|
||||
* Support for Postgres 10 CREATE SEQUENCE data type
|
||||
|
||||
### 2012-09-21: Version 2.4
|
||||
|
||||
#### New Features
|
||||
* Added support for ALTER SEQUENCE OWNED BY (patch by Mikhail Petrov).
|
||||
* Added support for CREATE TRIGGER ... UPDATE OF column.
|
||||
* Added switch --ignore-slony-triggers which causes that Slony triggers
|
||||
_slony_logtrigger and _slony_denyaccess are completely ignored during parsing
|
||||
and diffing.
|
||||
* Added switch --ignore-schema-creation which removes the need of CREATE SCHEMA
|
||||
declararions in the input files.
|
||||
|
||||
#### Fixes
|
||||
* Fixed issue with comments not being added on newly created columns.
|
||||
* Improved logging errors when parsing strings.
|
||||
* Added support for IF NOT EXISTS (patch by Felipe Sateler).
|
||||
* Fixed NPE when search_path contains quoted schema (patch by Steven Elliott).
|
||||
* Fixed dropping of default values when --add-defaults is specified (patch by
|
||||
Jim Mlodgenski).
|
||||
* Fixed all bugs related to incorrect parsing of end of statement, most often
|
||||
resulting in StringIndexOutOfBoundException.
|
||||
* Fixed CREATE TABLE statement output when table contains no column.
|
||||
|
||||
### 2010-10-22: Version 2.3
|
||||
|
||||
#### New Features
|
||||
* Added support for diffing of COMMENT ON statements.
|
||||
* Added switch --list-charsets to output list of supported charsets.
|
||||
|
||||
#### Fixes
|
||||
* Added user error messages instead of NullPointerException for cases when
|
||||
referenced database object was not found in the dump.
|
||||
* Fixed bug with parsing quoted object name when both first and second part of
|
||||
the name was quoted.
|
||||
* Fixed bug in parser that caused in some cases invalid match against expected
|
||||
"word".
|
||||
* Fixed bug in parser that caused array data types not detected correctly (was
|
||||
caused by fix of invalid match of "word" above).
|
||||
* Functions are now created after tables are create and updated, because of
|
||||
functions depending on tables and columns. Later will be implemented solution
|
||||
for cases where functions have to be created before table columns.
|
||||
|
||||
### 2010-10-09: Version 2.2.2
|
||||
|
||||
#### Fixes
|
||||
* Added missing new line after ALTER VIEW ... ALTER COLUMN ... SET/DROP DEFAULT.
|
||||
* Fixed parsing of quoted string values.
|
||||
* Fixed detection of function body separator (did not work when there was
|
||||
another 'AS' specified on the same line after the 'AS' starting function
|
||||
body).
|
||||
* If two dumps are completely same and there is more than one schema in the
|
||||
dumps, the output now does not contain 'SET search_path = ...'. In other
|
||||
words, if two dumps are completely same, no output is produced.
|
||||
* Replaced 'ALTER VIEW name ALTER COLUMN ...' with 'ALTER TABLE view_name ALTER
|
||||
COLUMN ...' to make it compatible with PostgreSQL releases prior to 8.4.
|
||||
* Fixed parsing of '' escapes.
|
||||
|
||||
#### Other
|
||||
* Added support for localization of apgdiff.
|
||||
* Added Czech localization.
|
||||
|
||||
### 2010-10-03: Version 2.2.1
|
||||
|
||||
#### New Features
|
||||
* Commands like OWNER TO and ENABLE/DISABLE TRIGGER/RULE are now added to the
|
||||
diff output even for commands that are otherwise being parsed, like ALTER
|
||||
TABLE.
|
||||
|
||||
#### Fixes
|
||||
* Fixed bug where default values were dropped from VIEW columns even if they
|
||||
were not modified.
|
||||
|
||||
### 2010-10-02: Version 2.2
|
||||
|
||||
#### New Features
|
||||
* Statements that are not supported by apgdiff yet are now all ignored(till this
|
||||
release apgdiff had to be instructed to know what commands to ignore which was
|
||||
not good solution). This is the same behavior as in 1.* releases.
|
||||
* Added command line switch <code>--output-ignored-statements</code> which can
|
||||
be used to output statements that apgdiff ignores in the dump files. This
|
||||
feature makes more clear what statements were ignored and developer has to
|
||||
handle them manually eventually.
|
||||
|
||||
### 2010-09-30: Version 2.1
|
||||
|
||||
#### New Features
|
||||
* Added support for ALTER VIEW.
|
||||
* Added support for ALTER TABLE view_name/sequence_name.
|
||||
|
||||
#### Fixes
|
||||
* Fixed issue with comparison of VIEWs when columns are not specified but query
|
||||
has changed.
|
||||
* Fixed parsing of quoted names at many places.
|
||||
* CREATE RULE is now silently skipped.
|
||||
|
||||
### 2010-09-16: Version 2.0.2
|
||||
|
||||
#### Fixes
|
||||
* CREATE DOMAIN is now silently skipped.
|
||||
|
||||
### 2010-09-16: Version 2.0.1
|
||||
|
||||
#### Fixes
|
||||
* CREATE OPERATOR and ALTER LANGUAGE are now silently skipped.
|
||||
|
||||
### 2010-09-13: Version 2.0 Including Beta Releases
|
||||
|
||||
#### New Features
|
||||
* SQL parser has been completely rewritten to allow safer and more flexible
|
||||
parsing of SQL statements.
|
||||
* Statements not supported by apgdiff are now not silenty ignored if apgdiff is
|
||||
not told (by me in code) to ignore them.
|
||||
* Added support for ALTER TABLE ... ALTER COLUMN ... SET STORAGE
|
||||
PLAIN|EXTERNAL|EXTENDED|MAIN.
|
||||
* Added support for CREATE TABLE ... TABLESPACE.
|
||||
* Updated parsing of CREATE TABLE ... WITH/WITHOUT OIDS.
|
||||
* Added support for CREATE TRIGGER ... WHEN and for even TRUNCATE.
|
||||
* Added support for CREATE SEQUENCE ... OWNED BY.
|
||||
* CREATE SCHEMA is now supported for both syntaxes.
|
||||
* Added support for default values on function arguments.
|
||||
* Added support for parsing ALTER TABLE ... ENABLE/DISABLE TRIGGER/PARSER, but
|
||||
they are not diffed for now.
|
||||
|
||||
#### Fixes
|
||||
* ALTER SEQUENCE and CREATE AGGREGATE are now silently skipped.
|
||||
* Fixed parsing of end of function.
|
||||
* Improved handling of dotted quoted names.
|
||||
* Fixed quoting of SQL reserved keywords.
|
||||
* Fixed parsing of function arguments.
|
||||
* Triggers are now dropped before functions are dropped (bug #2991245).
|
||||
* Improved diffing of CREATE TABLE ... INHERITS.
|
||||
|
||||
### Versions Prior to 2.0
|
||||
|
||||
These versions are not covered in changelog.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<project name="apgdiff" default="jar" basedir=".">
|
||||
<property name="name" value="Another PostgreSQL Diff Tool"/>
|
||||
<property name="version" value="2.7"/>
|
||||
<property name="build" value="build"/>
|
||||
<property name="dist" value="dist"/>
|
||||
<property name="build.output" value="${build}/classes"/>
|
||||
<property name="dist.javadoc" value="${dist}/javadoc"/>
|
||||
<property name="build.final.name" value="${ant.project.name}-${version}"/>
|
||||
<property name="lib" value="lib"/>
|
||||
<property name="test.reports" value="${build}/test-reports"/>
|
||||
<property name="test.output" value="${build}/test-classes"/>
|
||||
<property name="src" value="src/main/java"/>
|
||||
<property name="src.resources" value="src/main/resources"/>
|
||||
<property name="test.src" value="src/test/java"/>
|
||||
<property name="test.resources.src" value="src/test/resources"/>
|
||||
<property name="source" value="1.8"/>
|
||||
<property name="target" value="1.8"/>
|
||||
<property name="main.class" value="cz.startnet.utils.pgdiff.Main"/>
|
||||
|
||||
<path id="build.classpath">
|
||||
<fileset dir="${lib}">
|
||||
<include name="**/*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<target name="init" description="Initializes build environment">
|
||||
<tstamp/>
|
||||
</target>
|
||||
|
||||
<target name="clean" description="Clean the output directory">
|
||||
<delete dir="${build}"/>
|
||||
<delete dir="${dist}"/>
|
||||
<delete dir="${lib}"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="init,get-deps"
|
||||
description="Compile the code">
|
||||
<mkdir dir="${build.output}"/>
|
||||
<javac destdir="${build.output}" srcdir="${src}" source="${source}"
|
||||
target="${target}" debug="true" deprecation="true"
|
||||
optimize="false"/>
|
||||
<copy todir="${build.output}">
|
||||
<fileset dir="${src.resources}"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile" description="Clean the JAR">
|
||||
<mkdir dir="${dist}"/>
|
||||
<jar jarfile="${dist}/${build.final.name}.jar"
|
||||
basedir="${build.output}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="${main.class}"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="compile-tests" depends="compile"
|
||||
description="Compile the test code">
|
||||
<mkdir dir="${test.output}"/>
|
||||
<javac destdir="${test.output}" srcdir="${test.src}" source="${source}"
|
||||
target="${target}" debug="true" deprecation="true"
|
||||
optimize="false">
|
||||
<classpath>
|
||||
<path refid="build.classpath"/>
|
||||
<pathelement location="${build.output}"/>
|
||||
</classpath>
|
||||
</javac>
|
||||
<copy todir="${test.output}">
|
||||
<fileset dir="${test.resources.src}"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="test" depends="compile-tests"
|
||||
description="Run the test cases">
|
||||
<mkdir dir="${test.reports}"/>
|
||||
<junit printSummary="yes" haltonerror="true" haltonfailure="true"
|
||||
fork="false" dir=".">
|
||||
<sysproperty key="basedir" value="."/>
|
||||
<formatter type="plain" usefile="false"/>
|
||||
<classpath>
|
||||
<path refid="build.classpath"/>
|
||||
<pathelement location="${build.output}"/>
|
||||
<pathelement location="${test.output}"/>
|
||||
</classpath>
|
||||
<batchtest todir="${test.reports}">
|
||||
<fileset dir="${test.src}">
|
||||
<include name="**/*Test.java"/>
|
||||
</fileset>
|
||||
</batchtest>
|
||||
</junit>
|
||||
</target>
|
||||
|
||||
<target name="get-deps" description="Download all dependencies"
|
||||
unless="noget">
|
||||
<mkdir dir="${lib}"/>
|
||||
<get src="http://central.maven.org/maven2/junit/junit/4.8.1/junit-4.8.1.jar"
|
||||
dest="${lib}/junit.jar"/>
|
||||
<get src="http://central.maven.org/maven2/org/hamcrest/hamcrest-all/1.1/hamcrest-all-1.1.jar"
|
||||
dest="${lib}/hamcrest-all.jar"/>
|
||||
</target>
|
||||
|
||||
<target name="javadoc" description="Generates javadoc">
|
||||
<delete dir="doc"/>
|
||||
<mkdir dir="doc"/>
|
||||
<javadoc packagenames="cz.*" sourcepath="${src}"
|
||||
destdir="${dist.javadoc}" classpathref="build.classpath"
|
||||
author="true" version="true" private="false" use="true"
|
||||
splitindex="true" windowtitle="${name} ${version}"
|
||||
doctitle="${name} ${version}"/>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
Copyright (c) 2006 StartNet s.r.o.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
--
|
||||
-- openGauss database dump
|
||||
--
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET xmloption = content;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
|
||||
SET search_path = public;
|
||||
|
||||
--
|
||||
-- Name: tri_insert_func(); Type: FUNCTION; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
CREATE FUNCTION tri_insert_func() RETURNS trigger
|
||||
LANGUAGE plpgsql NOT SHIPPABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
INSERT INTO test_trigger VALUES(NEW.account_id, NEW.first_name, NEW.last_name);
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;
|
||||
|
||||
|
||||
ALTER FUNCTION public.tri_insert_func() OWNER TO omm;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- Name: customer; Type: TABLE; Schema: public; Owner: omm; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE customer (
|
||||
customer_id integer,
|
||||
account_id integer,
|
||||
first_name character(10),
|
||||
last_name character varying(20) DEFAULT ' '::character varying
|
||||
)
|
||||
WITH (orientation=row, compression=no);
|
||||
ALTER TABLE customer ALTER COLUMN first_name SET STORAGE MAIN;
|
||||
|
||||
|
||||
ALTER TABLE public.customer OWNER TO omm;
|
||||
|
||||
--
|
||||
-- Name: myview; Type: VIEW; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
CREATE VIEW myview(account_id,first_name) AS
|
||||
SELECT customer.account_id, customer.first_name FROM customer;
|
||||
|
||||
|
||||
ALTER VIEW public.myview OWNER TO omm;
|
||||
|
||||
--
|
||||
-- Name: test; Type: TABLE; Schema: public; Owner: yy; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE test (
|
||||
t integer
|
||||
)
|
||||
WITH (orientation=row, compression=no);
|
||||
|
||||
|
||||
ALTER TABLE public.test OWNER TO yy;
|
||||
|
||||
--
|
||||
-- Name: test_trigger; Type: TABLE; Schema: public; Owner: omm; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE test_trigger (
|
||||
customer_id integer,
|
||||
account_id integer,
|
||||
first_name character(10),
|
||||
last_name character varying(20)
|
||||
)
|
||||
WITH (orientation=row, compression=no);
|
||||
|
||||
|
||||
ALTER TABLE public.test_trigger OWNER TO omm;
|
||||
|
||||
--
|
||||
-- Name: serial1; Type: SEQUENCE; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
CREATE SEQUENCE serial1
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 20;
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Name: serial1; Type: LARGE SEQUENCE OWNED BY; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
ALTER SEQUENCE serial1 OWNED BY customer.customer_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: customer_id; Type: DEFAULT; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
ALTER TABLE customer ALTER COLUMN customer_id SET DEFAULT nextval('serial1'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: customer; Type: TABLE DATA; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
COPY customer (customer_id, account_id, first_name, last_name) FROM stdin;
|
||||
1 1 Job Lee
|
||||
\.
|
||||
;
|
||||
|
||||
--
|
||||
-- Name: serial1; Type: SEQUENCE SET; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('serial1', 20, true);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: test; Type: TABLE DATA; Schema: public; Owner: yy
|
||||
--
|
||||
|
||||
COPY test (t) FROM stdin;
|
||||
\.
|
||||
;
|
||||
|
||||
--
|
||||
-- Data for Name: test_trigger; Type: TABLE DATA; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
COPY test_trigger (customer_id, account_id, first_name, last_name) FROM stdin;
|
||||
\.
|
||||
;
|
||||
|
||||
--
|
||||
-- Name: first_name_index; Type: INDEX; Schema: public; Owner: omm; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX first_name_index ON customer USING btree (first_name) TABLESPACE pg_default;
|
||||
|
||||
ALTER TABLE customer CLUSTER ON first_name_index;
|
||||
|
||||
|
||||
--
|
||||
-- Name: _RETURN; Type: RULE; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
SET enable_cluster_resize = on;
|
||||
CREATE RULE "_RETURN" AS ON SELECT TO test_trigger DO INSTEAD SELECT customer.customer_id, customer.account_id, customer.first_name, customer.last_name FROM customer;
|
||||
|
||||
|
||||
--
|
||||
-- Name: _RETURN; Type: RULE; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
SET enable_cluster_resize = on;
|
||||
CREATE RULE "_RETURN" AS ON SELECT TO customer DO INSTEAD SELECT test_trigger.customer_id, test_trigger.account_id, test_trigger.first_name, test_trigger.last_name FROM test_trigger;
|
||||
ALTER TABLE customer DISABLE RULE "_RETURN";
|
||||
|
||||
|
||||
--
|
||||
-- Name: insert_trigger; Type: TRIGGER; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
CREATE TRIGGER insert_trigger BEFORE INSERT ON customer FOR EACH ROW EXECUTE PROCEDURE tri_insert_func();
|
||||
|
||||
ALTER TABLE customer DISABLE TRIGGER insert_trigger;
|
||||
|
||||
|
||||
--
|
||||
-- Name: public; Type: ACL; Schema: -; Owner: omm
|
||||
--
|
||||
|
||||
REVOKE ALL ON SCHEMA public FROM PUBLIC;
|
||||
REVOKE ALL ON SCHEMA public FROM omm;
|
||||
GRANT CREATE,USAGE ON SCHEMA public TO omm;
|
||||
GRANT USAGE ON SCHEMA public TO PUBLIC;
|
||||
|
||||
|
||||
--
|
||||
-- openGauss database dump complete
|
||||
--
|
||||
|
||||
Binary file not shown.
|
|
@ -0,0 +1,54 @@
|
|||
--
|
||||
-- openGauss database dump
|
||||
--
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET xmloption = content;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
|
||||
SET search_path = public;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- Name: customer; Type: TABLE; Schema: public; Owner: omm; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE customer (
|
||||
customer_id integer,
|
||||
account_id integer,
|
||||
first_name character(10),
|
||||
last_name character(10)
|
||||
)
|
||||
WITH (orientation=row, compression=no);
|
||||
|
||||
|
||||
ALTER TABLE public.customer OWNER TO omm;
|
||||
|
||||
--
|
||||
-- Data for Name: customer; Type: TABLE DATA; Schema: public; Owner: omm
|
||||
--
|
||||
|
||||
COPY customer (customer_id, account_id, first_name, last_name) FROM stdin;
|
||||
\.
|
||||
;
|
||||
|
||||
--
|
||||
-- Name: public; Type: ACL; Schema: -; Owner: omm
|
||||
--
|
||||
|
||||
REVOKE ALL ON SCHEMA public FROM PUBLIC;
|
||||
REVOKE ALL ON SCHEMA public FROM omm;
|
||||
GRANT CREATE,USAGE ON SCHEMA public TO omm;
|
||||
GRANT USAGE ON SCHEMA public TO PUBLIC;
|
||||
|
||||
|
||||
--
|
||||
-- openGauss database dump complete
|
||||
--
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
CREATE SEQUENCE serial1
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 20;
|
||||
|
||||
CREATE TABLE test (
|
||||
t integer
|
||||
)
|
||||
WITH (orientation=row, compression=no);
|
||||
|
||||
ALTER TABLE test OWNER TO yy;
|
||||
|
||||
CREATE TABLE test_trigger (
|
||||
customer_id integer,
|
||||
account_id integer,
|
||||
first_name character(10),
|
||||
last_name character varying(20)
|
||||
)
|
||||
WITH (orientation=row, compression=no);
|
||||
|
||||
ALTER TABLE test_trigger OWNER TO omm;
|
||||
|
||||
ALTER TABLE customer
|
||||
ALTER COLUMN customer_id SET DEFAULT nextval('serial1'::regclass),
|
||||
ALTER COLUMN last_name TYPE character varying(20) USING last_name::character varying(20) /* TYPE change - table: customer original: character(10) new: character varying(20) */,
|
||||
ALTER COLUMN last_name SET DEFAULT ' '::character varying;
|
||||
|
||||
ALTER TABLE ONLY customer ALTER COLUMN first_name SET STORAGE MAIN;
|
||||
ALTER SEQUENCE serial1
|
||||
OWNED BY customer.customer_id;
|
||||
|
||||
CREATE OR REPLACE FUNCTION tri_insert_func() RETURNS trigger
|
||||
LANGUAGE plpgsql NOT SHIPPABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
INSERT INTO test_trigger VALUES(NEW.account_id, NEW.first_name, NEW.last_name);
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX first_name_index ON customer USING btree (first_name) TABLESPACE pg_default;
|
||||
|
||||
ALTER TABLE customer CLUSTER ON first_name_index;
|
||||
|
||||
CREATE TRIGGER insert_trigger
|
||||
BEFORE INSERT ON customer
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE tri_insert_func();
|
||||
|
||||
ALTER TABLE customer DISABLE TRIGGER insert_trigger;
|
||||
|
||||
|
||||
CREATE VIEW myview (account_id, first_name) AS
|
||||
SELECT customer.account_id, customer.first_name FROM customer;
|
||||
|
||||
ALTER VIEW myview OWNER TO omm;
|
||||
|
||||
CREATE RULE "_RETURN" AS
|
||||
ON SELECT TO customer
|
||||
DO INSTEAD SELECT test_trigger.customer_id, test_trigger.account_id, test_trigger.first_name, test_trigger.last_name FROM test_trigger;
|
||||
|
||||
CREATE RULE "_RETURN" AS
|
||||
ON SELECT TO test_trigger
|
||||
DO INSTEAD SELECT customer.customer_id, customer.account_id, customer.first_name, customer.last_name FROM customer;
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>MavenCentral</id>
|
||||
<name>Maven repository</name>
|
||||
<url>https://repo1.maven.org/maven2</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
<groupId>cz.startnet</groupId>
|
||||
<artifactId>apgdiff</artifactId>
|
||||
<name>Another PostgreSQL Diff Tool</name>
|
||||
<version>2.7.0</version>
|
||||
<description>Simple PostgreSQL diff tool that is useful for schema upgrades. The tool compares two schema dump files and creates output file that is (after some hand-made modifications) suitable for upgrade of old schema.</description>
|
||||
|
||||
<url>http://www.apgdiff.com/</url>
|
||||
|
||||
<issueManagement>
|
||||
<system>GitHub Issue Tracker</system>
|
||||
<url>https://github.com/fordfrog/apgdiff/issues?sort=created&state=open</url>
|
||||
</issueManagement>
|
||||
|
||||
<inceptionYear>2006</inceptionYear>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>fordfrog</id>
|
||||
<name>Miroslav Šulc</name>
|
||||
<email>miroslav.sulc@startnet.cz</email>
|
||||
<organization>StartNet s.r.o.</organization>
|
||||
<organizationUrl>http://www.startnet.biz/</organizationUrl>
|
||||
<roles>
|
||||
<role>main developer</role>
|
||||
</roles>
|
||||
<timezone>+1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<contributors>
|
||||
<contributor>
|
||||
<name>Matthieu Patou</name>
|
||||
<roles>
|
||||
<role>CLUSTER and STATISTICS support + ordering of columns according to source DDL</role>
|
||||
</roles>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Jure Mercun</name>
|
||||
<roles>
|
||||
<role>Case insensitivity of parsers + correct handling of quoted names</role>
|
||||
</roles>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Hans Idink</name>
|
||||
<roles>
|
||||
<role>Initial help with support of FUNCTIONs</role>
|
||||
</roles>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Anders Semb Hermansen</name>
|
||||
<roles>
|
||||
<role>Ignoring multiple whitespace while comparing content of functions </role>
|
||||
</roles>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Jalisson Mello</name>
|
||||
<roles>
|
||||
<role>Postgres 10 Support </role>
|
||||
</roles>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Velten Heyn</name>
|
||||
<roles>
|
||||
<role>Part time manager</role>
|
||||
</roles>
|
||||
</contributor>
|
||||
</contributors>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT</name>
|
||||
<url>license.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
<comments>A business-friendly OSS license</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/fordfrog/apgdiff.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com:fordfrog/apgdiff.git</developerConnection>
|
||||
<url>https://github.com/fordfrog/apgdiff.git</url>
|
||||
</scm>
|
||||
|
||||
<organization>
|
||||
<name>StartNet s.r.o.</name>
|
||||
<url>http://www.startnet.biz</url>
|
||||
</organization>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
<debug>false</debug>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<classpathPrefix>lib</classpathPrefix>
|
||||
<mainClass>cz.startnet.utils.pgdiff.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadoc</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<show>private</show>
|
||||
<tags>
|
||||
<tag>
|
||||
<name>todo</name>
|
||||
<!-- todo tag for all places -->
|
||||
<placement>a</placement>
|
||||
<head>To do something:</head>
|
||||
</tag>
|
||||
</tags>
|
||||
<source>8</source>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>${basedir}/src/main/assembly/bin.xml</descriptor>
|
||||
<descriptor>${basedir}/src/main/assembly/src.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<type>maven-plugin</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<type>maven-plugin</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<type>maven-plugin</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<minmemory>128m</minmemory>
|
||||
<maxmemory>512</maxmemory>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<tags>
|
||||
<tag>
|
||||
<name>todo</name>
|
||||
<placement>a</placement>
|
||||
<head>To do something:</head>
|
||||
</tag>
|
||||
</tags>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>net.sf</groupId>
|
||||
<artifactId>stat-scm</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-pmd-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<sourceEncoding>utf-8</sourceEncoding>
|
||||
<targetJdk>1.8</targetJdk>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>taglist-maven-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<inputEncoding>UTF-8</inputEncoding>
|
||||
<outputEncoding>UTF-8</outputEncoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>3.0.5</version>
|
||||
<configuration>
|
||||
<threshold>Ignore</threshold>
|
||||
<effort>Max</effort>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,21 @@
|
|||
<assembly>
|
||||
<id>bin</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
|
||||
<files>
|
||||
<file>
|
||||
<source>${project.build.directory}/${project.build.finalName}.${project.packaging}</source>
|
||||
<outputDirectory></outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${basedir}/license.txt</source>
|
||||
<outputDirectory></outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>${basedir}/README.md</source>
|
||||
<outputDirectory></outputDirectory>
|
||||
</file>
|
||||
</files>
|
||||
</assembly>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue