Git
Threads by month
- ----- 2025 -----
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 11 participants
- 38616 discussions

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3670-gf9550ae
by Marc Delisle 15 Jun '11
by Marc Delisle 15 Jun '11
15 Jun '11
The branch, master has been updated
via f9550ae3e2808ecc8d82b7528b7a9a3ee92249db (commit)
via 131e9caa5f8682ba36808279dbea81417e554d54 (commit)
via 81ebc00206d6aee95900b1c28b0212ee79d3bb25 (commit)
via 161f8977fa8c053af5585dc79cacb8a0c8a6e60a (commit)
via b18be4eaec8321c40659ec5683f49698551d8d76 (commit)
via 09fad13dcb1bbeb269d7dad6e5cf1a8e82aa754d (commit)
via 8bb5946a907ea85e51e31bd07a791b689b9be2e0 (commit)
via 2be90c39071327412677d74c67b1b8b0e662266c (commit)
via 8b2439f4a71507c9c43ca58f844ce8e14c2e1d7b (commit)
via af73dd335d86302e0be30e95ed39262a4e3b5caa (commit)
via f2588c536df0510f1de28e7a033da2199aa7ebfa (commit)
via b53ac0870cd2845dfb4b214ca1fe6583c636c444 (commit)
from 84a92c03f4f82bbce4313bd2bcbce3ed87754f07 (commit)
- Log -----------------------------------------------------------------
commit f9550ae3e2808ecc8d82b7528b7a9a3ee92249db
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 09:13:00 2011 -0400
ChangeLog entry for Thilanka Kaushalya's latest new feature
commit 131e9caa5f8682ba36808279dbea81417e554d54
Merge: 84a92c03f4f82bbce4313bd2bcbce3ed87754f07 81ebc00206d6aee95900b1c28b0212ee79d3bb25
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 09:09:23 2011 -0400
Merge commit '81ebc00206d6aee95900b1c28b0212ee79d3bb25'
commit 81ebc00206d6aee95900b1c28b0212ee79d3bb25
Merge: 161f8977fa8c053af5585dc79cacb8a0c8a6e60a 551cc9a465269ab1208438d81fc0d492ce797366
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Wed Jun 15 11:36:40 2011 +0530
Merge branch 'master' of git://phpmyadmin.git.sourceforge.net/gitroot/phpmyadmin/phpmyadmin
commit 161f8977fa8c053af5585dc79cacb8a0c8a6e60a
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Wed Jun 15 11:35:59 2011 +0530
Fixed the delay at dialog load in table insert
commit b18be4eaec8321c40659ec5683f49698551d8d76
Merge: 09fad13dcb1bbeb269d7dad6e5cf1a8e82aa754d 25dc50a5dd8a69f435272d37f7febafffc1d0ff0
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Tue Jun 14 21:53:12 2011 +0530
Merge branch 'master' of git://phpmyadmin.git.sourceforge.net/gitroot/phpmyadmin/phpmyadmin
commit 09fad13dcb1bbeb269d7dad6e5cf1a8e82aa754d
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Tue Jun 14 21:51:21 2011 +0530
Added the update row count in db structure
commit 8bb5946a907ea85e51e31bd07a791b689b9be2e0
Merge: 2be90c39071327412677d74c67b1b8b0e662266c d0974bd61f2b228338e5a2af7f5967bcc7037d08
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Mon Jun 13 18:12:17 2011 +0530
Merge branch 'master' of git://phpmyadmin.git.sourceforge.net/gitroot/phpmyadmin/phpmyadmin
commit 2be90c39071327412677d74c67b1b8b0e662266c
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Mon Jun 13 18:11:00 2011 +0530
Added update the tablesForm in db insert
commit 8b2439f4a71507c9c43ca58f844ce8e14c2e1d7b
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Sun Jun 12 23:02:32 2011 +0530
added the buttonYes click actions
commit af73dd335d86302e0be30e95ed39262a4e3b5caa
Merge: f2588c536df0510f1de28e7a033da2199aa7ebfa 54c062c0ad19db67250fde19eff7f44f31429e74
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Fri Jun 10 22:55:31 2011 +0530
Merge branch 'master' of git://phpmyadmin.git.sourceforge.net/gitroot/phpmyadmin/phpmyadmin
commit f2588c536df0510f1de28e7a033da2199aa7ebfa
Merge: b53ac0870cd2845dfb4b214ca1fe6583c636c444 cd5cddb2e4324094eae365e01422e8d165d372e8
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Fri Jun 10 22:15:57 2011 +0530
Merge branch 'master' of git://phpmyadmin.git.sourceforge.net/gitroot/phpmyadmin/phpmyadmin
commit b53ac0870cd2845dfb4b214ca1fe6583c636c444
Author: Thilanka Kaushalya <lgtkaushalya(a)gmail.com>
Date: Fri Jun 10 11:06:36 2011 +0530
Apply ajax behavior to the insert to table option in db structure
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 1 +
db_structure.php | 5 +-
js/db_structure.js | 136 +++++++++++++++++++++++++++++++++++++++++++++++++---
tbl_replace.php | 3 +-
4 files changed, 135 insertions(+), 10 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index daa1a62..e648e2f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -18,6 +18,7 @@
+ [interface] Changed way of generating charts.
+ rfe #939233 [interface] Flexible column width
+ [interface] Mouse-based column reordering in query results
++ AJAX for Insert to a table from database Structure page
3.4.3.0 (not yet released)
- bug #3311170 [sync] Missing helper icons in Synchronize
diff --git a/db_structure.php b/db_structure.php
index 864a668..b49b417 100644
--- a/db_structure.php
+++ b/db_structure.php
@@ -12,6 +12,7 @@ require_once './libraries/common.inc.php';
$GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js';
$GLOBALS['js_include'][] = 'db_structure.js';
+$GLOBALS['js_include'][] = 'tbl_change.js';
/**
* Prepares the tables list if the user where not redirected to this script
@@ -354,8 +355,8 @@ foreach ($tables as $keyname => $each_table) {
<?php echo $titles['Structure']; ?></a></td>
<td align="center"><?php echo $search_table; ?></td>
<?php if (! $db_is_information_schema) { ?>
- <td align="center">
- <a href="tbl_change.php?<?php echo $tbl_url_query; ?>">
+ <td align="center" class="insert_table">
+ <a <?php echo ($GLOBALS['cfg']['AjaxEnable'] ? 'class="ajax"' : ''); ?> href="tbl_change.php?<?php echo $tbl_url_query; ?>">
<?php echo $titles['Insert']; ?></a></td>
<td align="center"><?php echo $empty_table; ?></td>
<td align="center">
diff --git a/js/db_structure.js b/js/db_structure.js
index 4affd75..92dae86 100644
--- a/js/db_structure.js
+++ b/js/db_structure.js
@@ -10,7 +10,7 @@
/**
* AJAX scripts for db_structure.php
- *
+ *
* Actions ajaxified here:
* Drop Database
* Truncate Table
@@ -33,7 +33,7 @@ function PMA_adjustTotals($this_anchor) {
// (not really needed in case we are dropping the table)
$rows_td.text('0');
// set size to unknown (not sure how to get the exact
- // value here, as an empty InnoDB table would have a size)
+ // value here, as an empty InnoDB table would have a size)
$size_td.text('-');
// try to compute a new total row number
@@ -51,6 +51,128 @@ function PMA_adjustTotals($this_anchor) {
}
$(document).ready(function() {
+ /**
+ * Ajax Event handler for 'Insert Table'
+ *
+ * @uses PMA_ajaxShowMessage()
+ * @see $cfg['AjaxEnable']
+ */
+ var currrent_insert_table;
+ $("td.insert_table a.ajax").live('click', function(event){
+ event.preventDefault();
+ currrent_insert_table = $(this);
+ var url = $(this).attr("href");
+ if (url.substring(0, 15) == "tbl_change.php?") {
+ url = url.substring(15);
+ }
+
+ var div = $('<div id="insert_table_dialog"></div>');
+ var target = "tbl_change.php";
+
+ /**
+ * @var button_options Object that stores the options passed to jQueryUI
+ * dialog
+ */
+ var button_options = {};
+ // in the following function we need to use $(this)
+ button_options[PMA_messages['strCancel']] = function() {$(this).parent().dialog('close').remove();}
+
+ var button_options_error = {};
+ button_options_error[PMA_messages['strOK']] = function() {$(this).parent().dialog('close').remove();}
+
+ var $msgbox = PMA_ajaxShowMessage();
+
+ $.get( target , url+"&ajax_request=true" , function(data) {
+ //in the case of an error, show the error message returned.
+ if (data.success != undefined && data.success == false) {
+ div
+ .append(data.error)
+ .dialog({
+ title: PMA_messages['strInsertTable'],
+ height: 230,
+ width: 900,
+ open: PMA_verifyTypeOfAllColumns,
+ buttons : button_options_error
+ })// end dialog options
+ } else {
+ div
+ .append(data)
+ .dialog({
+ title: PMA_messages['strInsertTable'],
+ height: 600,
+ width: 900,
+ open: PMA_verifyTypeOfAllColumns,
+ buttons : button_options
+ })
+ //Remove the top menu container from the dialog
+ .find("#topmenucontainer").hide()
+ ; // end dialog options
+ $(".insertRowTable").addClass("ajax");
+ $("#buttonYes").addClass("ajax");
+ }
+ PMA_ajaxRemoveMessage($msgbox);
+ }) // end $.get()
+
+ });
+
+ $("#insertForm .insertRowTable.ajax input[value=Go]").live('click', function(event) {
+ event.preventDefault();
+ /**
+ * @var the_form object referring to the insert form
+ */
+ var $form = $("#insertForm");
+ $("#result_query").remove();
+ PMA_prepareForAjaxRequest($form);
+ //User wants to submit the form
+ $.post($form.attr('action'), $form.serialize() , function(data) {
+ if(data.success == true) {
+ PMA_ajaxShowMessage(data.message);
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ if ($("#insert_table_dialog").length > 0) {
+ $("#insert_table_dialog").dialog("close").remove();
+ }
+ /**Update the row count at the tableForm*/
+ currrent_insert_table.closest('tr').find('.value.tbl_rows').html(data.row_count);
+ }) // end $.post()
+ }) // end insert table button "Go"
+
+ $("#buttonYes.ajax").live('click', function(event){
+ event.preventDefault();
+ /**
+ * @var the_form object referring to the insert form
+ */
+ var $form = $("#insertForm");
+ /**Get the submit type and the after insert type in the form*/
+ var selected_submit_type = $("#insertForm").find("#actions_panel .control_at_footer option:selected").attr('value');
+ var selected_after_insert = $("#insertForm").find("#actions_panel select[name=after_insert] option:selected").attr('value');
+ $("#result_query").remove();
+ PMA_prepareForAjaxRequest($form);
+ //User wants to submit the form
+ $.post($form.attr('action'), $form.serialize() , function(data) {
+ if(data.success == true) {
+ PMA_ajaxShowMessage(data.message);
+ if (selected_submit_type == "showinsert") {
+ $(data.sql_query).insertAfter("#topmenucontainer");
+ $("#result_query .notice").remove();
+ $("#result_query").prepend((data.message));
+ }
+ if (selected_after_insert == "new_insert") {
+ /**Trigger the insert dialog for new_insert option*/
+ currrent_insert_table.trigger('click');
+ }
+
+ } else {
+ PMA_ajaxShowMessage(data.error);
+ }
+ if ($("#insert_table_dialog").length > 0) {
+ $("#insert_table_dialog").dialog("close").remove();
+ }
+ /**Update the row count at the tableForm*/
+ currrent_insert_table.closest('tr').find('.value.tbl_rows').html(data.row_count);
+ }) // end $.post()
+ });
/**
* Ajax Event handler for 'Truncate Table'
@@ -149,7 +271,7 @@ $(document).ready(function() {
/**
* Ajax Event handler for 'Drop Event'
- *
+ *
* @uses $.PMA_confirm()
* @uses PMA_ajaxShowMessage()
* @see $cfg['AjaxEnable']
@@ -188,7 +310,7 @@ $(document).ready(function() {
/**
* Ajax Event handler for 'Drop Procedure'
- *
+ *
* @uses $.PMA_confirm()
* @uses PMA_ajaxShowMessage()
* @see $cfg['AjaxEnable']
@@ -220,10 +342,10 @@ $(document).ready(function() {
}) // end $.get()
}) // end $.PMA_confirm()
}) //end Drop Procedure
-
+
/**
* Ajax Event handler for 'Drop tracking'
- *
+ *
* @uses $.PMA_confirm()
* @uses PMA_ajaxShowMessage()
* @see $cfg['AjaxEnable']
@@ -261,7 +383,7 @@ $(document).ready(function() {
//Calculate Real End for InnoDB
/**
* Ajax Event handler for calculatig the real end for a InnoDB table
- *
+ *
* @uses $.PMA_confirm
*/
$('#real_end_input').live('click', function(event) {
diff --git a/tbl_replace.php b/tbl_replace.php
index d23482e..e08dafb 100644
--- a/tbl_replace.php
+++ b/tbl_replace.php
@@ -424,7 +424,8 @@ if (! empty($error_messages)) {
unset($error_messages, $warning_messages, $total_affected_rows, $last_messages, $last_message);
if($GLOBALS['is_ajax_request'] == true) {
-
+ /**Get the total row count of the table*/
+ $extra_data['row_count'] = PMA_Table::countRecords($_REQUEST['db'],$_REQUEST['table']);
$extra_data['sql_query'] = PMA_showMessage(NULL, $GLOBALS['display_query']);
PMA_ajaxResponse($message, $message->isSuccess(), $extra_data);
}
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3657-g42f0468
by Marc Delisle 15 Jun '11
by Marc Delisle 15 Jun '11
15 Jun '11
The branch, master has been updated
via 42f04687c39dd1bae0f58270900dff3924453efb (commit)
via 0e5c8a9497ee30c62863191ee82490b3bbf51c37 (commit)
via b3faf9cf42c98de53c1722c15208554a81ff62cf (commit)
via ff5e4c83612ce9de4feadea570389499a66a824d (commit)
via d28d7daba1a15b184cebaadc94dad787170260e5 (commit)
via 5bf85368313d488b57d0edce531de8b172dba8f3 (commit)
via 2d8932b95e848741f387af9b4fb0998b5e96570e (commit)
via c1f6904eba56f816b203049a7bf2a529903da1e3 (commit)
via 3750dcbb2fddc1e513c3ededf94acce95f549d12 (commit)
via 73b9166240e1ed5cca413499825bf096d87d1536 (commit)
via 770b1c7dd39c979cfafb018e0ecd46bbfc975d0e (commit)
via 8a87d944a56d64073cfc840ea43a5f61d9d0cbe1 (commit)
via ff292800be9d276c06949ae4dc79c373de3099f7 (commit)
via 916db6a9362418d7cb4ee6ee3f9491f379febc8f (commit)
via 84ad2a865c82dac2924c04299952d5e87bf71ae9 (commit)
via 823decbaf4fbbf2feab9f2af2dc42324742226f5 (commit)
via 8493052a2f24c9a8213af50359632e2efd6d6945 (commit)
via 467e6beaf7e9c58bbba7d809bbc654829f8439b6 (commit)
via 4d27d21e80ef895990c919a5164fac535c9283d4 (commit)
via cf64d5282be2c3c20d323256c8aa9fc295fd5ee8 (commit)
via c89e0aa2ab2629eb9b47255b070eb14eef75aa19 (commit)
via 039d56f97534f2c720df73f4e5325f3a9c838023 (commit)
via 2397b6f06f4056a8c3e9bd189adf2cc52646116b (commit)
via fcc2f82fa5bbdb3552cae6f15d60246a19442303 (commit)
via 78e7565cfd9779065e98ffc1bf8078d73d9a709d (commit)
via 83f7170304de06b1789e32105424bdf216bd3227 (commit)
via 7c18b8106a8f8b72dbc2b7a2de8da11fec0f975c (commit)
via 5814064b81cff9c581a83e773ff7f78fd026eebc (commit)
via 169686bccfd6466c631e38c40133227171fee3cb (commit)
via eb7c64dfad5203de7c51e87e096b1d677f2dc5ca (commit)
via b81ae2e6cc04f004192c127ae6955d11d4356191 (commit)
via 031e5c97714f62f39c15fe63cb512d1a6d4e67ce (commit)
via fcf671b382eb2c9956fe5d46abccc86fd2da314f (commit)
via 0ea798b7940a8ffdb7d4aade42679d860f45e9a1 (commit)
via 07c067a83225dc5edab572b3f113915fdae3cff8 (commit)
via d37b0fa24609cc4abd9c5c605179519be3c68e5f (commit)
via 3ba2aa446336c5bdf48eebf8e116830c2e350f40 (commit)
via d7f8b537e32920b6822dd5cdc89a947ffa5d6079 (commit)
via 53d7bfa2b1f8261d7ea609ff1ec2d96a3a4c780a (commit)
via 46e360de1e827c396a01c26fab0c25b79370d6dd (commit)
via 0b933d261484ceb095b47e8aa5ffcb71c362bd54 (commit)
via 3ac62ac37f1cab119ef3579e3a8268805ccb8fe0 (commit)
via 403e2ed2bfeec79a4bfff4952e3a1499b8c269cd (commit)
via 803b4639bd4e036c246d40b545ff9eb573f1181a (commit)
via aad9a55b81635ec2c6e25b6f79e0f989508436f6 (commit)
via 082345bdfa6b0661a3a6196269ead832e01cd862 (commit)
via d0e41349ee45d0d46b32a8fac0856470233097e1 (commit)
via c2b78975ab87a7848004c7b211ae8df2b843b92f (commit)
via c991a19ceb32493ff0cb27a69a2843546d39ddba (commit)
via e9c055c5bc7a94636d7b6cd5801f2044bca86cca (commit)
via 5ed509bd89bd363e09b2c6593fc6ea4e6a03dab7 (commit)
via 3e3234b156a300e5517d1239755ff49270f24000 (commit)
via d64685c25afe282eabe73f23ba5ba2cbfcb541e0 (commit)
via ff5544d1c7ecd8efec1a52931f4bb113780118f2 (commit)
via e16b77c0a5198b0f79175018e77909037826ae22 (commit)
via 04fbe12ac7f6e3efe1b8f81b3d39fe8d11e4cc1f (commit)
via 1a66a483cf127c216fad038c9299520442e49b95 (commit)
via b0717ff8d5aa263b572746711acc50f7996ea87d (commit)
via 86d787267ddca21ee3f478662d46aafd2c8736b4 (commit)
via 0e73a2e1b3edae033a8a1ffd86b20a54237b070d (commit)
via e1d0b67af6a178e7674b3377435fcb4df4ed1362 (commit)
via 13bcc44628d42991818bb8d0e1504f33a52f5721 (commit)
via 0cca021b23384898dd96839288176fb749047273 (commit)
via dfc7171d6fbd2ef500b23232b8e2f9baaf3f3402 (commit)
via ce359e7506533803b97f287d77abd0ef935e2841 (commit)
via 88c89b2329fd2474050a73cdb6b1c9b383c0e154 (commit)
via 2e9b5a08d21ae7d97fbc0ec8923c45d5260854ec (commit)
via 8b8917ff747592e4ae72a5f7c7eda3f735102d57 (commit)
via 9fbbafdf7cbd5d06af0d37142724faa809799b51 (commit)
via dc81db3c76ba47e467a563b6fb4393589f9318e0 (commit)
via 1ab6f82e1dd3e7db2e86fd79b238297e5c56d6e6 (commit)
via 6c532a3ba33cceb49f64d19a0f4e82e59a5d1ad4 (commit)
via 40c325c4442985ea86345015f852f774bea44f58 (commit)
via 211d76b012cbcf6173de2fd75f4b5fe585e2eeb9 (commit)
via 3cf69dfbb89b5476de32828a636dd3e27e912a50 (commit)
via 3b7f35d5c9f2c25c4fe5d7322fbe79c181637764 (commit)
via 6bfeb440764226a7bbe8c8a2c3828809ca4b21d5 (commit)
via d174e8b41c6df7b8e38900e2fc4d8560b1501bd5 (commit)
via 9ac5173788e174c76865394257c535cf69ecf4b6 (commit)
via 67d8f469d24580ae8ad623ad2a175ae017acf193 (commit)
via c4c57b86c32647c1a1dcb0b9125d35fbced18aac (commit)
via 80981154486608cc154d6ab461847e999336566e (commit)
via 159696ba69d85fc59e4f82a343c3ad7a45f1636f (commit)
via 6b69e631d72bf08eb32e36c52d99d87db6cf4912 (commit)
via f4b47d4cf0a846f394570673429a9759eb5f4991 (commit)
via 566851d210104cb44ec536dc195b05707e0496c8 (commit)
via 25ae3e2387ca8936434d427fb5eb1f2d0a4f6d13 (commit)
via 36680cff27ab43da1051b3877f9134b06ad2873f (commit)
via 518638fa87b73c9fb02d341f47a7a01ddb5e4f5b (commit)
via c40fed887e27558aa0d07ca10ce69534c4636813 (commit)
via ad6e109e0ee20b61fc5b1107856387cd09aeb2d0 (commit)
via 9ae87854ed6474e111a4d77a0d747215e1cff9f6 (commit)
from 551cc9a465269ab1208438d81fc0d492ce797366 (commit)
- Log -----------------------------------------------------------------
commit 42f04687c39dd1bae0f58270900dff3924453efb
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 08:12:58 2011 -0400
ChangeLog entries for Aris Feryanto's latest new features
commit 0e5c8a9497ee30c62863191ee82490b3bbf51c37
Merge: 551cc9a465269ab1208438d81fc0d492ce797366 b3faf9cf42c98de53c1722c15208554a81ff62cf
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 08:06:30 2011 -0400
Merge commit 'b3faf9cf42c98de53c1722c15208554a81ff62cf'
commit b3faf9cf42c98de53c1722c15208554a81ff62cf
Merge: ff5e4c83612ce9de4feadea570389499a66a824d 5bf85368313d488b57d0edce531de8b172dba8f3
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 15 07:23:37 2011 +0700
Merge branch 'tablesort' into aris
commit ff5e4c83612ce9de4feadea570389499a66a824d
Merge: d28d7daba1a15b184cebaadc94dad787170260e5 2d8932b95e848741f387af9b4fb0998b5e96570e
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 15 07:23:32 2011 +0700
Merge branch 'colresize' into aris
commit d28d7daba1a15b184cebaadc94dad787170260e5
Merge: 73b9166240e1ed5cca413499825bf096d87d1536 25dc50a5dd8a69f435272d37f7febafffc1d0ff0
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 15 07:23:06 2011 +0700
Fix merge conflict with origin/master
commit 5bf85368313d488b57d0edce531de8b172dba8f3
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 15 07:10:16 2011 +0700
Fix bug in 'Remember Sorting': modified caused error in PHP
commit 2d8932b95e848741f387af9b4fb0998b5e96570e
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 23:38:30 2011 +0700
Better column order remembering AJAX
commit c1f6904eba56f816b203049a7bf2a529903da1e3
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 22:57:47 2011 +0700
Change PMA_isBrowsing to PMA_isSelect
commit 3750dcbb2fddc1e513c3ededf94acce95f549d12
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 22:47:19 2011 +0700
Column resizing & reordering: better support without javascript
commit 73b9166240e1ed5cca413499825bf096d87d1536
Merge: 8a87d944a56d64073cfc840ea43a5f61d9d0cbe1 770b1c7dd39c979cfafb018e0ecd46bbfc975d0e
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 11:56:06 2011 +0700
Merge branch 'colresize' into aris
commit 770b1c7dd39c979cfafb018e0ecd46bbfc975d0e
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 11:55:35 2011 +0700
Fix: column resizing & reordering under db_search.php (also enable inline edit support)
commit 8a87d944a56d64073cfc840ea43a5f61d9d0cbe1
Merge: ff292800be9d276c06949ae4dc79c373de3099f7 916db6a9362418d7cb4ee6ee3f9491f379febc8f
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 09:44:10 2011 +0700
Merge branch 'tablesort' into aris
commit ff292800be9d276c06949ae4dc79c373de3099f7
Merge: 467e6beaf7e9c58bbba7d809bbc654829f8439b6 84ad2a865c82dac2924c04299952d5e87bf71ae9
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 09:44:05 2011 +0700
Merge branch 'colresize' into aris
commit 916db6a9362418d7cb4ee6ee3f9491f379febc8f
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 09:41:32 2011 +0700
Remember sorting: Fix criteria for browsing, following PMA_isBrowsing
commit 84ad2a865c82dac2924c04299952d5e87bf71ae9
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 09:38:34 2011 +0700
Fix PMA_isBrowsing criteria
commit 823decbaf4fbbf2feab9f2af2dc42324742226f5
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 09:37:51 2011 +0700
Add 'Click to mark' hint
commit 8493052a2f24c9a8213af50359632e2efd6d6945
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 14 08:40:04 2011 +0700
Fix column resizing and reordering compatibility with IE
commit 467e6beaf7e9c58bbba7d809bbc654829f8439b6
Merge: 2397b6f06f4056a8c3e9bd189adf2cc52646116b 4d27d21e80ef895990c919a5164fac535c9283d4
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 13 13:49:12 2011 +0700
Merge branch 'colresize' into aris
commit 4d27d21e80ef895990c919a5164fac535c9283d4
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 13 13:46:18 2011 +0700
No reorder column for table with only 1 column
commit cf64d5282be2c3c20d323256c8aa9fc295fd5ee8
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 13 12:08:17 2011 +0700
Remember column order only in 'Browse' tab
commit c89e0aa2ab2629eb9b47255b070eb14eef75aa19
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 13 11:21:09 2011 +0700
Merge hint for column sorting
commit 039d56f97534f2c720df73f4e5325f3a9c838023
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 13 08:28:21 2011 +0700
Better CSS unit
commit 2397b6f06f4056a8c3e9bd189adf2cc52646116b
Merge: 169686bccfd6466c631e38c40133227171fee3cb fcc2f82fa5bbdb3552cae6f15d60246a19442303
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sun Jun 12 11:12:23 2011 +0700
Merge branch 'colresize' into aris
commit fcc2f82fa5bbdb3552cae6f15d60246a19442303
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sun Jun 12 11:11:03 2011 +0700
Change column reordering text
commit 78e7565cfd9779065e98ffc1bf8078d73d9a709d
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sun Jun 12 10:58:15 2011 +0700
Fix bug in column reordering tooltip
commit 83f7170304de06b1789e32105424bdf216bd3227
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sun Jun 12 10:51:53 2011 +0700
Preloading of column pointer arrow
commit 7c18b8106a8f8b72dbc2b7a2de8da11fec0f975c
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sun Jun 12 10:29:15 2011 +0700
Better jQuery animation for column reordering popup balloon
commit 5814064b81cff9c581a83e773ff7f78fd026eebc
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sun Jun 12 09:38:34 2011 +0700
Compress col_pointer images
commit 169686bccfd6466c631e38c40133227171fee3cb
Merge: 031e5c97714f62f39c15fe63cb512d1a6d4e67ce eb7c64dfad5203de7c51e87e096b1d677f2dc5ca
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sat Jun 11 10:23:11 2011 +0700
Merge branch 'colresize' into aris
commit eb7c64dfad5203de7c51e87e096b1d677f2dc5ca
Merge: b81ae2e6cc04f004192c127ae6955d11d4356191 54c062c0ad19db67250fde19eff7f44f31429e74
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sat Jun 11 10:19:10 2011 +0700
Merge remote-tracking branch 'origin/master' into colresize
commit b81ae2e6cc04f004192c127ae6955d11d4356191
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Sat Jun 11 10:17:58 2011 +0700
Add column dragging hint & some css prettifier
commit 031e5c97714f62f39c15fe63cb512d1a6d4e67ce
Merge: 0ea798b7940a8ffdb7d4aade42679d860f45e9a1 fcf671b382eb2c9956fe5d46abccc86fd2da314f
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 17:29:03 2011 +0700
Merge branch 'colresize' into aris
commit fcf671b382eb2c9956fe5d46abccc86fd2da314f
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 17:27:11 2011 +0700
Use __() for string in 'Restore column order' button, change javascript code style
commit 0ea798b7940a8ffdb7d4aade42679d860f45e9a1
Merge: d37b0fa24609cc4abd9c5c605179519be3c68e5f 07c067a83225dc5edab572b3f113915fdae3cff8
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 09:33:24 2011 +0700
Merge branch 'colresize' into aris
commit 07c067a83225dc5edab572b3f113915fdae3cff8
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 09:32:39 2011 +0700
Fix for 'Restore column order'
commit d37b0fa24609cc4abd9c5c605179519be3c68e5f
Merge: d7f8b537e32920b6822dd5cdc89a947ffa5d6079 3ba2aa446336c5bdf48eebf8e116830c2e350f40
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 08:48:41 2011 +0700
Merge branch 'colresize' into aris
commit 3ba2aa446336c5bdf48eebf8e116830c2e350f40
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 08:47:47 2011 +0700
Fix: remove system's drag and drop when dragging table header link
commit d7f8b537e32920b6822dd5cdc89a947ffa5d6079
Merge: 46e360de1e827c396a01c26fab0c25b79370d6dd 53d7bfa2b1f8261d7ea609ff1ec2d96a3a4c780a
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 08:06:29 2011 +0700
Merge branch 'colresize' into aris
commit 53d7bfa2b1f8261d7ea609ff1ec2d96a3a4c780a
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 10 08:05:37 2011 +0700
Change 'Restore table' button to 'Restore column order'
commit 46e360de1e827c396a01c26fab0c25b79370d6dd
Merge: 3ac62ac37f1cab119ef3579e3a8268805ccb8fe0 0b933d261484ceb095b47e8aa5ffcb71c362bd54
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 9 14:44:10 2011 +0700
Merge branch 'colresize' into aris
commit 0b933d261484ceb095b47e8aa5ffcb71c362bd54
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 9 14:43:34 2011 +0700
Fix more bug: Reordering not work correctly if table structure changed
commit 3ac62ac37f1cab119ef3579e3a8268805ccb8fe0
Merge: 403e2ed2bfeec79a4bfff4952e3a1499b8c269cd 803b4639bd4e036c246d40b545ff9eb573f1181a
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 9 13:04:57 2011 +0700
Merge branch 'colresize' into aris
commit 403e2ed2bfeec79a4bfff4952e3a1499b8c269cd
Merge: aad9a55b81635ec2c6e25b6f79e0f989508436f6 90099b7b5de240e5789ea8bfd29fcb03e867d825
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 9 13:04:53 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit 803b4639bd4e036c246d40b545ff9eb573f1181a
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 9 13:04:41 2011 +0700
Fix bug: Remember sorting and Column reordering not work correctly if table structure changed
commit aad9a55b81635ec2c6e25b6f79e0f989508436f6
Merge: 082345bdfa6b0661a3a6196269ead832e01cd862 d0e41349ee45d0d46b32a8fac0856470233097e1
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 8 13:59:42 2011 +0700
Merge branch 'colresize' into aris
commit 082345bdfa6b0661a3a6196269ead832e01cd862
Merge: c2b78975ab87a7848004c7b211ae8df2b843b92f 4333f4c55b40b94f2f8208d1751cf6c743317704
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 8 13:59:38 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit d0e41349ee45d0d46b32a8fac0856470233097e1
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 8 13:58:12 2011 +0700
Add disabled state for 'Restore table' button
commit c2b78975ab87a7848004c7b211ae8df2b843b92f
Merge: c991a19ceb32493ff0cb27a69a2843546d39ddba e9c055c5bc7a94636d7b6cd5801f2044bca86cca
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 7 18:27:01 2011 +0700
Merge branch 'colresize' into aris
commit c991a19ceb32493ff0cb27a69a2843546d39ddba
Merge: ff5544d1c7ecd8efec1a52931f4bb113780118f2 ca78ac3dc5fe7fe6c35f650430af2c192d666e10
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 7 18:26:18 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit e9c055c5bc7a94636d7b6cd5801f2044bca86cca
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 7 18:22:22 2011 +0700
Column reordering: remember the last column order
commit 5ed509bd89bd363e09b2c6593fc6ea4e6a03dab7
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 7 12:51:22 2011 +0700
Fix column resize bug: wrong resize when RepeatCells work
commit 3e3234b156a300e5517d1239755ff49270f24000
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 7 12:37:23 2011 +0700
Fix bug: Header not displayed correctly when RowActionLinks = Nowhere, RepeatCells = some value that enough to display repeated header
commit d64685c25afe282eabe73f23ba5ba2cbfcb541e0
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue Jun 7 12:32:14 2011 +0700
Add column reordering compatibility with RepeatCells configuration
commit ff5544d1c7ecd8efec1a52931f4bb113780118f2
Merge: 04fbe12ac7f6e3efe1b8f81b3d39fe8d11e4cc1f e16b77c0a5198b0f79175018e77909037826ae22
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 6 16:10:18 2011 +0700
Merge branch 'colresize' into aris
commit e16b77c0a5198b0f79175018e77909037826ae22
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 6 16:03:08 2011 +0700
Add different column pointer for vertical display
commit 04fbe12ac7f6e3efe1b8f81b3d39fe8d11e4cc1f
Merge: 1a66a483cf127c216fad038c9299520442e49b95 b0717ff8d5aa263b572746711acc50f7996ea87d
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 6 14:58:27 2011 +0700
Merge branch 'colresize' into aris
commit 1a66a483cf127c216fad038c9299520442e49b95
Merge: 0e73a2e1b3edae033a8a1ffd86b20a54237b070d 8b47a1dbd4a38fa12abe57f0eb0cbf74f1e433de
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 6 14:58:24 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit b0717ff8d5aa263b572746711acc50f7996ea87d
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 6 14:54:07 2011 +0700
Inline edit: fix compatibility with custom 'action column' (RowActionLinks) position
commit 86d787267ddca21ee3f478662d46aafd2c8736b4
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Mon Jun 6 12:20:41 2011 +0700
Fix column resizing and reordering compatibility with custom actions column position
commit 0e73a2e1b3edae033a8a1ffd86b20a54237b070d
Merge: 0cca021b23384898dd96839288176fb749047273 e1d0b67af6a178e7674b3377435fcb4df4ed1362
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 3 09:59:56 2011 +0700
Merge branch 'colresize' into aris
commit e1d0b67af6a178e7674b3377435fcb4df4ed1362
Merge: 13bcc44628d42991818bb8d0e1504f33a52f5721 d6b26b2d8f0cdd4b4bfb798e0b34f2974007fa85
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 3 09:59:35 2011 +0700
Fix merge conflict caused by new syntax highlighter
commit 13bcc44628d42991818bb8d0e1504f33a52f5721
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri Jun 3 09:47:25 2011 +0700
Fix bug in firefox 4 / ubuntu 11.04, column resize buggy for the second click on the same column
commit 0cca021b23384898dd96839288176fb749047273
Merge: ce359e7506533803b97f287d77abd0ef935e2841 dfc7171d6fbd2ef500b23232b8e2f9baaf3f3402
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 2 15:02:22 2011 +0700
Merge branch 'colresize' into aris
commit dfc7171d6fbd2ef500b23232b8e2f9baaf3f3402
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 2 15:00:16 2011 +0700
Fix column reordering's compatibility with IE8. No graying table anymore
commit ce359e7506533803b97f287d77abd0ef935e2841
Merge: 88c89b2329fd2474050a73cdb6b1c9b383c0e154 2e9b5a08d21ae7d97fbc0ec8923c45d5260854ec
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 2 12:15:30 2011 +0700
Merge branch 'colresize' into aris
commit 88c89b2329fd2474050a73cdb6b1c9b383c0e154
Merge: 8b8917ff747592e4ae72a5f7c7eda3f735102d57 d4892728458b6090a148f1f12dd41c4add336fb8
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 2 12:15:25 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit 2e9b5a08d21ae7d97fbc0ec8923c45d5260854ec
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Thu Jun 2 12:09:58 2011 +0700
Add column pointer when reordering and some fixes for Original theme
commit 8b8917ff747592e4ae72a5f7c7eda3f735102d57
Merge: dc81db3c76ba47e467a563b6fb4393589f9318e0 9fbbafdf7cbd5d06af0d37142724faa809799b51
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 23:40:04 2011 +0700
Merge branch 'colresize' into aris
commit 9fbbafdf7cbd5d06af0d37142724faa809799b51
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 23:39:15 2011 +0700
Disable noSelect after drag ended
commit dc81db3c76ba47e467a563b6fb4393589f9318e0
Merge: 6c532a3ba33cceb49f64d19a0f4e82e59a5d1ad4 1ab6f82e1dd3e7db2e86fd79b238297e5c56d6e6
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 22:28:12 2011 +0700
Merge branch 'colresize' into aris
commit 1ab6f82e1dd3e7db2e86fd79b238297e5c56d6e6
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 22:25:29 2011 +0700
Fix: restrict action column header to be moved
commit 6c532a3ba33cceb49f64d19a0f4e82e59a5d1ad4
Merge: 40c325c4442985ea86345015f852f774bea44f58 211d76b012cbcf6173de2fd75f4b5fe585e2eeb9
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 18:01:14 2011 +0700
Merge branch 'colresize' into aris
commit 40c325c4442985ea86345015f852f774bea44f58
Merge: 6bfeb440764226a7bbe8c8a2c3828809ca4b21d5 0dbc9f4a3c7fa46baed1477f001e37dad14fc908
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 18:01:00 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit 211d76b012cbcf6173de2fd75f4b5fe585e2eeb9
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 17:59:09 2011 +0700
Added new feature: column reordering. Some fixes in column resizing
commit 3cf69dfbb89b5476de32828a636dd3e27e912a50
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 13:11:53 2011 +0700
Some optimization for column resize script
commit 3b7f35d5c9f2c25c4fe5d7322fbe79c181637764
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 12:45:55 2011 +0700
Add makegrid triggering in other files
commit 6bfeb440764226a7bbe8c8a2c3828809ca4b21d5
Merge: 9ac5173788e174c76865394257c535cf69ecf4b6 d174e8b41c6df7b8e38900e2fc4d8560b1501bd5
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 10:40:31 2011 +0700
Merge branch 'colresize' into aris
commit d174e8b41c6df7b8e38900e2fc4d8560b1501bd5
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 10:39:45 2011 +0700
Use of .find for code consistency
commit 9ac5173788e174c76865394257c535cf69ecf4b6
Merge: 67d8f469d24580ae8ad623ad2a175ae017acf193 c4c57b86c32647c1a1dcb0b9125d35fbced18aac
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 10:15:57 2011 +0700
Merge colresize branch into aris
commit 67d8f469d24580ae8ad623ad2a175ae017acf193
Merge: 80981154486608cc154d6ab461847e999336566e 575f265293ffe0625da6a8fc0877a84d595d5322
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Wed Jun 1 09:47:06 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit c4c57b86c32647c1a1dcb0b9125d35fbced18aac
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 11:54:04 2011 +0700
Column resize: fix compatibility with inline edit
commit 80981154486608cc154d6ab461847e999336566e
Merge: 159696ba69d85fc59e4f82a343c3ad7a45f1636f 6b69e631d72bf08eb32e36c52d99d87db6cf4912
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 13:13:27 2011 +0700
Merge branch 'colresize' into aris
commit 159696ba69d85fc59e4f82a343c3ad7a45f1636f
Merge: 36680cff27ab43da1051b3877f9134b06ad2873f b5c1ee65092ebf67537931dfc1e396acfb1b10c2
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 13:13:23 2011 +0700
Merge remote-tracking branch 'origin/master' into aris
commit 6b69e631d72bf08eb32e36c52d99d87db6cf4912
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 11:54:04 2011 +0700
Column resize: fix compatibility with inline edit
commit f4b47d4cf0a846f394570673429a9759eb5f4991
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 10:50:56 2011 +0700
Support column resizing for vertical display mode
commit 566851d210104cb44ec536dc195b05707e0496c8
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 09:46:25 2011 +0700
Add trigger to remake the resizable column when an ajax request is done
commit 25ae3e2387ca8936434d427fb5eb1f2d0a4f6d13
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Tue May 31 09:23:37 2011 +0700
Add checking to first column (actions) span for resizing column
commit 36680cff27ab43da1051b3877f9134b06ad2873f
Merge: 518638fa87b73c9fb02d341f47a7a01ddb5e4f5b 9ae87854ed6474e111a4d77a0d747215e1cff9f6
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri May 27 19:09:38 2011 +0700
Merge branch 'colresize' into aris
commit 518638fa87b73c9fb02d341f47a7a01ddb5e4f5b
Merge: c40fed887e27558aa0d07ca10ce69534c4636813 e4ba1dc06605fd81718d1991ad509d8e47f159fc
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri May 27 19:09:35 2011 +0700
Merge branch 'tablesorting' into aris
commit c40fed887e27558aa0d07ca10ce69534c4636813
Merge: a55873c9871bfb05bac8a3f42b6c30e23c88677d ad6e109e0ee20b61fc5b1107856387cd09aeb2d0
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri May 27 19:09:30 2011 +0700
Merge branch 'recent' into aris
commit ad6e109e0ee20b61fc5b1107856387cd09aeb2d0
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri May 27 19:03:47 2011 +0700
Fix RecentTable class -> PMA_RecentTable, some fix on documentation
commit 9ae87854ed6474e111a4d77a0d747215e1cff9f6
Author: Aris Feryanto <aris_feryanto(a)yahoo.com>
Date: Fri May 27 14:05:45 2011 +0700
Add ability to resize column when browsing tables
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 2 +
Documentation.html | 2 +-
db_search.php | 2 +
db_sql.php | 1 +
js/db_search.js | 10 +-
js/makegrid.js | 617 +++++++++++++++++++++++++++++++
js/sql.js | 121 +++++--
js/tbl_select.js | 1 +
libraries/RecentTable.class.php | 10 +-
libraries/Table.class.php | 118 +++++-
libraries/common.lib.php | 16 +
libraries/display_tbl.lib.php | 91 +++++-
libraries/header.inc.php | 2 +-
navigation.php | 4 +-
server_sql.php | 1 +
sql.php | 23 +-
tbl_replace.php | 1 +
tbl_select.php | 1 +
tbl_sql.php | 1 +
themes/original/css/theme_right.css.php | 65 ++++
themes/original/img/col_pointer.png | Bin 0 -> 121 bytes
themes/original/img/col_pointer_ver.png | Bin 0 -> 128 bytes
themes/pmahomme/css/theme_right.css.php | 69 ++++
themes/pmahomme/img/col_pointer.png | Bin 0 -> 136 bytes
themes/pmahomme/img/col_pointer_ver.png | Bin 0 -> 138 bytes
25 files changed, 1082 insertions(+), 76 deletions(-)
create mode 100644 js/makegrid.js
create mode 100644 themes/original/img/col_pointer.png
create mode 100644 themes/original/img/col_pointer_ver.png
create mode 100644 themes/pmahomme/img/col_pointer.png
create mode 100644 themes/pmahomme/img/col_pointer_ver.png
diff --git a/ChangeLog b/ChangeLog
index 49c7305..daa1a62 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,8 @@
+ Patch #3271804 for rfe #3177495, new DisableMultiTableMaintenance directive
+ [interface] Reorganised server status page.
+ [interface] Changed way of generating charts.
++ rfe #939233 [interface] Flexible column width
++ [interface] Mouse-based column reordering in query results
3.4.3.0 (not yet released)
- bug #3311170 [sync] Missing helper icons in Synchronize
diff --git a/Documentation.html b/Documentation.html
index 4db4ec0..d2dea87 100644
--- a/Documentation.html
+++ b/Documentation.html
@@ -1073,7 +1073,7 @@ ALTER TABLE `pma_column_comments`
Without configuring the storage, you can still access the recently used tables,
but it will disappear after you logout.<br/><br/>
- To allow the usage of this functionality:
+ To allow the usage of this functionality persistently:
<ul>
<li>set up <a href="#pmadb">pmadb</a> and the phpMyAdmin configuration storage</li>
diff --git a/db_search.php b/db_search.php
index d93fd74..a91bf87 100644
--- a/db_search.php
+++ b/db_search.php
@@ -38,6 +38,8 @@
require_once './libraries/common.inc.php';
$GLOBALS['js_include'][] = 'db_search.js';
+$GLOBALS['js_include'][] = 'sql.js';
+$GLOBALS['js_include'][] = 'makegrid.js';
/**
* Gets some core libraries and send headers
diff --git a/db_sql.php b/db_sql.php
index 08eb5ca..26a6299 100644
--- a/db_sql.php
+++ b/db_sql.php
@@ -14,6 +14,7 @@ require_once './libraries/common.inc.php';
* Runs common work
*/
$GLOBALS['js_include'][] = 'functions.js';
+$GLOBALS['js_include'][] = 'makegrid.js';
$GLOBALS['js_include'][] = 'sql.js';
require './libraries/db_common.inc.php';
diff --git a/js/db_search.js b/js/db_search.js
index 6fff82d..0cb04b7 100644
--- a/js/db_search.js
+++ b/js/db_search.js
@@ -26,7 +26,14 @@ function loadResult(result_path , table_name , link , ajaxEnable){
/** Load the browse results to the page */
$("#table-info").show();
$('#table-link').attr({"href" : 'sql.php?'+link }).text(table_name);
- $('#browse-results').load(result_path + " '"+'#sqlqueryresults' + "'").show();
+ $('#browse-results').load(result_path + " '"+'#sqlqueryresults' + "'", null, function() {
+ // because under db_search, window.parent.table is not defined yet,
+ // we assign it manually from #table-link
+ window.parent.table = $('#table-link').text().trim();
+
+ appendInlineAnchor();
+ $('#table_results').makegrid();
+ }).show();
}
else
{
@@ -173,7 +180,6 @@ $(document).ready(function() {
if (typeof response == 'string') {
// found results
$("#searchresults").html(response);
- $("#sqlqueryresults").trigger('appendAnchor');
$('#togglesearchresultlink')
// always start with the Show message
diff --git a/js/makegrid.js b/js/makegrid.js
new file mode 100644
index 0000000..038300b
--- /dev/null
+++ b/js/makegrid.js
@@ -0,0 +1,617 @@
+(function ($) {
+ $.grid = function(t) {
+ // prepare the grid
+ var g = {
+ // constant
+ minColWidth: 5,
+
+ // variables, assigned with default value, changed later
+ alignment: 'horizontal', // 3 possibilities: vertical, horizontal, horizontalflipped
+ actionSpan: 5,
+ colOrder: new Array(), // array of column order
+ tableCreateTime: null, // table creation time, only available in "Browse tab"
+ hintShown: false, // true if hint balloon is shown, used by updateHint() method
+ reorderHint: '', // string, hint for column reordering
+ sortHint: '', // string, hint for column sorting
+ markHint: '', // string, hint for column marking
+ showReorderHint: false, // boolean, used by showHint() method
+ showSortHint: false, // boolean, used by showHint() method
+ showMarkHint: false,
+ hintIsHiding: false, // true when hint is still shown, but hide() already called
+
+ // functions
+ dragStartRsz: function(e, obj) { // start column resize
+ var n = $(this.cRsz).find('div').index(obj);
+ this.colRsz = {
+ x0: e.pageX,
+ n: n,
+ obj: obj,
+ objLeft: $(obj).position().left,
+ objWidth: this.alignment != 'vertical' ?
+ $(this.t).find('th.draggable:eq(' + n + ') span').outerWidth() :
+ $(this.t).find('tr:first td:eq(' + n + ') span').outerWidth()
+ };
+ $('body').css('cursor', 'col-resize');
+ $('body').noSelect();
+ },
+
+ dragStartMove: function(e, obj) { // start column move
+ // prepare the cCpy and cPointer from the dragged column
+ $(this.cCpy).text($(obj).text());
+ var objPos = $(obj).position();
+ if (this.alignment != 'vertical') {
+ $(this.cCpy).css({
+ top: objPos.top + 20,
+ left: objPos.left,
+ height: $(obj).height(),
+ width: $(obj).width()
+ });
+ $(this.cPointer).css({
+ top: objPos.top
+ });
+ } else { // vertical alignment
+ $(this.cCpy).css({
+ top: objPos.top,
+ left: objPos.left + 30,
+ height: $(obj).height(),
+ width: $(obj).width()
+ });
+ $(this.cPointer).css({
+ top: objPos.top
+ });
+ }
+
+ // get the column index, zero-based
+ var n = this.getHeaderIdx(obj);
+
+ this.colMov = {
+ x0: e.pageX,
+ y0: e.pageY,
+ n: n,
+ newn: n,
+ obj: obj,
+ objTop: objPos.top,
+ objLeft: objPos.left
+ };
+ $('body').css('cursor', 'move');
+ this.hideHint();
+ $('body').noSelect();
+ },
+
+ dragMove: function(e) {
+ if (this.colRsz) {
+ var dx = e.pageX - this.colRsz.x0;
+ if (this.colRsz.objWidth + dx > this.minColWidth)
+ $(this.colRsz.obj).css('left', this.colRsz.objLeft + dx + 'px');
+ } else if (this.colMov) {
+ // dragged column animation
+ if (this.alignment != 'vertical') {
+ var dx = e.pageX - this.colMov.x0;
+ $(this.cCpy)
+ .css('left', this.colMov.objLeft + dx)
+ .show();
+ } else { // vertical alignment
+ var dy = e.pageY - this.colMov.y0;
+ $(this.cCpy)
+ .css('top', this.colMov.objTop + dy)
+ .show();
+ }
+
+ // pointer animation
+ var hoveredCol = this.getHoveredCol(e);
+ if (hoveredCol) {
+ var newn = this.getHeaderIdx(hoveredCol);
+ this.colMov.newn = newn;
+ if (newn != this.colMov.n) {
+ // show the column pointer in the right place
+ var colPos = $(hoveredCol).position();
+ if (this.alignment != 'vertical') {
+ var newleft = newn < this.colMov.n ?
+ colPos.left :
+ colPos.left + $(hoveredCol).outerWidth();
+ $(this.cPointer)
+ .css({
+ left: newleft,
+ visibility: 'visible'
+ });
+ } else { // vertical alignment
+ var newtop = newn < this.colMov.n ?
+ colPos.top :
+ colPos.top + $(hoveredCol).outerHeight();
+ $(this.cPointer)
+ .css({
+ top: newtop,
+ visibility: 'visible'
+ });
+ }
+ } else {
+ // no movement to other column, hide the column pointer
+ $(this.cPointer).css('visibility', 'hidden');
+ }
+ }
+ }
+ },
+
+ dragEnd: function(e) {
+ if (this.colRsz) {
+ var dx = e.pageX - this.colRsz.x0;
+ var nw = this.colRsz.objWidth + dx;
+ if (nw < this.minColWidth) {
+ nw = this.minColWidth;
+ }
+ var n = this.colRsz.n;
+ // do the resizing
+ if (this.alignment != 'vertical') {
+ $(this.t).find('tr').each(function() {
+ $(this).find('th.draggable:eq(' + n + ') span,' +
+ 'td:eq(' + (g.actionSpan + n) + ') span')
+ .css('width', nw);
+ });
+ } else { // vertical alignment
+ $(this.t).find('tr').each(function() {
+ $(this).find('td:eq(' + n + ') span')
+ .css('width', nw);
+ });
+ }
+ $('body').css('cursor', 'default');
+ this.reposRsz();
+ this.colRsz = false;
+ } else if (this.colMov) {
+ // shift columns
+ if (this.colMov.newn != this.colMov.n) {
+ this.shiftCol(this.colMov.n, this.colMov.newn);
+ // assign new position
+ var objPos = $(this.colMov.obj).position();
+ this.colMov.objTop = objPos.top;
+ this.colMov.objLeft = objPos.left;
+ this.colMov.n = this.colMov.newn;
+ // send request to server to remember the column order
+ if (this.tableCreateTime) {
+ this.sendColOrder();
+ }
+ this.refreshRestoreButton();
+ }
+
+ // animate new column position
+ $(this.cCpy).stop(true, true)
+ .animate({
+ top: g.colMov.objTop,
+ left: g.colMov.objLeft
+ }, 'fast')
+ .fadeOut();
+ $(this.cPointer).css('visibility', 'hidden');
+
+ this.colMov = false;
+ }
+ $('body').css('cursor', 'default');
+ $('body').noSelect(false);
+ },
+
+ /**
+ * Reposition column resize bars.
+ */
+ reposRsz: function() {
+ $(this.cRsz).find('div').hide();
+ $firstRowCols = this.alignment != 'vertical' ?
+ $(this.t).find('tr:first th.draggable') :
+ $(this.t).find('tr:first td');
+ for (var n = 0; n < $firstRowCols.length; n++) {
+ $this = $($firstRowCols[n]);
+ $cb = $(g.cRsz).find('div:eq(' + n + ')'); // column border
+ var pad = parseInt($this.css('padding-right'));
+ $cb.css('left', Math.floor($this.position().left + $this.width() + pad))
+ .show();
+ }
+ },
+
+ /**
+ * Shift column from index oldn to newn.
+ */
+ shiftCol: function(oldn, newn) {
+ if (this.alignment != 'vertical') {
+ $(this.t).find('tr').each(function() {
+ if (newn < oldn) {
+ $(this).find('th.draggable:eq(' + newn + '),' +
+ 'td:eq(' + (g.actionSpan + newn) + ')')
+ .before($(this).find('th.draggable:eq(' + oldn + '),' +
+ 'td:eq(' + (g.actionSpan + oldn) + ')'));
+ } else {
+ $(this).find('th.draggable:eq(' + newn + '),' +
+ 'td:eq(' + (g.actionSpan + newn) + ')')
+ .after($(this).find('th.draggable:eq(' + oldn + '),' +
+ 'td:eq(' + (g.actionSpan + oldn) + ')'));
+ }
+ });
+ // reposition the column resize bars
+ this.reposRsz();
+
+ } else { // vertical alignment
+ // shift rows
+ if (newn < oldn) {
+ $(this.t).find('tr:eq(' + (g.actionSpan + newn) + ')')
+ .before($(this.t).find('tr:eq(' + (g.actionSpan + oldn) + ')'));
+ } else {
+ $(this.t).find('tr:eq(' + (g.actionSpan + newn) + ')')
+ .after($(this.t).find('tr:eq(' + (g.actionSpan + oldn) + ')'));
+ }
+ }
+ // adjust the colOrder
+ var tmp = this.colOrder[oldn];
+ this.colOrder.splice(oldn, 1);
+ this.colOrder.splice(newn, 0, tmp);
+ },
+
+ /**
+ * Find currently hovered table column's header (excluding actions column).
+ * @return the hovered column's th object or undefined if no hovered column found.
+ */
+ getHoveredCol: function(e) {
+ var hoveredCol;
+ $headers = $(this.t).find('th.draggable');
+ if (this.alignment != 'vertical') {
+ $headers.each(function() {
+ var left = $(this).position().left;
+ var right = left + $(this).outerWidth();
+ if (left <= e.pageX && e.pageX <= right) {
+ hoveredCol = this;
+ }
+ });
+ } else { // vertical alignment
+ $headers.each(function() {
+ var top = $(this).position().top;
+ var bottom = top + $(this).height();
+ if (top <= e.pageY && e.pageY <= bottom) {
+ hoveredCol = this;
+ }
+ });
+ }
+ return hoveredCol;
+ },
+
+ /**
+ * Get a zero-based index from a <th class="draggable"> tag in a table.
+ */
+ getHeaderIdx: function(obj) {
+ var n;
+ if (this.alignment != 'vertical') {
+ n = $(obj).parents('tr').find('th.draggable').index(obj);
+ } else {
+ var column_idx = $(obj).index();
+ var $th_in_same_column = $(this.t).find('th.draggable:nth-child(' + (column_idx + 1) + ')');
+ n = $th_in_same_column.index(obj);
+ }
+ return n;
+ },
+
+ /**
+ * Reposition the table back to normal order.
+ */
+ restore: function() {
+ // use insertion sort, since we already have shiftCol function
+ for (var i = 1; i < this.colOrder.length; i++) {
+ var x = this.colOrder[i];
+ var j = i - 1;
+ while (j >= 0 && x < this.colOrder[j]) {
+ j--;
+ }
+ if (j != i - 1) {
+ this.shiftCol(i, j + 1);
+ }
+ }
+ if (this.tableCreateTime) {
+ // send request to server to remember the column order
+ this.sendColOrder();
+ }
+ this.refreshRestoreButton();
+ },
+
+ /**
+ * Send column order to the server.
+ */
+ sendColOrder: function() {
+ $.post('sql.php', {
+ ajax_request: true,
+ db: window.parent.db,
+ table: window.parent.table,
+ token: window.parent.token,
+ set_col_order: true,
+ col_order: this.colOrder.toString(),
+ table_create_time: this.tableCreateTime
+ });
+ },
+
+ /**
+ * Refresh restore button state.
+ * Make restore button disabled if the table is similar with initial state.
+ */
+ refreshRestoreButton: function() {
+ // check if table state is as initial state
+ var isInitial = true;
+ for (var i = 0; i < this.colOrder.length; i++) {
+ if (this.colOrder[i] != i) {
+ isInitial = false;
+ break;
+ }
+ }
+ // enable or disable restore button
+ if (isInitial) {
+ $('.restore_column').hide();
+ } else {
+ $('.restore_column').show();
+ }
+ },
+
+ /**
+ * Show hint with the text supplied.
+ */
+ showHint: function(e) {
+ if (!this.colRsz && !this.colMov) { // if not resizing or dragging
+ var text = '';
+ if (this.showReorderHint) {
+ text += this.reorderHint;
+ }
+ if (this.showSortHint) {
+ text += text.length > 0 ? '<br />' : '';
+ text += this.sortHint;
+ }
+ if (this.showMarkHint) {
+ text += text.length > 0 ? '<br />' : '';
+ text += this.markHint;
+ }
+
+ // hide the hint if no text
+ if (!text) {
+ this.hideHint();
+ return;
+ }
+
+ $(this.dHint).html(text);
+ if (!this.hintShown || this.hintIsHiding) {
+ $(this.dHint)
+ .stop(true, true)
+ .css({
+ top: e.pageY,
+ left: e.pageX + 15
+ })
+ .show('fast');
+ this.hintShown = true;
+ this.hintIsHiding = false;
+ }
+ }
+ },
+
+ /**
+ * Hide the hint.
+ */
+ hideHint: function() {
+ if (this.hintShown) {
+ $(this.dHint)
+ .stop(true, true)
+ .hide(300, function() {
+ g.hintShown = false;
+ g.hintIsHiding = false;
+ });
+ this.hintIsHiding = true;
+ }
+ },
+
+ /**
+ * Update hint position.
+ */
+ updateHint: function(e) {
+ if (this.hintShown) {
+ $(this.dHint).css({
+ top: e.pageY,
+ left: e.pageX + 15
+ });
+ }
+ }
+ }
+
+ g.gDiv = document.createElement('div'); // create global div
+ g.cRsz = document.createElement('div'); // column resizer
+ g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header
+ g.cPointer = document.createElement('div'); // column pointer, used when reordering column
+ g.dHint = document.createElement('div'); // draggable hint
+
+ // assign the table alignment
+ g.alignment = $("#top_direction_dropdown").val();
+
+ // adjust g.cCpy
+ g.cCpy.className = 'cCpy';
+ $(g.cCpy).hide();
+
+ // adjust g.cPoint
+ g.cPointer.className = g.alignment != 'vertical' ? 'cPointer' : 'cPointerVer';
+ $(g.cPointer).css('visibility', 'hidden');
+
+ // adjust g.dHint
+ g.dHint.className = 'dHint';
+ $(g.dHint).hide();
+
+ // chain table and grid together
+ t.grid = g;
+ g.t = t;
+
+ // get first row data columns
+ var $firstRowCols = g.alignment != 'vertical' ?
+ $(t).find('tr:first th.draggable') :
+ $(t).find('tr:first td');
+
+ // assign first column (actions) span
+ if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist
+ g.actionSpan = g.alignment != 'vertical' ?
+ $(t).find('tr:first th:first').prop('colspan') :
+ $(t).find('tr:first th:first').prop('rowspan');
+ } else {
+ g.actionSpan = 0;
+ }
+
+ // assign table create time
+ // #table_create_time will only available if we are in "Browse" tab
+ g.tableCreateTime = $('#table_create_time').val();
+
+ // assign column reorder & column sort hint
+ g.reorderHint = $('#col_order_hint').val();
+ g.sortHint = $('#sort_hint').val();
+ g.markHint = $('#col_mark_hint').val();
+
+ // determine whether to show the column reordering hint or not
+ g.showReorderHint = $firstRowCols.length > 1;
+
+ // initialize column order
+ $col_order = $('#col_order');
+ if ($col_order.length > 0) {
+ g.colOrder = $col_order.val().split(',');
+ for (var i = 0; i < g.colOrder.length; i++) {
+ g.colOrder[i] = parseInt(g.colOrder[i]);
+ }
+ } else {
+ g.colOrder = new Array();
+ for (var i = 0; i < $firstRowCols.length; i++) {
+ g.colOrder.push(i);
+ }
+ }
+
+ // create column borders
+ $firstRowCols.each(function() {
+ $this = $(this);
+ var cb = document.createElement('div'); // column border
+ var pad = parseInt($this.css('padding-right'));
+ $(cb).css('left', Math.floor($this.position().left + $this.width() + pad));
+ $(cb).addClass('colborder');
+ $(cb).mousedown(function(e) {
+ g.dragStartRsz(e, this);
+ });
+ $(g.cRsz).append(cb);
+ });
+
+ // wrap all data cells, except actions cell, with span
+ $(t).find('th, td:not(:has(span))')
+ .wrapInner('<span />');
+
+ // register events
+ if ($firstRowCols.length > 1) {
+ $(t).find('th.draggable')
+ .css('cursor', 'move')
+ .mousedown(function(e) {
+ g.dragStartMove(e, this);
+ });
+ }
+ $(t).find('th.draggable')
+ .mouseenter(function(e) {
+ g.showMarkHint = !g.showSortHint;
+ g.showHint(e);
+ })
+ .mouseleave(function(e) {
+ g.hideHint();
+ });
+ $(t).find('th.draggable a')
+ .attr('title', '') // hide default tooltip for sorting
+ .mouseenter(function(e) {
+ g.showSortHint = true;
+ g.showMarkHint = false;
+ g.showHint(e);
+ })
+ .mouseleave(function(e) {
+ g.showSortHint = false;
+ g.showMarkHint = true;
+ g.showHint(e);
+ });
+ $(document).mousemove(function(e) {
+ g.dragMove(e);
+ g.updateHint(e);
+ });
+ $(document).mouseup(function(e) {
+ g.dragEnd(e);
+ });
+ $('.restore_column').click(function() {
+ g.restore();
+ });
+
+ // add table class
+ $(t).addClass('pma_table');
+
+ // link all divs
+ $(t).before(g.gDiv);
+ $(g.gDiv).append(t);
+ $(g.gDiv).prepend(g.cRsz);
+ $(g.gDiv).append(g.cCpy);
+ $(g.gDiv).append(g.cPointer);
+ $(g.gDiv).append(g.dHint);
+
+ // some adjustment
+ g.refreshRestoreButton();
+ g.cRsz.className = 'cRsz';
+ $(t).removeClass('data');
+ $(g.gDiv).addClass('data');
+ $(g.cRsz).css('height', $(t).height());
+ $(t).find('th a').bind('dragstart', function() {
+ return false;
+ });
+ };
+
+ // document ready checking
+ var docready = false;
+ $(document).ready(function() {
+ docready = true;
+ });
+
+ // Additional jQuery functions
+ /**
+ * Make resizable, reorderable grid.
+ */
+ $.fn.makegrid = function() {
+ return this.each(function() {
+ if (!docready) {
+ var t = this;
+ $(document).ready(function() {
+ $.grid(t);
+ });
+ } else {
+ $.grid(this);
+ }
+ });
+ };
+ /**
+ * Refresh grid. This must be called after changing the grid's content.
+ */
+ $.fn.refreshgrid = function() {
+ return this.each(function() {
+ if (!docready) {
+ var t = this;
+ $(document).ready(function() {
+ if (t.grid) t.grid.reposRsz();
+ });
+ } else {
+ if (this.grid) this.grid.reposRsz();
+ }
+ });
+ }
+ $.fn.noSelect = function (p) { //no select plugin by Paulo P.Marinas
+ var prevent = (p == null) ? true : p;
+ if (prevent) {
+ return this.each(function () {
+ if ($.browser.msie || $.browser.safari) $(this).bind('selectstart', function () {
+ return false;
+ });
+ else if ($.browser.mozilla) {
+ $(this).css('MozUserSelect', 'none');
+ $('body').trigger('focus');
+ } else if ($.browser.opera) $(this).bind('mousedown', function () {
+ return false;
+ });
+ else $(this).attr('unselectable', 'on');
+ });
+ } else {
+ return this.each(function () {
+ if ($.browser.msie || $.browser.safari) $(this).unbind('selectstart');
+ else if ($.browser.mozilla) $(this).css('MozUserSelect', 'inherit');
+ else if ($.browser.opera) $(this).unbind('mousedown');
+ else $(this).removeAttr('unselectable', 'on');
+ });
+ }
+ }; //end noSelect
+
+})(jQuery);
+
diff --git a/js/sql.js b/js/sql.js
index 65d209a..dfe05da 100644
--- a/js/sql.js
+++ b/js/sql.js
@@ -42,12 +42,13 @@ function getFieldName($this_field, disp_mode) {
else {
var this_field_index = $this_field.index();
// ltr or rtl direction does not impact how the DOM was generated
- //
+ // check if the action column in the left exist
+ var leftActionExist = !$('#table_results').find('th:first').hasClass('draggable');
// 5 columns to account for the checkbox, edit, appended inline edit, copy and delete anchors but index is zero-based so substract 4
- var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+') a').text();
+ var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index - (leftActionExist ? 4 : 0)) + ') a').text();
// happens when just one row (headings contain no a)
if ("" == field_name) {
- field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+')').text();
+ field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index - (leftActionExist ? 4 : 0)) + ')').text();
}
}
@@ -211,6 +212,24 @@ $(document).ready(function() {
$("#sqlqueryresults").live('appendAnchor',function() {
appendInlineAnchor();
})
+
+ /**
+ * Attach the {@link makegrid} function to a custom event, which will be
+ * triggered manually everytime the table of results is reloaded
+ * @memberOf jQuery
+ */
+ $("#sqlqueryresults").live('makegrid', function() {
+ $('#table_results').makegrid();
+ })
+
+ /**
+ * Attach the {@link refreshgrid} function to a custom event, which will be
+ * triggered manually everytime the table of results is manipulated (e.g., by inline edit)
+ * @memberOf jQuery
+ */
+ $("#sqlqueryresults").live('refreshgrid', function() {
+ $('#table_results').refreshgrid();
+ })
/**
* Trigger the appendAnchor event to prepare the first table for inline edit
@@ -324,6 +343,7 @@ $(document).ready(function() {
$('#sqlqueryresults').show();
$("#sqlqueryresults").html(data);
$("#sqlqueryresults").trigger('appendAnchor');
+ $("#sqlqueryresults").trigger('makegrid');
$('#togglequerybox').show();
if($("#togglequerybox").siblings(":visible").length > 0) {
$("#togglequerybox").trigger('click');
@@ -364,6 +384,7 @@ $(document).ready(function() {
$.post($the_form.attr('action'), $the_form.serialize(), function(data) {
$("#sqlqueryresults").html(data);
$("#sqlqueryresults").trigger('appendAnchor');
+ $("#sqlqueryresults").trigger('makegrid');
PMA_init_slider();
PMA_ajaxRemoveMessage($msgbox);
@@ -387,6 +408,7 @@ $(document).ready(function() {
$.post($the_form.attr('action'), $the_form.serialize() + '&ajax_request=true', function(data) {
$("#sqlqueryresults").html(data);
$("#sqlqueryresults").trigger('appendAnchor');
+ $("#sqlqueryresults").trigger('makegrid');
PMA_init_slider();
PMA_ajaxRemoveMessage($msgbox);
}) // end $.post()
@@ -412,7 +434,8 @@ $(document).ready(function() {
$.get($anchor.attr('href'), $anchor.serialize() + '&ajax_request=true', function(data) {
$("#sqlqueryresults")
.html(data)
- .trigger('appendAnchor');
+ .trigger('appendAnchor')
+ .trigger('makegrid');
PMA_ajaxRemoveMessage($msgbox);
}) // end $.get()
})//end Sort results table
@@ -431,7 +454,8 @@ $(document).ready(function() {
$.post($form.attr('action'), $form.serialize() + '&ajax_request=true' , function(data) {
$("#sqlqueryresults")
.html(data)
- .trigger('appendAnchor');
+ .trigger('appendAnchor')
+ .trigger('makegrid');
PMA_init_slider();
}) // end $.post()
})
@@ -517,21 +541,22 @@ $(document).ready(function() {
$this_hide.parent().removeClass("hover noclick");
$this_hide.siblings().removeClass("hover");
- var last_column = $this_hide.siblings().length;
+ var $input_siblings = $this_hide.parent('tr').find('.inline_edit');
var txt = '';
- for(var i = 4; i < last_column; i++) {
- if($this_hide.siblings("td:eq(" + i + ")").hasClass("inline_edit") == false) {
- continue;
+ $input_siblings.each(function() {
+ var $this_hide_siblings = $(this);
+ txt = $this_hide_siblings.data('original_data');
+ if($this_hide_siblings.children('span').children().length != 0) {
+ $this_hide_siblings.children('span').empty();
+ $this_hide_siblings.children('span').append(txt);
}
- txt = $this_hide.siblings("td:eq(" + i + ")").data('original_data');
- if($this_hide.siblings("td:eq(" + i + ")").children().length != 0) {
- $this_hide.siblings("td:eq(" + i + ")").empty();
- $this_hide.siblings("td:eq(" + i + ")").append(txt);
- }
- }
+ });
$(this).prev().prev().remove();
$(this).prev().remove();
$(this).remove();
+
+ // refresh the grid
+ $("#sqlqueryresults").trigger('refreshgrid');
});
} else {
var txt = '';
@@ -539,7 +564,8 @@ $(document).ready(function() {
$('#table_results tbody tr td span a#hide').click(function() {
var $hide_a = $(this);
- var pos = $hide_a.parents('td').index();
+ var $this_hide = $(this).parents('td');
+ var pos = $this_hide.index();
var $this_span = $hide_a.parent();
$this_span.find('a, br').remove();
@@ -547,22 +573,26 @@ $(document).ready(function() {
var $this_row = $this_span.parents('tr');
// changing inline_edit_active to inline_edit_anchor
- $this_row.siblings("tr:eq(3) td:eq(" + pos + ")").removeClass("inline_edit_active").addClass("inline_edit_anchor");
+ $this_hide.removeClass("inline_edit_active").addClass("inline_edit_anchor");
// removing marked and hover classes.
$this_row.parent('tbody').find('tr').find("td:eq(" + pos + ")").removeClass("marked hover");
- for( var i = 6; i <= rows + 2; i++){
+ for( var i = 0; i <= rows + 2; i++){
if( $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").hasClass("inline_edit") == false) {
continue;
}
- txt = $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").data('original_data');
- $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").empty();
- $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").append(txt);
+ $this_row_siblings = $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")");
+ txt = $this_row_siblings.data('original_data');
+ $this_row_siblings.children('span').empty();
+ $this_row_siblings.children('span').append(txt);
}
$(this).prev().remove();
$(this).prev().remove();
$(this).remove();
+
+ // refresh the grid
+ $("#sqlqueryresults").trigger('refreshgrid');
});
}
@@ -594,7 +624,7 @@ $(document).ready(function() {
/**
* @var data_value Current value of this field
*/
- var data_value = $(this).html();
+ var data_value = $(this).children('span').html();
// We need to retrieve the value from the server for truncated/relation fields
// Find the field name
@@ -604,6 +634,10 @@ $(document).ready(function() {
*/
var $this_field = $(this);
/**
+ * @var this_field_span Object referring to this field's child (<span>)
+ */
+ var $this_field_span = $(this).children('span');
+ /**
* @var field_name String containing the name of this field.
* @see getFieldName()
*/
@@ -620,11 +654,11 @@ $(document).ready(function() {
/**
* @var curr_value String current value of the field (for fields that are of type enum or set).
*/
- var curr_value = $this_field.text();
+ var curr_value = $this_field_span.text();
if($this_field.is(':not(.not_null)')){
// add a checkbox to mark null for all the field that are nullable.
- $this_field.html('<div class="null_div">Null :<input type="checkbox" class="checkbox_null_'+ field_name + '_' + this_row_index +'"></div>');
+ $this_field_span.html('<div class="null_div">Null :<input type="checkbox" class="checkbox_null_'+ field_name + '_' + this_row_index +'"></div>');
// check the 'checkbox_null_<field_name>_<row_index>' if the corresponding value is null
if($this_field.is('.null')) {
$('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', true);
@@ -671,7 +705,7 @@ $(document).ready(function() {
})
} else {
- $this_field.html('<div class="null_div"></div>');
+ $this_field_span.html('<div class="null_div"></div>');
}
// In each input sibling, wrap the current value in a textarea
@@ -679,7 +713,7 @@ $(document).ready(function() {
if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .set, .null)')) {
// handle non-truncated, non-transformed, non-relation values
// We don't need to get any more data, just wrap the value
- $this_field.append('<textarea>'+data_value+'</textarea>');
+ $this_field_span.append('<textarea>'+data_value+'</textarea>');
$this_field.data('original_data', data_value);
}
else if($this_field.is('.truncated, .transformed')) {
@@ -700,8 +734,9 @@ $(document).ready(function() {
'inline_edit' : true
}, function(data) {
if(data.success == true) {
- $this_field.append('<textarea>'+data.value+'</textarea>');
+ $this_field_span.append('<textarea>'+data.value+'</textarea>');
$this_field.data('original_data', data_value);
+ $("#sqlqueryresults").trigger('refreshgrid');
}
else {
PMA_ajaxShowMessage(data.error);
@@ -727,8 +762,9 @@ $(document).ready(function() {
}
$.post('sql.php', post_params, function(data) {
- $this_field.append(data.dropdown);
+ $this_field_span.append(data.dropdown);
$this_field.data('original_data', data_value);
+ $("#sqlqueryresults").trigger('refreshgrid');
}) // end $.post()
}
else if($this_field.is('.enum')) {
@@ -748,8 +784,9 @@ $(document).ready(function() {
'curr_value' : curr_value
}
$.post('sql.php', post_params, function(data) {
- $this_field.append(data.dropdown);
+ $this_field_span.append(data.dropdown);
$this_field.data('original_data', data_value);
+ $("#sqlqueryresults").trigger('refreshgrid');
}) // end $.post()
}
else if($this_field.is('.set')) {
@@ -770,16 +807,21 @@ $(document).ready(function() {
}
$.post('sql.php', post_params, function(data) {
- $this_field.append(data.select);
+ $this_field_span.append(data.select);
$this_field.data('original_data', data_value);
+ $("#sqlqueryresults").trigger('refreshgrid');
}) // end $.post()
}
else if($this_field.is('.null')) {
//handle null fields
- $this_field.append('<textarea></textarea>');
+ $this_field_span.append('<textarea></textarea>');
$this_field.data('original_data', 'NULL');
}
- })
+ });
+
+ // refresh the grid
+ $("#sqlqueryresults").trigger('refreshgrid');
+
}) // End On click, replace the current field with an input/textarea
/**
@@ -801,7 +843,7 @@ $(document).ready(function() {
* being edited
*
*/
- var $this_td = $(this).parent().parent();
+ var $this_td = $(this).parents('td');
var $test_element = ''; // to test the presence of a element
// Initialize variables
@@ -1043,10 +1085,11 @@ function PMA_unInlineEditRow($del_hide, $chg_submit, $this_td, $input_siblings,
$input_siblings.each(function() {
// Inline edit post has been successful.
$this_sibling = $(this);
+ $this_sibling_span = $(this).children('span');
var is_null = $this_sibling.find('input:checkbox').is(':checked');
if (is_null) {
- $this_sibling.html('NULL');
+ $this_sibling_span.html('NULL');
$this_sibling.addClass('null');
} else {
$this_sibling.removeClass('null');
@@ -1106,9 +1149,12 @@ function PMA_unInlineEditRow($del_hide, $chg_submit, $this_td, $input_siblings,
}
}
}
- $this_sibling.html(new_html);
+ $this_sibling_span.html(new_html);
}
})
+
+ // refresh the grid
+ $("#sqlqueryresults").trigger('refreshgrid');
}
/**
@@ -1155,6 +1201,11 @@ $(document).ready(function() {
$('.column_heading.marker').live('click', function() {
PMA_changeClassForColumn($(this), 'marked');
});
+
+ /**
+ * create resizable table
+ */
+ $("#sqlqueryresults").trigger('makegrid');
});
/*
diff --git a/js/tbl_select.js b/js/tbl_select.js
index 8115245..8d3049a 100644
--- a/js/tbl_select.js
+++ b/js/tbl_select.js
@@ -67,6 +67,7 @@ $(document).ready(function() {
// found results
$("#sqlqueryresults").html(response);
$("#sqlqueryresults").trigger('appendAnchor');
+ $("#sqlqueryresults").trigger('makegrid');
$('#tbl_search_form')
// work around for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome.
.slideToggle()
diff --git a/libraries/RecentTable.class.php b/libraries/RecentTable.class.php
index ebe93f5..b42a0aa 100644
--- a/libraries/RecentTable.class.php
+++ b/libraries/RecentTable.class.php
@@ -14,7 +14,7 @@ require_once './libraries/Message.class.php';
*
* @package phpMyAdmin
*/
-class RecentTable
+class PMA_RecentTable
{
/**
* Defines the internal PMA table which contains recent tables.
@@ -33,9 +33,9 @@ class RecentTable
public $tables;
/**
- * RecentTable instance.
+ * PMA_RecentTable instance.
*
- * @var RecentTable
+ * @var PMA_RecentTable
*/
private static $_instance;
@@ -56,12 +56,12 @@ class RecentTable
/**
* Returns class instance.
*
- * @return RecentTable
+ * @return PMA_RecentTable
*/
public static function getInstance()
{
if (is_null(self::$_instance)) {
- self::$_instance = new RecentTable();
+ self::$_instance = new PMA_RecentTable();
}
return self::$_instance;
}
diff --git a/libraries/Table.class.php b/libraries/Table.class.php
index c73eb31..cfc28d6 100644
--- a/libraries/Table.class.php
+++ b/libraries/Table.class.php
@@ -12,9 +12,10 @@
class PMA_Table
{
/**
- * UI preferences property: sorted column
+ * UI preferences properties
*/
const PROP_SORTED_COLUMN = 'sorted_col';
+ const PROP_COLUMN_ORDER = 'col_order';
static $cache = array();
@@ -1196,6 +1197,27 @@ class PMA_Table
}
/**
+ * Get all columns
+ *
+ * returns an array with all columns
+ *
+ * @param boolean whether to quote name with backticks ``
+ * @return array
+ */
+ public function getColumns($backquoted = true)
+ {
+ $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true);
+ $indexed = PMA_DBI_fetch_result($sql, 'Field', 'Field');
+
+ $return = array();
+ foreach ($indexed as $column) {
+ $return[] = $this->getFullName($backquoted) . '.' . ($backquoted ? PMA_backquote($column) : $column);
+ }
+
+ return $return;
+ }
+
+ /**
* Return UI preferences for this table from phpMyAdmin database.
*
* @uses PMA_query_as_controluser()
@@ -1276,25 +1298,11 @@ class PMA_Table
}
/**
- * Get UI preferences array for this table.
- * If pmadb and table_uiprefs is set, it will get the UI preferences from
- * phpMyAdmin database.
- *
- * @return array
- */
- public function getUiPrefs()
- {
- if (! isset($this->uiprefs)) {
- $this->loadUiPrefs();
- }
- return $this->uiprefs;
- }
-
- /**
* Get a property from UI preferences.
* Return false if the property is not found.
* Available property:
* - PROP_SORTED_COLUMN
+ * - PROP_COLUMN_ORDER
*
* @uses loadUiPrefs()
*
@@ -1306,6 +1314,42 @@ class PMA_Table
if (! isset($this->uiprefs)) {
$this->loadUiPrefs();
}
+ // do checking based on property
+ if ($property == self::PROP_SORTED_COLUMN) {
+ if (isset($this->uiprefs[$property])) {
+ // check if the column name is exist in this table
+ $tmp = explode(' ', $this->uiprefs[$property]);
+ $colname = $tmp[0];
+ $avail_columns = $this->getColumns();
+ foreach ($avail_columns as $each_col) {
+ // check if $each_col ends with $colname
+ if (substr_compare($each_col, $colname,
+ strlen($each_col) - strlen($colname)) === 0) {
+ return $this->uiprefs[$property];
+ }
+ }
+ // remove the property, since it is not exist anymore in database
+ $this->removeUiProp(self::PROP_SORTED_COLUMN);
+ return false;
+ } else {
+ return false;
+ }
+ } else if ($property == self::PROP_COLUMN_ORDER) {
+ if (isset($this->uiprefs[$property])) {
+ // check if the table has not been modified
+ if (self::sGetStatusInfo($this->db_name, $this->name, 'CREATE_TIME') ==
+ $this->uiprefs['CREATE_TIME']) {
+ return $this->uiprefs[$property];
+ } else {
+ // remove the property, since the table has been modified
+ $this->removeUiProp(self::PROP_COLUMN_ORDER);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ // default behaviour for other property:
return isset($this->uiprefs[$property]) ? $this->uiprefs[$property] : false;
}
@@ -1313,16 +1357,34 @@ class PMA_Table
* Set a property from UI preferences.
* If pmadb and table_uiprefs is set, it will save the UI preferences to
* phpMyAdmin database.
+ * Available property:
+ * - PROP_SORTED_COLUMN
+ * - PROP_COLUMN_ORDER
*
* @param string $property
* @param mixed $value
- * @return true|PMA_Message
+ * @param string $table_create_time Needed for PROP_COLUMN_ORDER
+ * @return boolean|PMA_Message
*/
- public function setUiProp($property, $value)
+ public function setUiProp($property, $value, $table_create_time = NULL)
{
if (! isset($this->uiprefs)) {
$this->loadUiPrefs();
}
+ // we want to save the create time if the property is PROP_COLUMN_ORDER
+ if ($property == self::PROP_COLUMN_ORDER) {
+ $curr_create_time = self::sGetStatusInfo($this->db_name, $this->name, 'CREATE_TIME');
+ if (isset($table_create_time) &&
+ $table_create_time == $curr_create_time) {
+ $this->uiprefs['CREATE_TIME'] = $curr_create_time;
+ } else {
+ // there is no $table_create_time, or
+ // supplied $table_create_time is older than current create time,
+ // so don't save
+ return false;
+ }
+ }
+ // save the value
$this->uiprefs[$property] = $value;
// check if pmadb is set
if (strlen($GLOBALS['cfg']['Server']['pmadb'])
@@ -1331,5 +1393,25 @@ class PMA_Table
}
return true;
}
+
+ /**
+ * Remove a property from UI preferences.
+ *
+ * @param string $property
+ */
+ public function removeUiProp($property)
+ {
+ if (! isset($this->uiprefs)) {
+ $this->loadUiPrefs();
+ }
+ if (isset($this->uiprefs[$property])) {
+ unset($this->uiprefs[$property]);
+ // check if pmadb is set
+ if (strlen($GLOBALS['cfg']['Server']['pmadb'])
+ && strlen($GLOBALS['cfg']['Server']['table_uiprefs'])) {
+ return $this->saveUiprefsToDb();
+ }
+ }
+ }
}
?>
diff --git a/libraries/common.lib.php b/libraries/common.lib.php
index 037656e..d2c8249 100644
--- a/libraries/common.lib.php
+++ b/libraries/common.lib.php
@@ -1032,6 +1032,22 @@ function PMA_showMessage($message, $sql_query = null, $type = 'notice', $is_view
// Analyze it
if (isset($parsed_sql)) {
$analyzed_display_query = PMA_SQP_analyze($parsed_sql);
+
+ // Same as below (append LIMIT), append the remembered ORDER BY
+ if ($GLOBALS['cfg']['RememberSorting']
+ && isset($analyzed_display_query[0]['queryflags']['select_from'])
+ && isset($GLOBALS['sql_order_to_append'])) {
+ $query_base = $analyzed_display_query[0]['section_before_limit']
+ . "\n" . $GLOBALS['sql_order_to_append']
+ . $analyzed_display_query[0]['section_after_limit'];
+
+ // Need to reparse query
+ $parsed_sql = PMA_SQP_parse($query_base);
+ // update the $analyzed_display_query
+ $analyzed_display_query[0]['section_before_limit'] .= $GLOBALS['sql_order_to_append'];
+ $analyzed_display_query[0]['order_by_clause'] = $GLOBALS['sorted_col'];
+ }
+
// Here we append the LIMIT added for navigation, to
// enable its display. Adding it higher in the code
// to $sql_query would create a problem when
diff --git a/libraries/display_tbl.lib.php b/libraries/display_tbl.lib.php
index 26d362d..84e6f7b 100644
--- a/libraries/display_tbl.lib.php
+++ b/libraries/display_tbl.lib.php
@@ -185,6 +185,25 @@ function PMA_setDisplayMode(&$the_disp_mode, &$the_total)
/**
+ * Return true if we are executing a query in the form of
+ * "SELECT * FROM <a table> ..."
+ *
+ * @return boolean
+ */
+function PMA_isSelect()
+{
+ // global variables set from sql.php
+ global $is_count, $is_export, $is_func, $is_analyse;
+ global $analyzed_sql;
+
+ return ! ($is_count || $is_export || $is_func || $is_analyse)
+ && count($analyzed_sql[0]['select_expr']) == 0
+ && isset($analyzed_sql[0]['queryflags']['select_from'])
+ && count($analyzed_sql[0]['table_ref']) == 1;
+}
+
+
+/**
* Displays a navigation button
*
* @uses $GLOBALS['cfg']['NavigationBarIconic']
@@ -374,6 +393,26 @@ function PMA_displayTableNavigation($pos_next, $pos_prev, $sql_query, $id_for_di
);
} // end move toward
?>
+ <td>
+ <input class="restore_column hide" type="submit" value="<?php echo __('Restore column order'); ?>" />
+ <?php
+ if (PMA_isSelect()) {
+ // generate the column order, if it is set
+ $pmatable = new PMA_Table($GLOBALS['table'], $GLOBALS['db']);
+ $col_order = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_ORDER);
+ if ($col_order) {
+ echo '<input id="col_order" type="hidden" value="' . implode(',', $col_order) . '" />';
+ }
+ // generate table create time
+ echo '<input id="table_create_time" type="hidden" value="' .
+ PMA_Table::sGetStatusInfo($GLOBALS['db'], $GLOBALS['table'], 'CREATE_TIME') . '" />';
+ }
+ // generate hints
+ echo '<input id="col_order_hint" type="hidden" value="' . __('Drag to reorder') . '" />';
+ echo '<input id="sort_hint" type="hidden" value="' . __('Click to sort') . '" />';
+ echo '<input id="col_mark_hint" type="hidden" value="' . __('Click to mark/unmark') . '" />';
+ ?>
+ </td>
</tr>
</table>
@@ -695,7 +734,7 @@ function PMA_displayTableHeaders(&$is_display, &$fields_meta, $fields_cnt = 0, $
// ... elseif display an empty column if the actions links are disabled to match the rest of the table
elseif ($GLOBALS['cfg']['RowActionLinks'] == 'none' && ($_SESSION['tmp_user_values']['disp_direction'] == 'horizontal'
|| $_SESSION['tmp_user_values']['disp_direction'] == 'horizontalflipped')) {
- echo '<td></td>';
+ echo '<th></th>';
}
// 2. Displays the fields' name
@@ -735,7 +774,17 @@ function PMA_displayTableHeaders(&$is_display, &$fields_meta, $fields_cnt = 0, $
}
}
- for ($i = 0; $i < $fields_cnt; $i++) {
+ if (PMA_isSelect()) {
+ // prepare to get the column order, if available
+ $pmatable = new PMA_Table($GLOBALS['table'], $GLOBALS['db']);
+ $col_order = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_ORDER);
+ } else {
+ $col_order = false;
+ }
+
+ for ($j = 0; $j < $fields_cnt; $j++) {
+ // assign $i with appropriate column order
+ $i = $col_order ? $col_order[$j] : $j;
// See if this column should get highlight because it's used in the
// where-query.
if (isset($highlight_columns[$fields_meta[$i]->name]) || isset($highlight_columns[PMA_backquote($fields_meta[$i]->name)])) {
@@ -870,6 +919,7 @@ function PMA_displayTableHeaders(&$is_display, &$fields_meta, $fields_cnt = 0, $
|| $_SESSION['tmp_user_values']['disp_direction'] == 'horizontalflipped') {
echo '<th';
$th_class = array();
+ $th_class[] = 'draggable';
if ($condition_field) {
$th_class[] = 'condition';
}
@@ -888,7 +938,7 @@ function PMA_displayTableHeaders(&$is_display, &$fields_meta, $fields_cnt = 0, $
echo '>' . $order_link . $comments . '</th>';
}
$vertical_display['desc'][] = ' <th '
- . ($condition_field ? ' class="condition"' : '') . '>' . "\n"
+ . 'class="draggable' . ($condition_field ? ' condition' : '') . '">' . "\n"
. $order_link . $comments . ' </th>' . "\n";
} // end if (2.1)
@@ -897,9 +947,12 @@ function PMA_displayTableHeaders(&$is_display, &$fields_meta, $fields_cnt = 0, $
if ($_SESSION['tmp_user_values']['disp_direction'] == 'horizontal'
|| $_SESSION['tmp_user_values']['disp_direction'] == 'horizontalflipped') {
echo '<th';
+ $th_class = array();
+ $th_class[] = 'draggable';
if ($condition_field) {
- echo ' class="condition"';
+ $th_class[] = 'condition';
}
+ echo ' class="' . implode(' ', $th_class) . '"';
if ($_SESSION['tmp_user_values']['disp_direction'] == 'horizontalflipped') {
echo ' valign="bottom"';
}
@@ -917,7 +970,7 @@ function PMA_displayTableHeaders(&$is_display, &$fields_meta, $fields_cnt = 0, $
echo "\n" . $comments . '</th>';
}
$vertical_display['desc'][] = ' <th '
- . ($condition_field ? ' class="condition"' : '') . '>' . "\n"
+ . 'class="draggable' . ($condition_field ? ' condition"' : '') . '">' . "\n"
. ' ' . htmlspecialchars($fields_meta[$i]->name) . "\n"
. $comments . ' </th>';
} // end else (2.2)
@@ -1150,6 +1203,8 @@ function PMA_displayTableBody(&$dt_result, &$is_display, $map, $analyzed_sql) {
if ($vertical_display['emptypre'] > 0) {
echo ' <th colspan="' . $vertical_display['emptypre'] . '">' . "\n"
.' </th>' . "\n";
+ } else if ($GLOBALS['cfg']['RowActionLinks'] == 'none') {
+ echo ' <th></th>' . "\n";
}
foreach ($vertical_display['desc'] as $val) {
@@ -1285,7 +1340,19 @@ function PMA_displayTableBody(&$dt_result, &$is_display, $map, $analyzed_sql) {
} // end if (1)
// 2. Displays the rows' values
- for ($i = 0; $i < $fields_cnt; ++$i) {
+
+ if (PMA_isSelect()) {
+ // prepare to get the column order, if available
+ $pmatable = new PMA_Table($GLOBALS['table'], $GLOBALS['db']);
+ $col_order = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_ORDER);
+ } else {
+ $col_order = false;
+ }
+
+ for ($j = 0; $j < $fields_cnt; ++$j) {
+ // assign $i with appropriate column order
+ $i = $col_order ? $col_order[$j] : $j;
+
$meta = $fields_meta[$i];
$not_null_class = $meta->not_null ? 'not_null' : '';
$relation_class = isset($map[$meta->name]) ? 'relation' : '';
@@ -1652,8 +1719,18 @@ function PMA_displayVerticalTable()
echo '</tr>' . "\n";
} // end if
+ if (PMA_isSelect()) {
+ // prepare to get the column order, if available
+ $pmatable = new PMA_Table($GLOBALS['table'], $GLOBALS['db']);
+ $col_order = $pmatable->getUiProp(PMA_Table::PROP_COLUMN_ORDER);
+ } else {
+ $col_order = false;
+ }
+
// Displays data
- foreach ($vertical_display['desc'] AS $key => $val) {
+ foreach ($vertical_display['desc'] AS $j => $val) {
+ // assign appropriate key with current column order
+ $key = $col_order ? $col_order[$j] : $j;
echo '<tr>' . "\n";
echo $val;
diff --git a/libraries/header.inc.php b/libraries/header.inc.php
index 4036f86..5c07472 100644
--- a/libraries/header.inc.php
+++ b/libraries/header.inc.php
@@ -19,7 +19,7 @@ require_once './libraries/RecentTable.class.php';
* @param string $table The table name
*/
function PMA_addRecentTable($db, $table) {
- $tmp_result = RecentTable::getInstance()->add($db, $table);
+ $tmp_result = PMA_RecentTable::getInstance()->add($db, $table);
if ($tmp_result === true) {
echo '<span class="hide" id="update_recent_tables"></span>';
} else {
diff --git a/navigation.php b/navigation.php
index 76f5b98..af3f138 100644
--- a/navigation.php
+++ b/navigation.php
@@ -59,7 +59,7 @@ require_once './libraries/RecentTable.class.php';
* Check if it is an ajax request to reload the recent tables list.
*/
if ($GLOBALS['is_ajax_request'] && $_REQUEST['recent_table']) {
- PMA_ajaxResponse('', true, array('options' => RecentTable::getInstance()->getHtmlSelectOption()) );
+ PMA_ajaxResponse('', true, array('options' => PMA_RecentTable::getInstance()->getHtmlSelectOption()) );
}
// keep the offset of the db list in session before closing it
@@ -195,7 +195,7 @@ require './libraries/navigation_header.inc.php';
// display recently used tables
if ($GLOBALS['cfg']['LeftRecentTable'] > 0) {
echo '<div id="recentTableList">';
- echo RecentTable::getInstance()->getHtmlSelect();
+ echo PMA_RecentTable::getInstance()->getHtmlSelect();
echo '</div>';
}
diff --git a/server_sql.php b/server_sql.php
index 243d0bf..f8609f4 100644
--- a/server_sql.php
+++ b/server_sql.php
@@ -14,6 +14,7 @@ require_once './libraries/common.inc.php';
* Does the common work
*/
$GLOBALS['js_include'][] = 'functions.js';
+$GLOBALS['js_include'][] = 'makegrid.js';
$GLOBALS['js_include'][] = 'sql.js';
require_once './libraries/server_common.inc.php';
diff --git a/sql.php b/sql.php
index e8850fb..bd3ff46 100644
--- a/sql.php
+++ b/sql.php
@@ -159,6 +159,17 @@ if(isset($_REQUEST['get_set_values']) && $_REQUEST['get_set_values'] == true) {
$extra_data['select'] = $select;
PMA_ajaxResponse(NULL, true, $extra_data);
}
+
+/**
+ * Check ajax request to set the column order
+ */
+if(isset($_REQUEST['set_col_order']) && $_REQUEST['set_col_order'] == true) {
+ $pmatable = new PMA_Table($table, $db);
+ $col_order = explode(',', $_REQUEST['col_order']);
+ $retval = $pmatable->setUiProp(PMA_Table::PROP_COLUMN_ORDER, $col_order, $_REQUEST['table_create_time']);
+ PMA_ajaxResponse(NULL, ($retval == true));
+}
+
// Default to browse if no query set and we have table
// (needed for browsing from DefaultTabTable)
if (empty($sql_query) && strlen($table) && strlen($db)) {
@@ -363,10 +374,13 @@ if ($is_select) { // see line 141
$is_maint = true;
}
+// assign default full_sql_query
+$full_sql_query = $sql_query;
+
// Handle remembered sorting order, only for single table query
if ($GLOBALS['cfg']['RememberSorting']
- && basename($GLOBALS['PMA_PHP_SELF']) == 'sql.php'
&& ! ($is_count || $is_export || $is_func || $is_analyse)
+ && count($analyzed_sql[0]['select_expr']) == 0
&& isset($analyzed_sql[0]['queryflags']['select_from'])
&& count($analyzed_sql[0]['table_ref']) == 1
) {
@@ -376,7 +390,7 @@ if ($GLOBALS['cfg']['RememberSorting']
if ($sorted_col) {
// retrieve the remembered sorting order for current table
$sql_order_to_append = ' ORDER BY ' . $sorted_col . ' ';
- $sql_query = $analyzed_sql[0]['section_before_limit'] . $sql_order_to_append . $analyzed_sql[0]['section_after_limit'];
+ $full_sql_query = $analyzed_sql[0]['section_before_limit'] . $sql_order_to_append . $analyzed_sql[0]['section_after_limit'];
// update the $analyzed_sql
$analyzed_sql[0]['section_before_limit'] .= $sql_order_to_append;
@@ -412,9 +426,7 @@ if ((! $cfg['ShowAll'] || $_SESSION['tmp_user_values']['max_rows'] != 'all')
}
}
-} else {
- $full_sql_query = $sql_query;
-} // end if...else
+}
if (strlen($db)) {
PMA_DBI_select_db($db);
@@ -846,6 +858,7 @@ else {
} else {
$GLOBALS['js_include'][] = 'functions.js';
+ $GLOBALS['js_include'][] = 'makegrid.js';
$GLOBALS['js_include'][] = 'sql.js';
unset($message);
diff --git a/tbl_replace.php b/tbl_replace.php
index 5b722c6..d23482e 100644
--- a/tbl_replace.php
+++ b/tbl_replace.php
@@ -65,6 +65,7 @@ PMA_DBI_select_db($GLOBALS['db']);
*/
$goto_include = false;
+$GLOBALS['js_include'][] = 'makegrid.js';
// Needed for generation of Inline Edit anchors
$GLOBALS['js_include'][] = 'sql.js';
diff --git a/tbl_select.php b/tbl_select.php
index 681083a..875182d 100644
--- a/tbl_select.php
+++ b/tbl_select.php
@@ -16,6 +16,7 @@
require_once './libraries/common.inc.php';
require_once './libraries/mysql_charsets.lib.php';
+$GLOBALS['js_include'][] = 'makegrid.js';
$GLOBALS['js_include'][] = 'sql.js';
$GLOBALS['js_include'][] = 'tbl_select.js';
$GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js';
diff --git a/tbl_sql.php b/tbl_sql.php
index 70fafc1..0321395 100644
--- a/tbl_sql.php
+++ b/tbl_sql.php
@@ -14,6 +14,7 @@ require_once './libraries/common.inc.php';
* Runs common work
*/
$GLOBALS['js_include'][] = 'functions.js';
+$GLOBALS['js_include'][] = 'makegrid.js';
$GLOBALS['js_include'][] = 'sql.js';
require './libraries/tbl_common.php';
diff --git a/themes/original/css/theme_right.css.php b/themes/original/css/theme_right.css.php
index 94ed5fe..72dec00 100644
--- a/themes/original/css/theme_right.css.php
+++ b/themes/original/css/theme_right.css.php
@@ -1913,3 +1913,68 @@ span.mysql-separator {
span.mysql-number {
color: <?php echo $GLOBALS['cfg']['SQP']['fmtColor']['digit_integer']; ?>;
}
+
+.colborder {
+ border-right: solid 1px #FFFFFF;
+ cursor: col-resize;
+ height: 100%;
+ margin-left: -3px;
+ position: absolute;
+ width: 5px;
+}
+
+.pma_table th.draggable span, .pma_table tbody td span {
+ display: block;
+ overflow: hidden;
+}
+
+.cRsz {
+ position: absolute;
+}
+
+.draggable {
+ cursor: move;
+}
+
+.cCpy {
+ background: #000;
+ color: #FFF;
+ font-weight: bold;
+ margin: 0.1em;
+ padding: 0.3em;
+ position: absolute;
+}
+
+.cPointer {
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>col_pointer.png);
+ height: 20px;
+ margin-left: -5px; /* must be minus half of its width */
+ margin-top: -10px;
+ position: absolute;
+ width: 10px;
+}
+
+.cPointerVer { /* cPointer with vertical display mode */
+ background: url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>col_pointer_ver.png);
+ height: 10px;
+ margin-left: -5px;
+ margin-top: -5px; /* must be minus half of its height */
+ position: absolute;
+ width: 20px;
+}
+
+.dHint {
+ background: #333;
+ border:1px solid #000;
+ color: #FFF;
+ font-size: 0.8em;
+ font-weight: bold;
+ margin-top: -1em;
+ opacity: 0.8;
+ padding: 0.5em 1em;
+ position: absolute;
+ text-shadow: -1px -1px #000;
+ -moz-border-radius: 0.3em;
+ -webkit-border-radius: 0.3em;
+ border-radius: 0.3em;
+}
diff --git a/themes/original/img/col_pointer.png b/themes/original/img/col_pointer.png
new file mode 100644
index 0000000..7126aef
Binary files /dev/null and b/themes/original/img/col_pointer.png differ
diff --git a/themes/original/img/col_pointer_ver.png b/themes/original/img/col_pointer_ver.png
new file mode 100644
index 0000000..4607912
Binary files /dev/null and b/themes/original/img/col_pointer_ver.png differ
diff --git a/themes/pmahomme/css/theme_right.css.php b/themes/pmahomme/css/theme_right.css.php
index c403698..3c9d951 100644
--- a/themes/pmahomme/css/theme_right.css.php
+++ b/themes/pmahomme/css/theme_right.css.php
@@ -2269,3 +2269,72 @@ span.mysql-separator {
span.mysql-number {
color: <?php echo $GLOBALS['cfg']['SQP']['fmtColor']['digit_integer']; ?>;
}
+
+.colborder {
+ border-left: 1px solid #FFF;
+ cursor: col-resize;
+ height: 100%;
+ margin-left: -1px;
+ position: absolute;
+ width: 5px;
+}
+
+.pma_table th.draggable span, .pma_table tbody td span {
+ display: block;
+ overflow: hidden;
+}
+
+.cRsz {
+ position: absolute;
+}
+
+.cCpy {
+ background: #333;
+ color: #FFF;
+ font-weight: bold;
+ margin: 0.1em;
+ padding: 0.3em;
+ position: absolute;
+ text-shadow: -1px -1px #000;
+
+ -moz-box-shadow: 0 0 0.7em #000;
+ -webkit-box-shadow: 0 0 0.7em #000;
+ box-shadow: 0 0 0.7em #000;
+ -moz-border-radius: 0.3em;
+ -webkit-border-radius: 0.3em;
+ border-radius: 0.3em;
+}
+
+.cPointer {
+ background: url(./themes/pmahomme/img/col_pointer.png);
+ height: 20px;
+ margin-left: -5px; /* must be minus half of its width */
+ margin-top: -10px;
+ position: absolute;
+ width: 10px;
+}
+
+.cPointerVer { /* cPointer with vertical display mode */
+ background: url(./themes/pmahomme/img/col_pointer_ver.png);
+ height: 10px;
+ margin-left: -5px;
+ margin-top: -5px; /* must be minus half of its height */
+ position: absolute;
+ width: 20px;
+}
+
+.dHint {
+ background: #333;
+ border:1px solid #000;
+ color: #FFF;
+ font-size: 0.8em;
+ font-weight: bold;
+ margin-top: -1em;
+ opacity: 0.8;
+ padding: 0.5em 1em;
+ position: absolute;
+ text-shadow: -1px -1px #000;
+ -moz-border-radius: 0.3em;
+ -webkit-border-radius: 0.3em;
+ border-radius: 0.3em;
+}
diff --git a/themes/pmahomme/img/col_pointer.png b/themes/pmahomme/img/col_pointer.png
new file mode 100644
index 0000000..8882a2e
Binary files /dev/null and b/themes/pmahomme/img/col_pointer.png differ
diff --git a/themes/pmahomme/img/col_pointer_ver.png b/themes/pmahomme/img/col_pointer_ver.png
new file mode 100644
index 0000000..8964754
Binary files /dev/null and b/themes/pmahomme/img/col_pointer_ver.png differ
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3565-g551cc9a
by Michal Čihař 15 Jun '11
by Michal Čihař 15 Jun '11
15 Jun '11
The branch, master has been updated
via 551cc9a465269ab1208438d81fc0d492ce797366 (commit)
via ba8e434a99e88ff84db5e391004b13df3de6f063 (commit)
via 0d7c2195ffed20f90f516fd0662d2e166f55aba6 (commit)
via 6faabf70f4c95cc0733c6f7abe43701f6b04ae48 (commit)
via 1f6f14a4ef1603ddc9c724d29a32b55f2ed40f7d (commit)
via e122e5ac70e939febb457d2445dcd1c7d0942340 (commit)
via 7ff287e1e045fabd9eb504d90de02634807d3424 (commit)
via 2b949e99d7aebe454280c4fab445afceee30ce39 (commit)
via 95c60e193d8a431712844871fa54edfeaedf8536 (commit)
via 60a2f965929ec35e82f4183265bd2f82e3a6a1af (commit)
via 2ad26bcc604daa46104952f57a3bf4a6a59d8ba2 (commit)
via 45330faa592c05fd6a20a90ea898baf580f9d84d (commit)
via bd731759a7339c7c5256630511da9f9dc1ad20c0 (commit)
via 139ac496c181950dea2ef2b032d718098a6e8db6 (commit)
via 11b8a407f35888c4a1f6b4f4bbef889ea868a5f7 (commit)
via 75bc5fc24a13d7743f8853265c94b10c044d01d1 (commit)
via 7d4841df4951193fd75e1d92df6c8b182f426d97 (commit)
via c4992e568cdc06c1a543a916bc3f021ebe884413 (commit)
via 6544f5f9e78db8b4364589b8cdbe2a80df609bc3 (commit)
via 50ae4412950b96b86562a02c4c8552611453dabd (commit)
via 3a2fd218fcd8c942a7b11188ad147d2e45a4ac0c (commit)
via 7cb695d5020d9b555b19fea60b0f92c2b880ccf0 (commit)
via 972be216dd261f2bc4ccf9a5fd2304936a9fff92 (commit)
via 9179fde0a587b5c722edcbea3a2c35ce60f56a95 (commit)
via 8c0a294dc5acc3fc2926b97706d518504d54b205 (commit)
via e8b26f8e5d11a56e5805c5d48fa79bfb368fce6d (commit)
via 4a09e17af3f1220a36dac61c3b578f9a925bb1b7 (commit)
via 60ad158b0451c3347e5a8813cc39099b40e78b1f (commit)
via fb91a17704339395c51d2027eef6b6ef5de7fc04 (commit)
via 9943ef0275b2f0e880680c4c166fd33f5048b874 (commit)
via 22e8102e3212045c0ab1718d52e69be0fdeebe34 (commit)
via f8dd706ad38113afe6291524f01b8b5e4cd9e8c5 (commit)
via 05235672e67e5856aa688adaeabc97b3780c02ae (commit)
via 2216824c900d18b5f39058c1e97a9b56d4309c99 (commit)
via a3510734b18cf12a77cedabceb76258539565e75 (commit)
via 9409b76db65fd5ab13a71e2d765cbf13c44b49c1 (commit)
from 25dc50a5dd8a69f435272d37f7febafffc1d0ff0 (commit)
- Log -----------------------------------------------------------------
commit 551cc9a465269ab1208438d81fc0d492ce797366
Merge: ba8e434a99e88ff84db5e391004b13df3de6f063 0d7c2195ffed20f90f516fd0662d2e166f55aba6
Author: Michal Čihař <mcihar(a)suse.cz>
Date: Wed Jun 15 07:50:05 2011 +0200
Merge remote-tracking branch 'pootle/master'
commit ba8e434a99e88ff84db5e391004b13df3de6f063
Merge: 25dc50a5dd8a69f435272d37f7febafffc1d0ff0 2216824c900d18b5f39058c1e97a9b56d4309c99
Author: Michal Čihař <mcihar(a)suse.cz>
Date: Wed Jun 15 07:49:56 2011 +0200
Merge remote-tracking branch 'tyron/master'
Conflicts:
server_status.php
commit 0d7c2195ffed20f90f516fd0662d2e166f55aba6
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:35:52 2011 +0200
Translation update done using Pootle.
commit 6faabf70f4c95cc0733c6f7abe43701f6b04ae48
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:35:44 2011 +0200
Translation update done using Pootle.
commit 1f6f14a4ef1603ddc9c724d29a32b55f2ed40f7d
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:35:36 2011 +0200
Translation update done using Pootle.
commit e122e5ac70e939febb457d2445dcd1c7d0942340
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:35:22 2011 +0200
Translation update done using Pootle.
commit 7ff287e1e045fabd9eb504d90de02634807d3424
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:35:12 2011 +0200
Translation update done using Pootle.
commit 2b949e99d7aebe454280c4fab445afceee30ce39
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:35:04 2011 +0200
Translation update done using Pootle.
commit 95c60e193d8a431712844871fa54edfeaedf8536
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:34:55 2011 +0200
Translation update done using Pootle.
commit 60a2f965929ec35e82f4183265bd2f82e3a6a1af
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:34:49 2011 +0200
Translation update done using Pootle.
commit 2ad26bcc604daa46104952f57a3bf4a6a59d8ba2
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:34:40 2011 +0200
Translation update done using Pootle.
commit 45330faa592c05fd6a20a90ea898baf580f9d84d
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:34:31 2011 +0200
Translation update done using Pootle.
commit bd731759a7339c7c5256630511da9f9dc1ad20c0
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:34:20 2011 +0200
Translation update done using Pootle.
commit 139ac496c181950dea2ef2b032d718098a6e8db6
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:34:10 2011 +0200
Translation update done using Pootle.
commit 11b8a407f35888c4a1f6b4f4bbef889ea868a5f7
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:33:58 2011 +0200
Translation update done using Pootle.
commit 75bc5fc24a13d7743f8853265c94b10c044d01d1
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:33:46 2011 +0200
Translation update done using Pootle.
commit 7d4841df4951193fd75e1d92df6c8b182f426d97
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:33:37 2011 +0200
Translation update done using Pootle.
commit c4992e568cdc06c1a543a916bc3f021ebe884413
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:33:28 2011 +0200
Translation update done using Pootle.
commit 6544f5f9e78db8b4364589b8cdbe2a80df609bc3
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:33:15 2011 +0200
Translation update done using Pootle.
commit 50ae4412950b96b86562a02c4c8552611453dabd
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:33:06 2011 +0200
Translation update done using Pootle.
commit 3a2fd218fcd8c942a7b11188ad147d2e45a4ac0c
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:32:52 2011 +0200
Translation update done using Pootle.
commit 7cb695d5020d9b555b19fea60b0f92c2b880ccf0
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:32:40 2011 +0200
Translation update done using Pootle.
commit 972be216dd261f2bc4ccf9a5fd2304936a9fff92
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:32:26 2011 +0200
Translation update done using Pootle.
commit 9179fde0a587b5c722edcbea3a2c35ce60f56a95
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:32:18 2011 +0200
Translation update done using Pootle.
commit 8c0a294dc5acc3fc2926b97706d518504d54b205
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:32:07 2011 +0200
Translation update done using Pootle.
commit e8b26f8e5d11a56e5805c5d48fa79bfb368fce6d
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:31:57 2011 +0200
Translation update done using Pootle.
commit 4a09e17af3f1220a36dac61c3b578f9a925bb1b7
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:31:46 2011 +0200
Translation update done using Pootle.
commit 60ad158b0451c3347e5a8813cc39099b40e78b1f
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:31:38 2011 +0200
Translation update done using Pootle.
commit fb91a17704339395c51d2027eef6b6ef5de7fc04
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:31:23 2011 +0200
Translation update done using Pootle.
commit 9943ef0275b2f0e880680c4c166fd33f5048b874
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:31:16 2011 +0200
Translation update done using Pootle.
commit 22e8102e3212045c0ab1718d52e69be0fdeebe34
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:31:06 2011 +0200
Translation update done using Pootle.
commit f8dd706ad38113afe6291524f01b8b5e4cd9e8c5
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:30:54 2011 +0200
Translation update done using Pootle.
commit 05235672e67e5856aa688adaeabc97b3780c02ae
Author: Marc Delisle <marc(a)infomarc.info>
Date: Wed Jun 15 02:30:40 2011 +0200
Translation update done using Pootle.
commit 2216824c900d18b5f39058c1e97a9b56d4309c99
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 14 20:55:37 2011 +0200
- Newest canvg version from trunk fixing display of hidden elements
- Some attempts to get png export working for IE8 (needs flashcanvas)
commit a3510734b18cf12a77cedabceb76258539565e75
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 14 17:19:40 2011 +0200
merge conflicts
commit 9409b76db65fd5ab13a71e2d765cbf13c44b49c1
Merge: d7fc94529cef1f3bbdb1ec302c2d22dcfd039112 4b73c39775b8234b66fd375aa3ac7387e945d704
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 14 17:16:26 2011 +0200
Merge remote-tracking branch 'origin/master'
Conflicts:
server_status.php
-----------------------------------------------------------------------
Summary of changes:
js/canvg/canvg.js | 304 ++++++++++---
js/canvg/flashcanvas.js | 1093 +++++++++++++++++++++++++++++++++++++++++++
js/canvg/flashcanvas.swf | Bin 0 -> 21235 bytes
js/highcharts/exporting.js | 4 +
js/highcharts/highcharts.js | 2 +-
po/en_GB.po | 85 ++--
server_status.php | 6 +
7 files changed, 1389 insertions(+), 105 deletions(-)
create mode 100644 js/canvg/flashcanvas.js
create mode 100644 js/canvg/flashcanvas.swf
diff --git a/js/canvg/canvg.js b/js/canvg/canvg.js
index 83c91b4..7b7e963 100644
--- a/js/canvg/canvg.js
+++ b/js/canvg/canvg.js
@@ -12,7 +12,6 @@ if(!window.console) {
window.console.dir = function(str) {};
}
-// <3 IE
if(!Array.indexOf){
Array.prototype.indexOf = function(obj){
for(var i=0; i<this.length; i++){
@@ -28,7 +27,7 @@ if(!Array.indexOf){
// canvg(target, s)
// empty parameters: replace all 'svg' elements on page with 'canvas' elements
// target: canvas element or the id of a canvas element
- // s: svg string or url to svg file
+ // s: svg string, url to svg file, or xml document
// opts: optional hash of options
// ignoreMouse: true => ignore mouse events
// ignoreAnimation: true => ignore animations
@@ -75,7 +74,11 @@ if(!Array.indexOf){
svg.opts = opts;
var ctx = target.getContext('2d');
- if (s.substr(0,1) == '<') {
+ if (typeof(s.documentElement) != 'undefined') {
+ // load from xml doc
+ svg.loadXmlDoc(ctx, s);
+ }
+ else if (s.substr(0,1) == '<') {
// load from xml string
svg.loadXml(ctx, s);
}
@@ -89,6 +92,7 @@ if(!Array.indexOf){
var svg = { };
svg.FRAMERATE = 30;
+ svg.MAX_VIRTUAL_PIXELS = 30000;
// globals
svg.init = function(ctx) {
@@ -99,6 +103,7 @@ if(!Array.indexOf){
svg.ctx = ctx;
svg.ViewPort = new (function () {
this.viewPorts = [];
+ this.Clear = function() { this.viewPorts = []; }
this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
this.RemoveCurrent = function() { this.viewPorts.pop(); }
this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
@@ -551,7 +556,7 @@ if(!Array.indexOf){
}
}
- var data = v.split(/\s(?=[a-z])/);
+ var data = svg.trim(svg.compressSpaces(v)).split(/\s(?=[a-z])/);
for (var i=0; i<data.length; i++) {
var type = data[i].split('(')[0];
var s = data[i].split('(')[1].replace(')','');
@@ -616,7 +621,7 @@ if(!Array.indexOf){
return a;
}
- // get or create style
+ // get or create style, crawls up node tree
this.style = function(name, createIfNotExists) {
var s = this.styles[name];
if (s != null) return s;
@@ -625,6 +630,14 @@ if(!Array.indexOf){
if (a != null && a.hasValue()) {
return a;
}
+
+ var p = this.parent;
+ if (p != null) {
+ var ps = p.style(name);
+ if (ps != null && ps.hasValue()) {
+ return ps;
+ }
+ }
s = new svg.Property(name, '');
if (createIfNotExists == true) this.styles[name] = s;
@@ -635,6 +648,9 @@ if(!Array.indexOf){
this.render = function(ctx) {
// don't render display=none
if (this.attribute('display').value == 'none') return;
+
+ // don't render visibility=hidden
+ if (this.attribute('visibility').value == 'hidden') return;
ctx.save();
this.setContext(ctx);
@@ -681,7 +697,7 @@ if(!Array.indexOf){
}
// add tag styles
- var styles = svg.Styles[this.type];
+ var styles = svg.Styles[node.nodeName];
if (styles != null) {
for (var name in styles) {
this.styles[name] = styles[name];
@@ -698,6 +714,12 @@ if(!Array.indexOf){
this.styles[name] = styles[name];
}
}
+ styles = svg.Styles[node.nodeName+'.'+classes[j]];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
}
}
@@ -856,7 +878,7 @@ if(!Array.indexOf){
var width = svg.ViewPort.width();
var height = svg.ViewPort.height();
- if (this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
+ if (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
width = this.attribute('width').Length.toPixels('x');
height = this.attribute('height').Length.toPixels('y');
@@ -1186,9 +1208,6 @@ if(!Array.indexOf){
pp.reset();
var bb = new svg.BoundingBox();
-
- if(this.attribute('visibility').value=='hidden') return;
-
if (ctx != null) ctx.beginPath();
while (!pp.isEnd()) {
pp.nextCommand();
@@ -1469,6 +1488,37 @@ if(!Array.indexOf){
for (var i=0; i<stopsContainer.stops.length; i++) {
g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
}
+
+ if (this.attribute('gradientTransform').hasValue()) {
+ // render as transformed pattern on temporary canvas
+ var rootView = svg.ViewPort.viewPorts[0];
+
+ var rect = new svg.Element.rect();
+ rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
+ rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
+ rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
+ rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
+
+ var group = new svg.Element.g();
+ group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
+ group.children = [ rect ];
+
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['x'] = new svg.Property('x', 0);
+ tempSvg.attributes['y'] = new svg.Property('y', 0);
+ tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
+ tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
+ tempSvg.children = [ group ];
+
+ var c = document.createElement('canvas');
+ c.width = rootView.width;
+ c.height = rootView.height;
+ var tempCtx = c.getContext('2d');
+ tempCtx.fillStyle = g;
+ tempSvg.render(tempCtx);
+ return tempCtx.createPattern(c, 'no-repeat');
+ }
+
return g;
}
}
@@ -1494,16 +1544,8 @@ if(!Array.indexOf){
var y2 = (this.gradientUnits == 'objectBoundingBox'
? bb.y() + bb.height() * this.attribute('y2').numValue()
: this.attribute('y2').Length.toPixels('y'));
-
- var p1 = new svg.Point(x1, y1);
- var p2 = new svg.Point(x2, y2);
- if (this.attribute('gradientTransform').hasValue()) {
- var transform = new svg.Transform(this.attribute('gradientTransform').value);
- transform.applyToPoint(p1);
- transform.applyToPoint(p2);
- }
-
- return ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
+
+ return ctx.createLinearGradient(x1, y1, x2, y2);
}
}
svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
@@ -1540,22 +1582,7 @@ if(!Array.indexOf){
? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
: this.attribute('r').Length.toPixels());
- var c = new svg.Point(cx, cy);
- var f = new svg.Point(fx, fy);
- if (this.attribute('gradientTransform').hasValue()) {
- var transform = new svg.Transform(this.attribute('gradientTransform').value);
- transform.applyToPoint(c);
- transform.applyToPoint(f);
-
- for (var i=0; i<transform.transforms.length; i++) {
- // average the scaling part of the transform, apply to radius
- var scale1 = transform.transforms[i].m[0];
- var scale2 = transform.transforms[i].m[3];
- r = r * ((scale1 + scale2) / 2.0);
- }
- }
-
- return ctx.createRadialGradient(f.x, f.y, 0, c.x, c.y, r);
+ return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
}
}
svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
@@ -1693,6 +1720,73 @@ if(!Array.indexOf){
}
svg.Element.animateTransform.prototype = new svg.Element.animate;
+ // font element
+ svg.Element.font = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.horizAdvX = this.attribute('horiz-adv-x').numValue();
+
+ this.isRTL = false;
+ this.isArabic = false;
+ this.fontFace = null;
+ this.missingGlyph = null;
+ this.glyphs = [];
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+ if (child.type == 'font-face') {
+ this.fontFace = child;
+ if (child.style('font-family').hasValue()) {
+ svg.Definitions[child.style('font-family').value] = this;
+ }
+ }
+ else if (child.type == 'missing-glyph') this.missingGlyph = child;
+ else if (child.type == 'glyph') {
+ if (child.arabicForm != '') {
+ this.isRTL = true;
+ this.isArabic = true;
+ if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
+ this.glyphs[child.unicode][child.arabicForm] = child;
+ }
+ else {
+ this.glyphs[child.unicode] = child;
+ }
+ }
+ }
+ }
+ svg.Element.font.prototype = new svg.Element.ElementBase;
+
+ // font-face element
+ svg.Element.fontface = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.ascent = this.attribute('ascent').value;
+ this.descent = this.attribute('descent').value;
+ this.unitsPerEm = this.attribute('units-per-em').numValue();
+ }
+ svg.Element.fontface.prototype = new svg.Element.ElementBase;
+
+ // missing-glyph element
+ svg.Element.missingglyph = function(node) {
+ this.base = svg.Element.path;
+ this.base(node);
+
+ this.horizAdvX = 0;
+ }
+ svg.Element.missingglyph.prototype = new svg.Element.path;
+
+ // glyph element
+ svg.Element.glyph = function(node) {
+ this.base = svg.Element.path;
+ this.base(node);
+
+ this.horizAdvX = this.attribute('horiz-adv-x').numValue();
+ this.unicode = this.attribute('unicode').value;
+ this.arabicForm = this.attribute('arabic-form').value;
+ }
+ svg.Element.glyph.prototype = new svg.Element.path;
+
// text element
svg.Element.text = function(node) {
this.base = svg.Element.RenderedElementBase;
@@ -1715,19 +1809,16 @@ if(!Array.indexOf){
this.baseSetContext = this.setContext;
this.setContext = function(ctx) {
this.baseSetContext(ctx);
- if (this.attribute('text-anchor').hasValue()) {
- var textAnchor = this.attribute('text-anchor').value;
+ if (this.style('text-anchor').hasValue()) {
+ var textAnchor = this.style('text-anchor').value;
ctx.textAlign = textAnchor == 'middle' ? 'center' : textAnchor;
}
if (this.attribute('alignment-baseline').hasValue()) ctx.textBaseline = this.attribute('alignment-baseline').value;
}
this.renderChildren = function(ctx) {
- if(this.attribute('visibility').value=='hidden') return;
-
var x = this.attribute('x').Length.toPixels('x');
var y = this.attribute('y').Length.toPixels('y');
-
for (var i=0; i<this.children.length; i++) {
var child = this.children[i];
@@ -1759,8 +1850,63 @@ if(!Array.indexOf){
this.base = svg.Element.RenderedElementBase;
this.base(node);
+ this.getGlyph = function(font, text, i) {
+ var c = text[i];
+ var glyph = null;
+ if (font.isArabic) {
+ var arabicForm = 'isolated';
+ if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal';
+ if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
+ if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
+ if (typeof(font.glyphs[c]) != 'undefined') {
+ glyph = font.glyphs[c][arabicForm];
+ if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
+ }
+ }
+ else {
+ glyph = font.glyphs[c];
+ }
+ if (glyph == null) glyph = font.missingGlyph;
+ return glyph;
+ }
+
this.renderChildren = function(ctx) {
- ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
+ var customFont = this.parent.style('font-family').Definition.getDefinition();
+ if (customFont != null) {
+ var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+ var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
+ var text = this.getText();
+ if (customFont.isRTL) text = text.split("").reverse().join("");
+
+ if (this.parent.style('text-anchor').value == 'middle') {
+ this.x = this.x - this.measureText(ctx) / 2.0;
+ }
+
+ var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
+ for (var i=0; i<text.length; i++) {
+ var glyph = this.getGlyph(customFont, text, i);
+ var scale = fontSize / customFont.fontFace.unitsPerEm;
+ ctx.translate(this.x, this.y);
+ ctx.scale(scale, -scale);
+ var lw = ctx.lineWidth;
+ ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
+ if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
+ glyph.render(ctx);
+ if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
+ ctx.lineWidth = lw;
+ ctx.scale(1/scale, -1/scale);
+ ctx.translate(-this.x, -this.y);
+
+ this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
+ if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
+ this.x += dx[i];
+ }
+ }
+ return;
+ }
+
+ if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
+ if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
}
this.getText = function() {
@@ -1768,6 +1914,23 @@ if(!Array.indexOf){
}
this.measureText = function(ctx) {
+ var customFont = this.parent.style('font-family').Definition.getDefinition();
+ if (customFont != null) {
+ var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+ var measure = 0;
+ var text = this.getText();
+ if (customFont.isRTL) text = text.split("").reverse().join("");
+ var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
+ for (var i=0; i<text.length; i++) {
+ var glyph = this.getGlyph(customFont, text, i);
+ measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
+ if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
+ measure += dx[i];
+ }
+ }
+ return measure;
+ }
+
var textToMeasure = svg.compressSpaces(this.getText());
if (!ctx.measureText) return textToMeasure.length * 10;
return ctx.measureText(textToMeasure).width;
@@ -1780,8 +1943,9 @@ if(!Array.indexOf){
this.base = svg.Element.TextElementBase;
this.base(node);
- // TEXT ELEMENT
- this.text = node.nodeType == 3 ? node.nodeValue : node.childNodes[0].nodeValue;
+ this.text = node.nodeType == 3 ? node.nodeValue : // text
+ node.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element
+ node.text;
this.getText = function() {
return this.text;
}
@@ -1931,8 +2095,9 @@ if(!Array.indexOf){
this.base = svg.Element.ElementBase;
this.base(node);
- var css = node.childNodes[0].nodeValue;
- css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gm, ''); // remove comments
+ // text, or spaces then CDATA
+ var css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');
+ css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
css = svg.compressSpaces(css); // replace whitespace
var cssDefs = css.split('}');
for (var i=0; i<cssDefs.length; i++) {
@@ -1945,14 +2110,31 @@ if(!Array.indexOf){
if (cssClass != '') {
var props = {};
for (var k=0; k<cssProps.length; k++) {
- var prop = cssProps[k].split(':');
- var name = prop[0];
- var value = prop[1];
+ var prop = cssProps[k].indexOf(':');
+ var name = cssProps[k].substr(0, prop);
+ var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
if (name != null && value != null) {
- props[svg.trim(prop[0])] = new svg.Property(svg.trim(prop[0]), svg.trim(prop[1]));
+ props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
}
}
svg.Styles[cssClass] = props;
+ if (cssClass == '@font-face') {
+ var fontFamily = props['font-family'].value.replace(/"/g,'');
+ var srcs = props['src'].value.split(',');
+ for (var s=0; s<srcs.length; s++) {
+ if (srcs[s].indexOf('format("svg")') > 0) {
+ var urlStart = srcs[s].indexOf('url');
+ var urlEnd = srcs[s].indexOf(')', urlStart);
+ var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
+ var doc = svg.parseXml(svg.ajax(url));
+ var fonts = doc.getElementsByTagName('font');
+ for (var f=0; f<fonts.length; f++) {
+ var font = svg.CreateElement(fonts[f]);
+ svg.Definitions[fontFamily] = font;
+ }
+ }
+ }
+ }
}
}
}
@@ -2004,6 +2186,10 @@ if(!Array.indexOf){
}
}
}
+
+ this.render = function(ctx) {
+ // NO RENDER
+ }
}
svg.Element.clipPath.prototype = new svg.Element.ElementBase;
@@ -2024,7 +2210,8 @@ if(!Array.indexOf){
// element factory
svg.CreateElement = function(node) {
- var className = node.nodeName.replace(/^[^:]+:/,'');
+ var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
+ className = className.replace(/\-/g,''); // remove dashes
var e = null;
if (typeof(svg.Element[className]) != 'undefined') {
e = new svg.Element[className](node);
@@ -2044,6 +2231,10 @@ if(!Array.indexOf){
// load from xml
svg.loadXml = function(ctx, xml) {
+ svg.loadXmlDoc(ctx, svg.parseXml(xml));
+ }
+
+ svg.loadXmlDoc = function(ctx, dom) {
svg.init(ctx);
var mapXY = function(p) {
@@ -2070,19 +2261,24 @@ if(!Array.indexOf){
};
}
- var dom = svg.parseXml(xml);
var e = svg.CreateElement(dom.documentElement);
+ e.root = true;
// render loop
var isFirstRender = true;
var draw = function() {
+ svg.ViewPort.Clear();
+ if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
+
if (svg.opts == null || svg.opts['ignoreDimensions'] != true) {
// set canvas size
if (e.style('width').hasValue()) {
- ctx.canvas.width = e.style('width').Length.toPixels(ctx.canvas.parentNode.clientWidth);
+ ctx.canvas.width = e.style('width').Length.toPixels('x');
+ ctx.canvas.style.width = ctx.canvas.width + 'px';
}
if (e.style('height').hasValue()) {
- ctx.canvas.height = e.style('height').Length.toPixels(ctx.canvas.parentNode.clientHeight);
+ ctx.canvas.height = e.style('height').Length.toPixels('y');
+ ctx.canvas.style.height = ctx.canvas.height + 'px';
}
}
svg.ViewPort.SetCurrent(ctx.canvas.clientWidth, ctx.canvas.clientHeight);
diff --git a/js/canvg/flashcanvas.js b/js/canvg/flashcanvas.js
new file mode 100644
index 0000000..6ecaa94
--- /dev/null
+++ b/js/canvg/flashcanvas.js
@@ -0,0 +1,1093 @@
+/*
+ * FlashCanvas
+ *
+ * Copyright (c) 2009 Tim Cameron Ryan
+ * Copyright (c) 2009-2011 FlashCanvas Project
+ * Released under the MIT/X License
+ */
+
+// Reference:
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-elem…
+// http://dev.w3.org/html5/spec/the-canvas-element.html
+
+// If the browser is IE and does not support HTML5 Canvas
+if (window["ActiveXObject"] && !window["CanvasRenderingContext2D"]) {
+
+(function(window, document, undefined) {
+
+/*
+ * Constant
+ */
+
+var NULL = null;
+var CANVAS = "canvas";
+var CANVAS_RENDERING_CONTEXT_2D = "CanvasRenderingContext2D";
+var CANVAS_GRADIENT = "CanvasGradient";
+var CANVAS_PATTERN = "CanvasPattern";
+var FLASH_CANVAS = "FlashCanvas";
+var G_VML_CANVAS_MANAGER = "G_vmlCanvasManager";
+var OBJECT_ID_PREFIX = "external";
+var ON_FOCUS = "onfocus";
+var ON_PROPERTY_CHANGE = "onpropertychange";
+var ON_READY_STATE_CHANGE = "onreadystatechange";
+var ON_UNLOAD = "onunload";
+
+var config = window[FLASH_CANVAS + "Options"] || {};
+var BASE_URL = config["swfPath"] || getScriptUrl().replace(/[^\/]+$/, "");
+var SWF_URL = BASE_URL + "flashcanvas.swf";
+
+// DOMException code
+var INDEX_SIZE_ERR = 1;
+var NOT_SUPPORTED_ERR = 9;
+var INVALID_STATE_ERR = 11;
+var SYNTAX_ERR = 12;
+var TYPE_MISMATCH_ERR = 17;
+var SECURITY_ERR = 18;
+
+/**
+ * @constructor
+ */
+function Lookup(array) {
+ for (var i = 0, n = array.length; i < n; i++)
+ this[array[i]] = i;
+}
+
+var properties = new Lookup([
+ // Canvas element
+ "toDataURL",
+
+ // CanvasRenderingContext2D
+ "save",
+ "restore",
+ "scale",
+ "rotate",
+ "translate",
+ "transform",
+ "setTransform",
+ "globalAlpha",
+ "globalCompositeOperation",
+ "strokeStyle",
+ "fillStyle",
+ "createLinearGradient",
+ "createRadialGradient",
+ "createPattern",
+ "lineWidth",
+ "lineCap",
+ "lineJoin",
+ "miterLimit",
+ "shadowOffsetX",
+ "shadowOffsetY",
+ "shadowBlur",
+ "shadowColor",
+ "clearRect",
+ "fillRect",
+ "strokeRect",
+ "beginPath",
+ "closePath",
+ "moveTo",
+ "lineTo",
+ "quadraticCurveTo",
+ "bezierCurveTo",
+ "arcTo",
+ "rect",
+ "arc",
+ "fill",
+ "stroke",
+ "clip",
+ "isPointInPath",
+// "drawFocusRing",
+ "font",
+ "textAlign",
+ "textBaseline",
+ "fillText",
+ "strokeText",
+ "measureText",
+ "drawImage",
+ "createImageData",
+ "getImageData",
+ "putImageData",
+
+ // CanvasGradient
+ "addColorStop",
+
+ // Internal use
+ "direction",
+ "resize"
+]);
+
+// Whether swf is ready for use
+var isReady = {};
+
+// Monitor the number of loading files
+var lock = {};
+
+// Canvas elements
+var canvases = {};
+
+// SPAN element embedded in the canvas
+var spans = {};
+
+/**
+ * 2D context
+ * @constructor
+ */
+var CanvasRenderingContext2D = function(canvas, swf) {
+ // back-reference to the canvas
+ this.canvas = canvas;
+
+ // back-reference to the swf
+ this._swf = swf;
+
+ // unique ID of canvas
+ this._canvasId = swf.id.slice(8);
+
+ // initialize drawing states
+ this._initialize();
+
+ // Count CanvasGradient and CanvasPattern objects
+ this._gradientPatternId = 0;
+
+ // Directionality of the canvas element
+ this._direction = "";
+
+ // frame update interval
+ var self = this;
+ setInterval(function() {
+ if (lock[self._canvasId] === 0) {
+ self._executeCommand();
+ }
+ }, 30);
+};
+
+CanvasRenderingContext2D.prototype = {
+ /*
+ * state
+ */
+
+ save: function() {
+ // write all properties
+ this._setCompositing();
+ this._setShadows();
+ this._setStrokeStyle();
+ this._setFillStyle();
+ this._setLineStyles();
+ this._setFontStyles();
+
+ // push state
+ this._stateStack.push([
+ this._globalAlpha,
+ this._globalCompositeOperation,
+ this._strokeStyle,
+ this._fillStyle,
+ this._lineWidth,
+ this._lineCap,
+ this._lineJoin,
+ this._miterLimit,
+ this._shadowOffsetX,
+ this._shadowOffsetY,
+ this._shadowBlur,
+ this._shadowColor,
+ this._font,
+ this._textAlign,
+ this._textBaseline
+ ]);
+
+ this._queue.push(properties.save);
+ },
+
+ restore: function() {
+ // pop state
+ var stateStack = this._stateStack;
+ if (stateStack.length) {
+ var state = stateStack.pop();
+ this.globalAlpha = state[0];
+ this.globalCompositeOperation = state[1];
+ this.strokeStyle = state[2];
+ this.fillStyle = state[3];
+ this.lineWidth = state[4];
+ this.lineCap = state[5];
+ this.lineJoin = state[6];
+ this.miterLimit = state[7];
+ this.shadowOffsetX = state[8];
+ this.shadowOffsetY = state[9];
+ this.shadowBlur = state[10];
+ this.shadowColor = state[11];
+ this.font = state[12];
+ this.textAlign = state[13];
+ this.textBaseline = state[14];
+ }
+
+ this._queue.push(properties.restore);
+ },
+
+ /*
+ * transformations
+ */
+
+ scale: function(x, y) {
+ this._queue.push(properties.scale, x, y);
+ },
+
+ rotate: function(angle) {
+ this._queue.push(properties.rotate, angle);
+ },
+
+ translate: function(x, y) {
+ this._queue.push(properties.translate, x, y);
+ },
+
+ transform: function(m11, m12, m21, m22, dx, dy) {
+ this._queue.push(properties.transform, m11, m12, m21, m22, dx, dy);
+ },
+
+ setTransform: function(m11, m12, m21, m22, dx, dy) {
+ this._queue.push(properties.setTransform, m11, m12, m21, m22, dx, dy);
+ },
+
+ /*
+ * compositing
+ */
+
+ _setCompositing: function() {
+ var queue = this._queue;
+ if (this._globalAlpha !== this.globalAlpha) {
+ this._globalAlpha = this.globalAlpha;
+ queue.push(properties.globalAlpha, this._globalAlpha);
+ }
+ if (this._globalCompositeOperation !== this.globalCompositeOperation) {
+ this._globalCompositeOperation = this.globalCompositeOperation;
+ queue.push(properties.globalCompositeOperation, this._globalCompositeOperation);
+ }
+ },
+
+ /*
+ * colors and styles
+ */
+
+ _setStrokeStyle: function() {
+ if (this._strokeStyle !== this.strokeStyle) {
+ var style = this._strokeStyle = this.strokeStyle;
+ this._queue.push(properties.strokeStyle, (typeof style === "object") ? style.id : style);
+ }
+ },
+
+ _setFillStyle: function() {
+ if (this._fillStyle !== this.fillStyle) {
+ var style = this._fillStyle = this.fillStyle;
+ this._queue.push(properties.fillStyle, (typeof style === "object") ? style.id : style);
+ }
+ },
+
+ createLinearGradient: function(x0, y0, x1, y1) {
+ // If any of the arguments are not finite numbers, throws a
+ // NOT_SUPPORTED_ERR exception.
+ if (!(isFinite(x0) && isFinite(y0) && isFinite(x1) && isFinite(y1))) {
+ throwException(NOT_SUPPORTED_ERR);
+ }
+
+ this._queue.push(properties.createLinearGradient, x0, y0, x1, y1);
+ return new CanvasGradient(this);
+ },
+
+ createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
+ // If any of the arguments are not finite numbers, throws a
+ // NOT_SUPPORTED_ERR exception.
+ if (!(isFinite(x0) && isFinite(y0) && isFinite(r0) &&
+ isFinite(x1) && isFinite(y1) && isFinite(r1))) {
+ throwException(NOT_SUPPORTED_ERR);
+ }
+
+ // If either of the radii are negative, throws an INDEX_SIZE_ERR
+ // exception.
+ if (r0 < 0 || r1 < 0) {
+ throwException(INDEX_SIZE_ERR);
+ }
+
+ this._queue.push(properties.createRadialGradient, x0, y0, r0, x1, y1, r1);
+ return new CanvasGradient(this);
+ },
+
+ createPattern: function(image, repetition) {
+ // If the image is null, the implementation must raise a
+ // TYPE_MISMATCH_ERR exception.
+ if (!image) {
+ throwException(TYPE_MISMATCH_ERR);
+ }
+
+ var tagName = image.tagName, src;
+ var canvasId = this._canvasId;
+
+ // If the first argument isn't an img, canvas, or video element,
+ // throws a TYPE_MISMATCH_ERR exception.
+ if (tagName) {
+ tagName = tagName.toLowerCase();
+ if (tagName === "img") {
+ src = image.getAttribute("src", 2);
+ } else if (tagName === CANVAS || tagName === "video") {
+ // For now, only HTMLImageElement is supported.
+ return;
+ } else {
+ throwException(TYPE_MISMATCH_ERR);
+ }
+ }
+
+ // Additionally, we accept any object that has a src property.
+ // This is useful when you'd like to specify a long data URI.
+ else if (image.src) {
+ src = image.src;
+ } else {
+ throwException(TYPE_MISMATCH_ERR);
+ }
+
+ // If the second argument isn't one of the allowed values, throws a
+ // SYNTAX_ERR exception.
+ if (!(repetition === "repeat" || repetition === "no-repeat" ||
+ repetition === "repeat-x" || repetition === "repeat-y" ||
+ repetition === "" || repetition === NULL)) {
+ throwException(SYNTAX_ERR);
+ }
+
+ // Special characters in the filename need escaping.
+ this._queue.push(properties.createPattern, encodeXML(src), repetition);
+
+ if (isReady[canvasId]) {
+ this._executeCommand();
+ ++lock[canvasId];
+ }
+
+ return new CanvasPattern(this);
+ },
+
+ /*
+ * line caps/joins
+ */
+
+ _setLineStyles: function() {
+ var queue = this._queue;
+ if (this._lineWidth !== this.lineWidth) {
+ this._lineWidth = this.lineWidth;
+ queue.push(properties.lineWidth, this._lineWidth);
+ }
+ if (this._lineCap !== this.lineCap) {
+ this._lineCap = this.lineCap;
+ queue.push(properties.lineCap, this._lineCap);
+ }
+ if (this._lineJoin !== this.lineJoin) {
+ this._lineJoin = this.lineJoin;
+ queue.push(properties.lineJoin, this._lineJoin);
+ }
+ if (this._miterLimit !== this.miterLimit) {
+ this._miterLimit = this.miterLimit;
+ queue.push(properties.miterLimit, this._miterLimit);
+ }
+ },
+
+ /*
+ * shadows
+ */
+
+ _setShadows: function() {
+ var queue = this._queue;
+ if (this._shadowOffsetX !== this.shadowOffsetX) {
+ this._shadowOffsetX = this.shadowOffsetX;
+ queue.push(properties.shadowOffsetX, this._shadowOffsetX);
+ }
+ if (this._shadowOffsetY !== this.shadowOffsetY) {
+ this._shadowOffsetY = this.shadowOffsetY;
+ queue.push(properties.shadowOffsetY, this._shadowOffsetY);
+ }
+ if (this._shadowBlur !== this.shadowBlur) {
+ this._shadowBlur = this.shadowBlur;
+ queue.push(properties.shadowBlur, this._shadowBlur);
+ }
+ if (this._shadowColor !== this.shadowColor) {
+ this._shadowColor = this.shadowColor;
+ queue.push(properties.shadowColor, this._shadowColor);
+ }
+ },
+
+ /*
+ * rects
+ */
+
+ clearRect: function(x, y, w, h) {
+ this._queue.push(properties.clearRect, x, y, w, h);
+ },
+
+ fillRect: function(x, y, w, h) {
+ this._setCompositing();
+ this._setShadows();
+ this._setFillStyle();
+ this._queue.push(properties.fillRect, x, y, w, h);
+ },
+
+ strokeRect: function(x, y, w, h) {
+ this._setCompositing();
+ this._setShadows();
+ this._setStrokeStyle();
+ this._setLineStyles();
+ this._queue.push(properties.strokeRect, x, y, w, h);
+ },
+
+ /*
+ * path API
+ */
+
+ beginPath: function() {
+ this._queue.push(properties.beginPath);
+ },
+
+ closePath: function() {
+ this._queue.push(properties.closePath);
+ },
+
+ moveTo: function(x, y) {
+ this._queue.push(properties.moveTo, x, y);
+ },
+
+ lineTo: function(x, y) {
+ this._queue.push(properties.lineTo, x, y);
+ },
+
+ quadraticCurveTo: function(cpx, cpy, x, y) {
+ this._queue.push(properties.quadraticCurveTo, cpx, cpy, x, y);
+ },
+
+ bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y) {
+ this._queue.push(properties.bezierCurveTo, cp1x, cp1y, cp2x, cp2y, x, y);
+ },
+
+ arcTo: function(x1, y1, x2, y2, radius) {
+ // Throws an INDEX_SIZE_ERR exception if the given radius is negative.
+ if (radius < 0 && isFinite(radius)) {
+ throwException(INDEX_SIZE_ERR);
+ }
+
+ this._queue.push(properties.arcTo, x1, y1, x2, y2, radius);
+ },
+
+ rect: function(x, y, w, h) {
+ this._queue.push(properties.rect, x, y, w, h);
+ },
+
+ arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
+ // Throws an INDEX_SIZE_ERR exception if the given radius is negative.
+ if (radius < 0 && isFinite(radius)) {
+ throwException(INDEX_SIZE_ERR);
+ }
+
+ this._queue.push(properties.arc, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0);
+ },
+
+ fill: function() {
+ this._setCompositing();
+ this._setShadows();
+ this._setFillStyle();
+ this._queue.push(properties.fill);
+ },
+
+ stroke: function() {
+ this._setCompositing();
+ this._setShadows();
+ this._setStrokeStyle();
+ this._setLineStyles();
+ this._queue.push(properties.stroke);
+ },
+
+ clip: function() {
+ this._queue.push(properties.clip);
+ },
+
+ isPointInPath: function(x, y) {
+ // TODO: Implement
+ },
+
+ /*
+ * text
+ */
+
+ _setFontStyles: function() {
+ var queue = this._queue;
+ if (this._font !== this.font) {
+ try {
+ var span = spans[this._canvasId];
+ span.style.font = this._font = this.font;
+
+ var style = span.currentStyle;
+ var fontSize = span.offsetHeight;
+ var font = [style.fontStyle, style.fontWeight, fontSize, style.fontFamily].join(" ");
+ queue.push(properties.font, font);
+ } catch(e) {
+ // If this.font cannot be parsed as a CSS font value, then it
+ // must be ignored.
+ }
+ }
+ if (this._textAlign !== this.textAlign) {
+ this._textAlign = this.textAlign;
+ queue.push(properties.textAlign, this._textAlign);
+ }
+ if (this._textBaseline !== this.textBaseline) {
+ this._textBaseline = this.textBaseline;
+ queue.push(properties.textBaseline, this._textBaseline);
+ }
+ if (this._direction !== this.canvas.currentStyle.direction) {
+ this._direction = this.canvas.currentStyle.direction;
+ queue.push(properties.direction, this._direction);
+ }
+ },
+
+ fillText: function(text, x, y, maxWidth) {
+ this._setCompositing();
+ this._setFillStyle();
+ this._setShadows();
+ this._setFontStyles();
+ this._queue.push(properties.fillText, encodeXML(text), x, y,
+ maxWidth === undefined ? Infinity : maxWidth);
+ },
+
+ strokeText: function(text, x, y, maxWidth) {
+ this._setCompositing();
+ this._setStrokeStyle();
+ this._setShadows();
+ this._setFontStyles();
+ this._queue.push(properties.strokeText, encodeXML(text), x, y,
+ maxWidth === undefined ? Infinity : maxWidth);
+ },
+
+ measureText: function(text) {
+ var span = spans[this._canvasId];
+ try {
+ span.style.font = this.font;
+ } catch(e) {
+ // If this.font cannot be parsed as a CSS font value, then it must
+ // be ignored.
+ }
+
+ // Replace space characters with tab characters because innerText
+ // removes trailing white spaces.
+ span.innerText = text.replace(/[ \n\f\r]/g, "\t");
+
+ return new TextMetrics(span.offsetWidth);
+ },
+
+ /*
+ * drawing images
+ */
+
+ drawImage: function(image, x1, y1, w1, h1, x2, y2, w2, h2) {
+ // If the image is null, the implementation must raise a
+ // TYPE_MISMATCH_ERR exception.
+ if (!image) {
+ throwException(TYPE_MISMATCH_ERR);
+ }
+
+ var tagName = image.tagName, src, argc = arguments.length;
+ var canvasId = this._canvasId;
+
+ // If the first argument isn't an img, canvas, or video element,
+ // throws a TYPE_MISMATCH_ERR exception.
+ if (tagName) {
+ tagName = tagName.toLowerCase();
+ if (tagName === "img") {
+ src = image.getAttribute("src", 2);
+ } else if (tagName === CANVAS || tagName === "video") {
+ // For now, only HTMLImageElement is supported.
+ return;
+ } else {
+ throwException(TYPE_MISMATCH_ERR);
+ }
+ }
+
+ // Additionally, we accept any object that has a src property.
+ // This is useful when you'd like to specify a long data URI.
+ else if (image.src) {
+ src = image.src;
+ } else {
+ throwException(TYPE_MISMATCH_ERR);
+ }
+
+ this._setCompositing();
+ this._setShadows();
+
+ // Special characters in the filename need escaping.
+ src = encodeXML(src);
+
+ if (argc === 3) {
+ this._queue.push(properties.drawImage, argc, src, x1, y1);
+ } else if (argc === 5) {
+ this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1);
+ } else if (argc === 9) {
+ // If one of the sw or sh arguments is zero, the implementation
+ // must raise an INDEX_SIZE_ERR exception.
+ if (w1 === 0 || h1 === 0) {
+ throwException(INDEX_SIZE_ERR);
+ }
+
+ this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1, x2, y2, w2, h2);
+ } else {
+ return;
+ }
+
+ if (isReady[canvasId]) {
+ this._executeCommand();
+ ++lock[canvasId];
+ }
+ },
+
+ /*
+ * pixel manipulation
+ */
+
+ // ImageData createImageData(in float sw, in float sh);
+ // ImageData createImageData(in ImageData imagedata);
+ createImageData: function() {
+ // TODO: Implement
+ },
+
+ // ImageData getImageData(in float sx, in float sy, in float sw, in float sh);
+ getImageData: function(sx, sy, sw, sh) {
+ // TODO: Implement
+ },
+
+ // void putImageData(in ImageData imagedata, in float dx, in float dy, [Optional] in float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight);
+ putImageData: function(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
+ // TODO: Implement
+ },
+
+ /*
+ * private methods
+ */
+
+ _initialize: function() {
+ // compositing
+ this.globalAlpha = this._globalAlpha = 1.0;
+ this.globalCompositeOperation = this._globalCompositeOperation = "source-over";
+
+ // colors and styles
+ this.strokeStyle = this._strokeStyle = "#000000";
+ this.fillStyle = this._fillStyle = "#000000";
+
+ // line caps/joins
+ this.lineWidth = this._lineWidth = 1.0;
+ this.lineCap = this._lineCap = "butt";
+ this.lineJoin = this._lineJoin = "miter";
+ this.miterLimit = this._miterLimit = 10.0;
+
+ // shadows
+ this.shadowOffsetX = this._shadowOffsetX = 0;
+ this.shadowOffsetY = this._shadowOffsetY = 0;
+ this.shadowBlur = this._shadowBlur = 0;
+ this.shadowColor = this._shadowColor = "rgba(0, 0, 0, 0.0)";
+
+ // text
+ this.font = this._font = "10px sans-serif";
+ this.textAlign = this._textAlign = "start";
+ this.textBaseline = this._textBaseline = "alphabetic";
+
+ // command queue
+ this._queue = [];
+
+ // stack of drawing states
+ this._stateStack = [];
+ },
+
+ _flush: function() {
+ var queue = this._queue;
+ this._queue = [];
+ return queue;
+ },
+
+ _executeCommand: function() {
+ // execute commands
+ var commands = this._flush();
+ if (commands.length > 0) {
+ return eval(this._swf.CallFunction(
+ '<invoke name="executeCommand" returntype="javascript"><arguments><string>'
+ + commands.join("�") + "</string></arguments></invoke>"
+ ));
+ }
+ },
+
+ _resize: function(width, height) {
+ // Flush commands in the queue
+ this._executeCommand();
+
+ // Clear back to the initial state
+ this._initialize();
+
+ // Adjust the size of Flash to that of the canvas
+ if (width > 0) {
+ this._swf.width = width;
+ }
+ if (height > 0) {
+ this._swf.height = height;
+ }
+
+ // Execute a resize command at the start of the next frame
+ this._queue.push(properties.resize, width, height);
+ }
+};
+
+/**
+ * CanvasGradient stub
+ * @constructor
+ */
+var CanvasGradient = function(ctx) {
+ this._ctx = ctx;
+ this.id = ctx._gradientPatternId++;
+};
+
+CanvasGradient.prototype = {
+ addColorStop: function(offset, color) {
+ // Throws an INDEX_SIZE_ERR exception if the offset is out of range.
+ if (isNaN(offset) || offset < 0 || offset > 1) {
+ throwException(INDEX_SIZE_ERR);
+ }
+
+ this._ctx._queue.push(properties.addColorStop, this.id, offset, color);
+ }
+};
+
+/**
+ * CanvasPattern stub
+ * @constructor
+ */
+var CanvasPattern = function(ctx) {
+ this.id = ctx._gradientPatternId++;
+};
+
+/**
+ * TextMetrics stub
+ * @constructor
+ */
+var TextMetrics = function(width) {
+ this.width = width;
+};
+
+/**
+ * DOMException
+ * @constructor
+ */
+var DOMException = function(code) {
+ this.code = code;
+ this.message = DOMExceptionNames[code];
+};
+
+DOMException.prototype = new Error;
+
+var DOMExceptionNames = {
+ 1: "INDEX_SIZE_ERR",
+ 9: "NOT_SUPPORTED_ERR",
+ 11: "INVALID_STATE_ERR",
+ 12: "SYNTAX_ERR",
+ 17: "TYPE_MISMATCH_ERR",
+ 18: "SECURITY_ERR"
+};
+
+/*
+ * Event handlers
+ */
+
+function onReadyStateChange() {
+ if (document.readyState === "complete") {
+ document.detachEvent(ON_READY_STATE_CHANGE, onReadyStateChange);
+
+ var canvases = document.getElementsByTagName(CANVAS);
+ for (var i = 0, n = canvases.length; i < n; ++i) {
+ FlashCanvas.initElement(canvases[i]);
+ }
+ }
+}
+
+function onFocus() {
+ // forward the event to the parent
+ var swf = event.srcElement, canvas = swf.parentNode;
+ swf.blur();
+ canvas.focus();
+}
+
+function onPropertyChange() {
+ var prop = event.propertyName;
+ if (prop === "width" || prop === "height") {
+ var canvas = event.srcElement;
+ var value = canvas[prop];
+ var number = parseInt(value, 10);
+
+ if (isNaN(number) || number < 0) {
+ number = (prop === "width") ? 300 : 150;
+ }
+
+ if (value === number) {
+ canvas.style[prop] = number + "px";
+ canvas.getContext("2d")._resize(canvas.width, canvas.height);
+ } else {
+ canvas[prop] = number;
+ }
+ }
+}
+
+function onUnload() {
+ window.detachEvent(ON_UNLOAD, onUnload);
+
+ for (var canvasId in canvases) {
+ var canvas = canvases[canvasId], swf = canvas.firstChild, prop;
+
+ // clean up the references of swf.executeCommand and swf.resize
+ for (prop in swf) {
+ if (typeof swf[prop] === "function") {
+ swf[prop] = NULL;
+ }
+ }
+
+ // clean up the references of canvas.getContext and canvas.toDataURL
+ for (prop in canvas) {
+ if (typeof canvas[prop] === "function") {
+ canvas[prop] = NULL;
+ }
+ }
+
+ // remove event listeners
+ swf.detachEvent(ON_FOCUS, onFocus);
+ canvas.detachEvent(ON_PROPERTY_CHANGE, onPropertyChange);
+ }
+
+ // delete exported symbols
+ window[CANVAS_RENDERING_CONTEXT_2D] = NULL;
+ window[CANVAS_GRADIENT] = NULL;
+ window[CANVAS_PATTERN] = NULL;
+ window[FLASH_CANVAS] = NULL;
+ window[G_VML_CANVAS_MANAGER] = NULL;
+}
+
+/*
+ * FlashCanvas API
+ */
+
+var FlashCanvas = {
+ initElement: function(canvas) {
+ // Check whether the initialization is required or not.
+ if (canvas.getContext) {
+ return canvas;
+ }
+
+ // initialize lock
+ var canvasId = getUniqueId();
+ var objectId = OBJECT_ID_PREFIX + canvasId;
+ isReady[canvasId] = false;
+ lock[canvasId] = 1;
+
+ // Set the width and height attributes.
+ setCanvasSize(canvas);
+
+ // embed swf and SPAN element
+ canvas.innerHTML =
+ '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"' +
+ ' codebase="' + location.protocol + '//fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0"' +
+ ' width="100%" height="100%" id="' + objectId + '">' +
+ '<param name="allowScriptAccess" value="always">' +
+ '<param name="flashvars" value="id=' + objectId + '">' +
+ '<param name="wmode" value="transparent">' +
+ '</object>' +
+ '<span style="margin:0;padding:0;border:0;display:inline-block;position:static;height:1em;overflow:visible;white-space:nowrap">' +
+ '</span>';
+
+ canvases[canvasId] = canvas;
+ var swf = canvas.firstChild;
+ spans[canvasId] = canvas.lastChild;
+
+ // Check whether the canvas element is in the DOM tree
+ var documentContains = document.body.contains;
+ if (documentContains(canvas)) {
+ // Load swf file immediately
+ swf["movie"] = SWF_URL;
+ } else {
+ // Wait until the element is added to the DOM tree
+ var intervalId = setInterval(function() {
+ if (documentContains(canvas)) {
+ clearInterval(intervalId);
+ swf["movie"] = SWF_URL;
+ }
+ }, 0);
+ }
+
+ // If the browser is IE6 or in quirks mode
+ if (document.compatMode === "BackCompat" || !window.XMLHttpRequest) {
+ spans[canvasId].style.overflow = "hidden";
+ }
+
+ // initialize context
+ var ctx = new CanvasRenderingContext2D(canvas, swf);
+
+ // canvas API
+ canvas.getContext = function(contextId) {
+ return contextId === "2d" ? ctx : NULL;
+ };
+
+ canvas.toDataURL = function(type, quality) {
+ if (("" + type).replace(/[A-Z]+/g, toLowerCase) === "image/jpeg") {
+ ctx._queue.push(properties.toDataURL, type,
+ typeof quality === "number" ? quality : "");
+ } else {
+ ctx._queue.push(properties.toDataURL, type);
+ }
+ return ctx._executeCommand();
+ };
+
+ // add event listener
+ swf.attachEvent(ON_FOCUS, onFocus);
+
+ return canvas;
+ },
+
+ saveImage: function(canvas) {
+ var swf = canvas.firstChild;
+ swf.saveImage();
+ },
+
+ setOptions: function(options) {
+ // TODO: Implement
+ },
+
+ trigger: function(canvasId, type) {
+ var canvas = canvases[canvasId];
+ canvas.fireEvent("on" + type);
+ },
+
+ unlock: function(canvasId, ready) {
+ if (lock[canvasId]) {
+ --lock[canvasId];
+ }
+ if (ready) {
+ var canvas = canvases[canvasId];
+ var swf = canvas.firstChild;
+ var width;
+ var height;
+
+ // Set the width and height attributes of the canvas element.
+ setCanvasSize(canvas);
+ width = canvas.width;
+ height = canvas.height;
+
+ canvas.style.width = width + "px";
+ canvas.style.height = height + "px";
+
+ // Adjust the size of Flash to that of the canvas
+ if (width > 0) {
+ swf.width = width;
+ }
+ if (height > 0) {
+ swf.height = height;
+ }
+ swf.resize(width, height);
+
+ // Add event listener
+ canvas.attachEvent(ON_PROPERTY_CHANGE, onPropertyChange);
+
+ // ExternalInterface is now ready for use
+ isReady[canvasId] = true;
+ }
+ }
+};
+
+/*
+ * Utility methods
+ */
+
+// Get the absolute URL of flashcanvas.js
+function getScriptUrl() {
+ var scripts = document.getElementsByTagName("script");
+ var script = scripts[scripts.length - 1];
+
+ // @see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
+ if (document.documentMode >= 8) {
+ return script.src;
+ } else {
+ return script.getAttribute("src", 4);
+ }
+}
+
+// Get a unique ID composed of alphanumeric characters.
+function getUniqueId() {
+ return Math.random().toString(36).slice(2) || "0";
+}
+
+// Escape characters not permitted in XML.
+function encodeXML(str) {
+ return ("" + str).replace(/&/g, "&").replace(/</g, "<");
+}
+
+function toLowerCase(str) {
+ return str.toLowerCase();
+}
+
+function throwException(code) {
+ throw new DOMException(code);
+}
+
+// The width and height attributes of a canvas element must have values that
+// are valid non-negative integers.
+function setCanvasSize(canvas) {
+ var width = parseInt(canvas.width, 10);
+ var height = parseInt(canvas.height, 10);
+
+ if (isNaN(width) || width < 0) {
+ width = 300;
+ }
+ if (isNaN(height) || height < 0) {
+ height = 150;
+ }
+
+ canvas.width = width;
+ canvas.height = height;
+}
+
+/*
+ * initialization
+ */
+
+// IE HTML5 shiv
+document.createElement(CANVAS);
+
+// setup default CSS
+document.createStyleSheet().cssText =
+ CANVAS + "{display:inline-block;overflow:hidden;width:300px;height:150px}";
+
+// initialize canvas elements
+if (document.readyState === "complete") {
+ onReadyStateChange();
+} else {
+ document.attachEvent(ON_READY_STATE_CHANGE, onReadyStateChange);
+}
+
+// prevent IE6 memory leaks
+window.attachEvent(ON_UNLOAD, onUnload);
+
+// preload SWF file if it's in the same domain
+if (SWF_URL.indexOf(location.protocol + "//" + location.host + "/") === 0) {
+ var req = new ActiveXObject("Microsoft.XMLHTTP");
+ req.open("GET", SWF_URL, false);
+ req.send(NULL);
+}
+
+/*
+ * public API
+ */
+
+window[CANVAS_RENDERING_CONTEXT_2D] = CanvasRenderingContext2D;
+window[CANVAS_GRADIENT] = CanvasGradient;
+window[CANVAS_PATTERN] = CanvasPattern;
+window[FLASH_CANVAS] = FlashCanvas;
+
+// ExplorerCanvas-compatible APIs for convenience
+window[G_VML_CANVAS_MANAGER] = {
+ init: function(){},
+ init_: function(){},
+ initElement: FlashCanvas.initElement
+};
+
+// Prevent Closure Compiler from removing the function.
+keep = CanvasRenderingContext2D.measureText;
+
+})(window, document);
+
+}
diff --git a/js/canvg/flashcanvas.swf b/js/canvg/flashcanvas.swf
new file mode 100644
index 0000000..66ff213
Binary files /dev/null and b/js/canvg/flashcanvas.swf differ
diff --git a/js/highcharts/exporting.js b/js/highcharts/exporting.js
index 4f134d5..036c6ae 100644
--- a/js/highcharts/exporting.js
+++ b/js/highcharts/exporting.js
@@ -308,6 +308,10 @@ extend(Chart.prototype, {
chart = this,
canvas=createElement('canvas');
+ if (typeof FlashCanvas != "undefined") {
+ FlashCanvas.initElement(canvas);
+ }
+
$('body').append(canvas);
$(canvas).hide();
diff --git a/js/highcharts/highcharts.js b/js/highcharts/highcharts.js
index c15862b..4ad0c6c 100644
--- a/js/highcharts/highcharts.js
+++ b/js/highcharts/highcharts.js
@@ -2210,7 +2210,7 @@ SVGRenderer.prototype = {
if (lineNo) {
// Webkit and opera sometimes return 'normal' as the line height. In that
// case, webkit uses offsetHeight, while Opera falls back to 18
- lineHeight = pInt(window.getComputedStyle(lastLine, null).getPropertyValue('line-height'));
+ if(window.getComputedStyle) lineHeight = pInt(window.getComputedStyle(lastLine, null).getPropertyValue('line-height'));
if (isNaN(lineHeight)) {
lineHeight = textLineHeight || lastLine.offsetHeight || 18;
}
diff --git a/po/en_GB.po b/po/en_GB.po
index ec6b80a..2c6d439 100644
--- a/po/en_GB.po
+++ b/po/en_GB.po
@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: phpMyAdmin 3.5.0-dev\n"
"Report-Msgid-Bugs-To: phpmyadmin-devel(a)lists.sourceforge.net\n"
"POT-Creation-Date: 2011-06-14 17:22+0200\n"
-"PO-Revision-Date: 2011-06-09 22:12+0200\n"
+"PO-Revision-Date: 2011-06-15 02:35+0200\n"
"Last-Translator: Marc Delisle <marc(a)infomarc.info>\n"
"Language-Team: english-gb <en_GB(a)li.org>\n"
"Language: en_GB\n"
@@ -1005,10 +1005,9 @@ msgstr "This is not a number!"
#. l10n: Default description for the y-Axis of Charts
#: js/messages.php:51
-#, fuzzy
#| msgid "Log file count"
msgid "Total count"
-msgstr "Log file count"
+msgstr "Total count"
#: js/messages.php:54
msgid "The host name is empty!"
@@ -1045,24 +1044,22 @@ msgid "Close"
msgstr "Close"
#: js/messages.php:64 server_status.php:393
-#, fuzzy
#| msgid "Server Choice"
msgid "Live traffic chart"
-msgstr "Server Choice"
+msgstr "Live traffic chart"
#: js/messages.php:65 server_status.php:396
msgid "Live conn./process chart"
-msgstr ""
+msgstr "Live conn./process chart"
#: js/messages.php:66 server_status.php:423
-#, fuzzy
#| msgid "Show query chart"
msgid "Live query chart"
-msgstr "Show query chart"
+msgstr "Live query chart"
#: js/messages.php:68
msgid "Static data"
-msgstr ""
+msgstr "Static data"
#. l10n: Total number of queries
#: js/messages.php:70 libraries/build_html_for_db.lib.php:45
@@ -1075,7 +1072,7 @@ msgstr "Total"
#. l10n: Other, small valued, queries
#: js/messages.php:72 server_status.php:586
msgid "Other"
-msgstr ""
+msgstr "Other"
#. l10n: Thousands separator
#: js/messages.php:74 libraries/common.lib.php:1359
@@ -8212,71 +8209,62 @@ msgid "Runtime Information"
msgstr "Runtime Information"
#: server_status.php:367
-#, fuzzy
#| msgid "Server Choice"
msgid "Server traffic"
-msgstr "Server Choice"
+msgstr "Server traffic"
#: server_status.php:368
msgid "Query statistics"
msgstr "Query statistics"
#: server_status.php:369
-#, fuzzy
#| msgid "See slave status table"
msgid "All status variables"
-msgstr "See slave status table"
+msgstr "All status variables"
#: server_status.php:379 server_status.php:410
-#, fuzzy
#| msgid "Refresh"
msgid "Refresh rate"
-msgstr "Refresh"
+msgstr "Refresh rate"
#: server_status.php:380 server_status.php:411
-#, fuzzy
#| msgid "Second"
msgid "second"
-msgstr "Second"
+msgstr "second"
#: server_status.php:381 server_status.php:382 server_status.php:383
#: server_status.php:384 server_status.php:385 server_status.php:412
#: server_status.php:413 server_status.php:414 server_status.php:415
#: server_status.php:416
-#, fuzzy
#| msgid "Second"
msgid "seconds"
-msgstr "Second"
+msgstr "seconds"
#: server_status.php:386 server_status.php:387 server_status.php:388
#: server_status.php:389 server_status.php:417 server_status.php:418
#: server_status.php:419 server_status.php:420
-#, fuzzy
#| msgid "Minute"
msgid "minutes"
-msgstr "Minute"
+msgstr "minutes"
#: server_status.php:440
-#, fuzzy
#| msgid "Do not change the password"
msgid "Containing the word:"
-msgstr "Do not change the password"
+msgstr "Containing the word:"
#: server_status.php:445
-#, fuzzy
#| msgid "Show open tables"
msgid "Show only alert values"
-msgstr "Show open tables"
+msgstr "Show only alert values"
#: server_status.php:449
msgid "Filter by category..."
-msgstr ""
+msgstr "Filter by category..."
#: server_status.php:462
-#, fuzzy
#| msgid "Related Links"
msgid "Related links:"
-msgstr "Related Links"
+msgstr "Related links:"
#: server_status.php:507 server_status.php:539 server_status.php:660
#: server_status.php:705
@@ -8298,12 +8286,12 @@ msgstr "Query type"
#. l10n: # = Amount of queries
#: server_status.php:537
msgid "#"
-msgstr ""
+msgstr "#"
#: server_status.php:609
#, php-format
msgid "Network traffic since startup: %s"
-msgstr ""
+msgstr "Network traffic since startup: %s"
#: server_status.php:617
#, php-format
@@ -8384,10 +8372,9 @@ msgid "ID"
msgstr "ID"
#: server_status.php:840
-#, fuzzy
#| msgid "Whether to enable SSL for connection to MySQL server."
msgid "The number of failed attempts to connect to the MySQL server."
-msgstr "Whether to enable SSL for connection to MySQL server."
+msgstr "The number of failed attempts to connect to the MySQL server."
#: server_status.php:841
msgid ""
@@ -8407,6 +8394,7 @@ msgstr "The number of transactions that used the temporary binary log cache."
msgid ""
"The number of connection attempts (successful or not) to the MySQL server."
msgstr ""
+"The number of connection attempts (successful or not) to the MySQL server."
#: server_status.php:844
msgid ""
@@ -8826,6 +8814,8 @@ msgid ""
"The maximum number of connections that have been in use simultaneously since "
"the server started."
msgstr ""
+"The maximum number of connections that have been in use simultaneously since "
+"the server started."
#: server_status.php:915
msgid "The number of rows waiting to be written in INSERT DELAYED queues."
@@ -8857,6 +8847,9 @@ msgid ""
"fragmentation issues, which may be solved by issuing a FLUSH QUERY CACHE "
"statement."
msgstr ""
+"The number of free memory blocks in query cache. High numbers can indicate "
+"fragmentation issues, which may be solved by issuing a FLUSH QUERY CACHE "
+"statement."
#: server_status.php:921
msgid "The amount of free memory for query cache."
@@ -9583,10 +9576,9 @@ msgid "Line"
msgstr "Line"
#: tbl_chart.php:88
-#, fuzzy
#| msgid "Inline"
msgid "Spline"
-msgstr "Inline"
+msgstr "Spline"
#: tbl_chart.php:89
msgid "Pie"
@@ -9597,50 +9589,43 @@ msgid "Stacked"
msgstr "Stacked"
#: tbl_chart.php:94
-#, fuzzy
#| msgid "Report title:"
msgid "Chart title"
-msgstr "Report title:"
+msgstr "Chart title"
#: tbl_chart.php:99
msgid "X-Axis:"
-msgstr ""
+msgstr "X-Axis:"
#: tbl_chart.php:113
-#, fuzzy
#| msgid "SQL queries"
msgid "Series:"
-msgstr "SQL queries"
+msgstr "Series:"
#: tbl_chart.php:115
-#, fuzzy
#| msgid "Textarea columns"
msgid "The remaining columns"
-msgstr "Textarea columns"
+msgstr "The remaining columns"
#: tbl_chart.php:128
-#, fuzzy
#| msgid "X Axis label"
msgid "X-Axis label:"
-msgstr "X Axis label"
+msgstr "X-Axis label:"
#: tbl_chart.php:128
-#, fuzzy
#| msgid "Value"
msgid "X Values"
-msgstr "Value"
+msgstr "X Values"
#: tbl_chart.php:129
-#, fuzzy
#| msgid "Y Axis label"
msgid "Y-Axis label:"
-msgstr "Y Axis label"
+msgstr "Y-Axis label:"
#: tbl_chart.php:129
-#, fuzzy
#| msgid "Value"
msgid "Y Values"
-msgstr "Value"
+msgstr "Y Values"
#: tbl_create.php:56
#, php-format
diff --git a/server_status.php b/server_status.php
index cba7266..c56eb70 100644
--- a/server_status.php
+++ b/server_status.php
@@ -44,13 +44,16 @@ if (isset($_REQUEST['ajax_request'])) {
cleanDeprecated($queries);
// admin commands are not queries
unset($queries['Com_admin_commands']);
+
$sum=array_sum($queries);
$ret = Array('x'=>(microtime(true)*1000),'y'=>$sum,'pointInfo'=>$queries);
exit(json_encode($ret));
case 'traffic':
$traffic = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name="Bytes_received" OR Variable_name="Bytes_sent"', 0, 1);
+
$ret = Array('x'=>(microtime(true)*1000),'y_sent'=>$traffic['Bytes_sent'],'y_received'=>$traffic['Bytes_received']);
exit(json_encode($ret));
+
}
}
}
@@ -73,6 +76,7 @@ $GLOBALS['js_include'][] = 'jquery/jquery.cookie.js'; // For tab persistence
$GLOBALS['js_include'][] = 'highcharts/highcharts.js';
/* Files required for chart exporting */
$GLOBALS['js_include'][] = 'highcharts/exporting.js';
+$GLOBALS['js_include'][] = 'canvg/flashcanvas.js';
$GLOBALS['js_include'][] = 'canvg/canvg.js';
$GLOBALS['js_include'][] = 'canvg/rgbcolor.js';
@@ -394,6 +398,8 @@ echo __('Runtime Information');
</a>
<a class="tabChart liveconnectionsLink" href="#">
<?php echo __('Live conn./process chart'); ?>
+
+
</a>
</div>
<div class="tabInnerContent">
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3528-gd404fd7
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, master has been updated
via d404fd72d66cce1cc876fed87c8d9944689f21a4 (commit)
via d7fc94529cef1f3bbdb1ec302c2d22dcfd039112 (commit)
via d26cace15bee541f8af9cac0d1ff157ab9e559db (commit)
from 4b73c39775b8234b66fd375aa3ac7387e945d704 (commit)
- Log -----------------------------------------------------------------
commit d404fd72d66cce1cc876fed87c8d9944689f21a4
Merge: 4b73c39775b8234b66fd375aa3ac7387e945d704 d7fc94529cef1f3bbdb1ec302c2d22dcfd039112
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 17:21:30 2011 +0200
Merge remote-tracking branch 'tyron/master'
Conflicts:
server_status.php
commit d7fc94529cef1f3bbdb1ec302c2d22dcfd039112
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 14 17:08:15 2011 +0200
Fix for Live charting stopping when changing tabs in Firefox
commit d26cace15bee541f8af9cac0d1ff157ab9e559db
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 23:36:07 2011 +0200
Traffic charting for traffic tab on status page
-----------------------------------------------------------------------
Summary of changes:
js/functions.js | 5 +-
js/messages.php | 5 +-
js/server_status.js | 291 ++++++++++++++---------
server_status.php | 19 +-
themes/original/css/theme_right.css.php | 1 +
themes/original/jquery/jquery-ui-1.8.custom.css | 7 +-
themes/pmahomme/css/theme_right.css.php | 1 +
themes/pmahomme/jquery/jquery-ui-1.8.custom.css | 7 +-
8 files changed, 211 insertions(+), 125 deletions(-)
diff --git a/js/functions.js b/js/functions.js
index 6266ba6..4ad7c77 100644
--- a/js/functions.js
+++ b/js/functions.js
@@ -1419,8 +1419,6 @@ function PMA_createChart(passedSettings) {
thisChart.options.realtime.timeoutCallBack = function() {
$.get(passedSettings.realtime.url,{ajax_request:1, chart_data:1, type:passedSettings.realtime.type},function(data) {
- if(chart_activeTimeouts[container]==null) return;
-
curValue = jQuery.parseJSON(data);
//if(lastValue==null) lastValue = curValue;
@@ -1433,6 +1431,9 @@ function PMA_createChart(passedSettings) {
lastValue = curValue;
numLoadedPoints++;
+
+ // Timeout has been cleared => don't start a new timeout
+ if(chart_activeTimeouts[container]==null) return;
chart_activeTimeouts[container] = setTimeout(thisChart.options.realtime.timeoutCallBack, thisChart.options.realtime.refreshRate);
});
diff --git a/js/messages.php b/js/messages.php
index a09078e..01aede4 100644
--- a/js/messages.php
+++ b/js/messages.php
@@ -61,7 +61,10 @@ $js_messages['strRemovingSelectedUsers'] = __('Removing Selected Users');
$js_messages['strClose'] = __('Close');
/* for server_status.js */
-$js_messages['strRealtimeChart'] = __('Realtime chart');
+$js_messages['strLiveTrafficChart'] = __('Live traffic chart');
+$js_messages['strLiveConnChart'] = __('Live conn./process chart');
+$js_messages['strLiveQueryChart'] = __('Live query chart');
+
$js_messages['strStaticData'] = __('Static data');
/* l10n: Total number of queries */
$js_messages['strTotal'] = __('Total');
diff --git a/js/server_status.js b/js/server_status.js
index 75894ad..4413c80 100644
--- a/js/server_status.js
+++ b/js/server_status.js
@@ -51,7 +51,7 @@ $(function() {
var tabStatus = new Object();
// Holds the current chart instances for each tab
var tabChart = new Object();
-
+
// Add tabs
$('#serverStatusTabs').tabs({
// Tab persistence
@@ -64,7 +64,10 @@ $(function() {
$(".ui-widget-content:not(.ui-tabs):not(.ui-helper-clearfix)").addClass("ui-helper-clearfix");
// Initialize each tab
- $('div.ui-tabs-panel').each(function() { initTab($(this),null); });
+ $('div.ui-tabs-panel').each(function() {
+ initTab($(this),null);
+ tabStatus[$(this).attr('id')] = 'static';
+ });
$('.statuslinks select').change(function() {
var chart=tabChart[$(this).parents('div.ui-tabs-panel').attr('id')];
@@ -93,83 +96,145 @@ $(function() {
return false;
});
- /** Realtime charting of variables (always the third element) **/
- $('.statuslinks a.tabChart').click(function() {
+
+ /** Realtime charting of variables **/
+
+ // Live traffic charting
+ $('.statuslinks a.livetrafficLink').click(function() {
// ui-tabs-panel class is added by the jquery tabs feature
- var tab=$(this).parents('div.ui-tabs-panel');
+ var $tab=$(this).parents('div.ui-tabs-panel');
+ var tabstat = tabStatus[$tab.attr('id')];
- if(tabStatus[tab.attr('id')]!='realtime') {
- var series, title;
- var settings = new Object();
+ if(tabstat=='static' || tabstat=='liveconnections') {
+ var settings = {
+ series: [{name:'kB sent since last refresh',data:[]},{name:'kB received since last refresh',data:[]}],
+ title: {text:'Server traffic (in kB)'},
+ realtime:{ url:'server_status.php?'+url_query,
+ type: 'traffic',
+ callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
+ if(lastVal==null) return;
+ chartObj.series[0].addPoint(
+ { x:curVal.x, y:(curVal.y_sent-lastVal.y_sent)/1024},
+ false, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ chartObj.series[1].addPoint(
+ { x:curVal.x, y:(curVal.y_received-lastVal.y_received)/1024},
+ true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ }
+ }
+ }
- switch(tab.attr('id')) {
- case 'statustabs_traffic':
- settings = {
- series: [{name:'Connections since last refresh', data:[]},{name:'Processes', data:[]}],
- title: {text:'Connections / Processes'},
- realtime:{ url:'server_status.php?'+url_query,
- type: 'proc',
- callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
- if(lastVal==null) return;
- chartObj.series[0].addPoint(
- { x:curVal.x, y:curVal.y_conn-lastVal.y_conn },
- false, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
- );
- chartObj.series[1].addPoint(
- { x:curVal.x, y:curVal.y_proc },
- true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
- );
- }
- }
- };
- break;
- case 'statustabs_queries':
- settings = {
- series: [{name:'Issued queries since last refresh', data:[]}],
- title: {text:'Issued queries'},
- tooltip: { formatter:function() { return this.point.name; } },
- realtime:{ url:'server_status.php?'+url_query,
- type: 'queries',
- callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
- if(lastVal==null) return;
- chartObj.series[0].addPoint(
- { x:curVal.x, y:curVal.y-lastVal.y, name:sortedQueriesPointInfo(curVal,lastVal) },
- true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
- );
- }
- }
- };
- break;
+ setupLiveChart($tab,this,settings);
+ if(tabstat=='liveconnections') $tab.find('.statuslinks a.liveconnectionsLink').html(PMA_messages['strLiveConnChart']);
+ tabStatus[$tab.attr('id')]='livetraffic';
+ } else {
+ $(this).html(PMA_messages['strLiveTrafficChart']);
+ setupLiveChart($tab,this,null);
+ }
+
+ return false;
+ });
+
+ // Live connection/process charting
+ $('.statuslinks a.liveconnectionsLink').click(function() {
+ var $tab=$(this).parents('div.ui-tabs-panel');
+ var tabstat = tabStatus[$tab.attr('id')];
+
+ if(tabstat=='static' || tabstat=='livetraffic') {
+ var settings = {
+ series: [{name:'Connections since last refresh', data:[]},{name:'Processes', data:[]}],
+ title: {text:'Connections / Processes'},
+ realtime:{ url:'server_status.php?'+url_query,
+ type: 'proc',
+ callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
+ if(lastVal==null) return;
+ chartObj.series[0].addPoint(
+ { x:curVal.x, y:curVal.y_conn-lastVal.y_conn },
+ false, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ chartObj.series[1].addPoint(
+ { x:curVal.x, y:curVal.y_proc },
+ true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ }
+ }
+ };
+
+ setupLiveChart($tab,this,settings);
+ if(tabstat=='livetraffic') $tab.find('.statuslinks a.livetrafficLink').html(PMA_messages['strLiveTrafficChart']);
+ tabStatus[$tab.attr('id')]='liveconnections';
+ } else {
+ $(this).html(PMA_messages['strLiveConnChart']);
+ setupLiveChart($tab,this,null);
+ }
+
+ return false;
+ });
- default:
- return;
+ // Live query charting
+ $('.statuslinks a.livequeriesLink').click(function() {
+ var $tab=$(this).parents('div.ui-tabs-panel');
+ var settings=null;
+
+ if(tabStatus[$tab.attr('id')]=='static') {
+ settings = {
+ series: [{name:'Issued queries since last refresh', data:[]}],
+ title: {text:'Issued queries'},
+ tooltip: { formatter:function() { return this.point.name; } },
+ realtime:{ url:'server_status.php?'+url_query,
+ type: 'queries',
+ callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
+ if(lastVal==null) return;
+ chartObj.series[0].addPoint(
+ { x:curVal.x, y:curVal.y-lastVal.y, name:sortedQueriesPointInfo(curVal,lastVal) },
+ true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ }
+ }
+ };
+ } else {
+ $(this).html(PMA_messages['strLiveQueryChart']);
+ }
+
+ setupLiveChart($tab,this,settings);
+ tabStatus[$tab.attr('id')]='livequeries';
+ return false;
+ });
+
+ function setupLiveChart($tab,link,settings) {
+ if(settings!=null) {
+ // Loading a chart with existing chart => remove old chart first
+ if(tabStatus[$tab.attr('id')]!='static') {
+ clearTimeout(chart_activeTimeouts[$tab.attr('id')+"_chart_cnt"]);
+ chart_activeTimeouts[$tab.attr('id')+"_chart_cnt"]=null;
+ tabChart[$tab.attr('id')].destroy();
+ // Also reset the select list
+ $tab.find('.statuslinks select').get(0).selectedIndex=0;
}
-
+
if(!settings.chart) settings.chart = {};
- settings.chart.renderTo=tab.attr('id')+"_chart_cnt";
+ settings.chart.renderTo=$tab.attr('id')+"_chart_cnt";
- tab.find('.tabInnerContent')
+ $tab.find('.tabInnerContent')
.hide()
- .after('<div style="clear:both; min-width:500px; height:400px; padding-bottom:80px;" id="'+tab.attr('id')+'_chart_cnt"></div>');
- tabStatus[tab.attr('id')]='realtime';
- tabChart[tab.attr('id')]=PMA_createChart(settings);
- $(this).html(PMA_messages['strStaticData']);
- tab.find('.statuslinks a.tabRefresh').hide();
- tab.find('.statuslinks select').show();
+ .after('<div style="clear:both; min-width:500px; height:400px; padding-bottom:80px;" id="'+$tab.attr('id')+'_chart_cnt"></div>');
+ tabChart[$tab.attr('id')]=PMA_createChart(settings);
+ $(link).html(PMA_messages['strStaticData']);
+ $tab.find('.statuslinks a.tabRefresh').hide();
+ $tab.find('.statuslinks select').show();
} else {
- clearTimeout(chart_activeTimeouts[tab.attr('id')+"_chart_cnt"]);
- chart_activeTimeouts[tab.attr('id')+"_chart_cnt"]=null;
- tab.find('.tabInnerContent').show();
- tab.find('div#'+tab.attr('id')+'_chart_cnt').remove();
- tabStatus[tab.attr('id')]='data';
- tabChart[tab.attr('id')].destroy();
- $(this).html(PMA_messages['strRealtimeChart']);
- tab.find('.statuslinks a.tabRefresh').show();
- tab.find('.statuslinks select').hide();
+ clearTimeout(chart_activeTimeouts[$tab.attr('id')+"_chart_cnt"]);
+ chart_activeTimeouts[$tab.attr('id')+"_chart_cnt"]=null;
+ $tab.find('.tabInnerContent').show();
+ $tab.find('div#'+$tab.attr('id')+'_chart_cnt').remove();
+ tabStatus[$tab.attr('id')]='static';
+ tabChart[$tab.attr('id')].destroy();
+ $tab.find('.statuslinks a.tabRefresh').show();
+ $tab.find('.statuslinks select').get(0).selectedIndex=0;
+ $tab.find('.statuslinks select').hide();
}
- return false;
- });
-
+ }
/* 3 Filtering functions */
$('#filterAlert').change(function() {
@@ -197,54 +262,54 @@ $(function() {
initTooltips();
break;
case 'statustabs_queries':
- if(data!=null) {
- queryPieChart.destroy();
- tab.find('.tabInnerContent').html(data);
- }
+ if(data!=null) {
+ queryPieChart.destroy();
+ tab.find('.tabInnerContent').html(data);
+ }
- // Build query statistics chart
- var cdata = new Array();
- $.each(jQuery.parseJSON($('#serverstatusquerieschart').html()),function(key,value) {
- cdata.push([key,parseInt(value)]);
- });
-
- queryPieChart=PMA_createChart({
- chart: {
- renderTo: 'serverstatusquerieschart'
-
- },
- title: {
- text:'',
- margin:0
- },
- series: [{
- type:'pie',
- name: 'Query statistics',
- data: cdata
- }],
- plotOptions: {
- pie: {
- allowPointSelect: true,
- cursor: 'pointer',
- dataLabels: {
- enabled: true,
- formatter: function() {
- return '<b>'+ this.point.name +'</b><br> '+ Highcharts.numberFormat(this.percentage, 2) +' %';
- }
- }
- }
- },
- tooltip: {
- formatter: function() { return '<b>'+ this.point.name +'</b><br/>'+Highcharts.numberFormat(this.y, 2)+'<br/>('+Highcharts.numberFormat(this.percentage, 2) +' %)'; }
- }
- });
+ // Build query statistics chart
+ var cdata = new Array();
+ $.each(jQuery.parseJSON($('#serverstatusquerieschart').html()),function(key,value) {
+ cdata.push([key,parseInt(value)]);
+ });
+
+ queryPieChart=PMA_createChart({
+ chart: {
+ renderTo: 'serverstatusquerieschart'
+
+ },
+ title: {
+ text:'',
+ margin:0
+ },
+ series: [{
+ type:'pie',
+ name: 'Query statistics',
+ data: cdata
+ }],
+ plotOptions: {
+ pie: {
+ allowPointSelect: true,
+ cursor: 'pointer',
+ dataLabels: {
+ enabled: true,
+ formatter: function() {
+ return '<b>'+ this.point.name +'</b><br> '+ Highcharts.numberFormat(this.percentage, 2) +' %';
+ }
+ }
+ }
+ },
+ tooltip: {
+ formatter: function() { return '<b>'+ this.point.name +'</b><br/>'+Highcharts.numberFormat(this.y, 2)+'<br/>('+Highcharts.numberFormat(this.percentage, 2) +' %)'; }
+ }
+ });
break;
-
+
case 'statustabs_allvars':
if(data!=null) {
- tab.find('.tabInnerContent').html(data);
- filterVariables();
- }
+ tab.find('.tabInnerContent').html(data);
+ filterVariables();
+ }
break;
}
diff --git a/server_status.php b/server_status.php
index 0ebfa0f..cba7266 100644
--- a/server_status.php
+++ b/server_status.php
@@ -44,10 +44,12 @@ if (isset($_REQUEST['ajax_request'])) {
cleanDeprecated($queries);
// admin commands are not queries
unset($queries['Com_admin_commands']);
-
$sum=array_sum($queries);
-
- $ret = Array('x'=>(microtime(true)*1000),'y'=>$sum,'pointInfo'=>$queries,'numQueries'=>count($queries));
+ $ret = Array('x'=>(microtime(true)*1000),'y'=>$sum,'pointInfo'=>$queries);
+ exit(json_encode($ret));
+ case 'traffic':
+ $traffic = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name="Bytes_received" OR Variable_name="Bytes_sent"', 0, 1);
+ $ret = Array('x'=>(microtime(true)*1000),'y_sent'=>$traffic['Bytes_sent'],'y_received'=>$traffic['Bytes_received']);
exit(json_encode($ret));
}
}
@@ -387,8 +389,11 @@ echo __('Runtime Information');
<option value="600">10 <?php echo __('minutes'); ?></option>
</select>
- <a class="tabChart" href="#">
- <?php echo __('Realtime chart'); ?>
+ <a class="tabChart livetrafficLink" href="#">
+ <?php echo __('Live traffic chart'); ?>
+ </a>
+ <a class="tabChart liveconnectionsLink" href="#">
+ <?php echo __('Live conn./process chart'); ?>
</a>
</div>
<div class="tabInnerContent">
@@ -414,8 +419,8 @@ echo __('Runtime Information');
<option value="300">5 <?php echo __('minutes'); ?></option>
<option value="600">10 <?php echo __('minutes'); ?></option>
</select>
- <a class="tabChart" href="#">
- <?php echo __('Realtime chart'); ?>
+ <a class="tabChart livequeriesLink" href="#">
+ <?php echo __('Live query chart'); ?>
</a>
</div>
<div class="tabInnerContent">
diff --git a/themes/original/css/theme_right.css.php b/themes/original/css/theme_right.css.php
index f2bbfb8..94ed5fe 100644
--- a/themes/original/css/theme_right.css.php
+++ b/themes/original/css/theme_right.css.php
@@ -972,6 +972,7 @@ table#serverstatusqueriesdetails th.headerSortDown img.sortableIcon, table#serve
.statuslinks {
float: <?php echo $right; ?>;
+ white-space: nowrap;
}
/* Also used for the variables page */
diff --git a/themes/original/jquery/jquery-ui-1.8.custom.css b/themes/original/jquery/jquery-ui-1.8.custom.css
index b2ad14f..f9cc5b7 100644
--- a/themes/original/jquery/jquery-ui-1.8.custom.css
+++ b/themes/original/jquery/jquery-ui-1.8.custom.css
@@ -414,7 +414,12 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs .ui-tabs-panel { display: block; border: 0; padding: 1em 1.4em; background: none; }
-.ui-tabs .ui-tabs-hide { display: none !important; }
+/*.ui-tabs .ui-tabs-hide { display: none !important; }*/
+/* Firefox has issues rendering SVG in display:none elements. See also http://jqueryui.com/demos/tabs/#...my_slider.2C_Google_Map.2C_sIFR_etc._not… */
+.ui-tabs .ui-tabs-hide {
+ position: absolute;
+ left: -10000px;
+}
/* Datepicker
----------------------------------*/
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
diff --git a/themes/pmahomme/css/theme_right.css.php b/themes/pmahomme/css/theme_right.css.php
index 7092358..c403698 100644
--- a/themes/pmahomme/css/theme_right.css.php
+++ b/themes/pmahomme/css/theme_right.css.php
@@ -1175,6 +1175,7 @@ table#serverstatusqueriesdetails th.headerSortDown img.sortableIcon, table#serve
.statuslinks {
float: <?php echo $right; ?>;
+ white-space: nowrap;
}
/* Also used for the variables page */
diff --git a/themes/pmahomme/jquery/jquery-ui-1.8.custom.css b/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
index d6a599f..9946953 100644
--- a/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
+++ b/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
@@ -414,7 +414,12 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs .ui-tabs-panel { display: block; border: 0; padding: 1em 1.4em; background: none; }
-.ui-tabs .ui-tabs-hide { display: none !important; }
+/*.ui-tabs .ui-tabs-hide { display: none !important; }*/
+/* Firefox has issues rendering SVG in display:none elements. See also http://jqueryui.com/demos/tabs/#...my_slider.2C_Google_Map.2C_sIFR_etc._not… */
+.ui-tabs .ui-tabs-hide {
+ position: absolute;
+ left: -10000px;
+}
/* Datepicker
----------------------------------*/
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3525-g4b73c39
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, master has been updated
via 4b73c39775b8234b66fd375aa3ac7387e945d704 (commit)
from b2651b53a350a1bb8104e68ef11801fb138da636 (commit)
- Log -----------------------------------------------------------------
commit 4b73c39775b8234b66fd375aa3ac7387e945d704
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 14:21:48 2011 +0200
Always use long <?php tags
-----------------------------------------------------------------------
Summary of changes:
libraries/mult_submits.inc.php | 8 ++--
server_status.php | 106 ++++++++++++++++++++--------------------
sql.php | 6 +-
3 files changed, 60 insertions(+), 60 deletions(-)
diff --git a/libraries/mult_submits.inc.php b/libraries/mult_submits.inc.php
index 8038705..74ff20f 100644
--- a/libraries/mult_submits.inc.php
+++ b/libraries/mult_submits.inc.php
@@ -29,7 +29,7 @@ if (! empty($submit_mult)
} else {
$selected = $selected_tbl;
switch ($submit_mult) {
- case 'add_prefix_tbl':
+ case 'add_prefix_tbl':
case 'replace_prefix_tbl':
case 'copy_tbl_change_prefix':
case 'drop_db':
@@ -254,7 +254,7 @@ if (!empty($submit_mult) && !empty($what)) {
</table>
</fieldset>
<fieldset class="tblFooters">
- <button type="submit" name="mult_btn" value="<?php echo __('Yes'); ?>" id="buttonYes"><? echo __('Submit'); ?></button>
+ <button type="submit" name="mult_btn" value="<?php echo __('Yes'); ?>" id="buttonYes"><?php echo __('Submit'); ?></button>
</fieldset>
<?php
}
@@ -268,7 +268,7 @@ if (!empty($submit_mult) && !empty($what)) {
</table>
</fieldset>
<fieldset class="tblFooters">
- <button type="submit" name="mult_btn" value="<?php echo __('Yes'); ?>" id="buttonYes"><? echo __('Submit'); ?></button>
+ <button type="submit" name="mult_btn" value="<?php echo __('Yes'); ?>" id="buttonYes"><?php echo __('Submit'); ?></button>
</fieldset>
<?php
}
@@ -410,7 +410,7 @@ elseif ($mult_btn == __('Yes')) {
. PMA_backquote($selected[$i])
. (($i == $selected_cnt-1) ? ');' : '');
break;
-
+
case 'add_prefix_tbl':
$newtablename = $add_prefix . $selected[$i];
$a_query = 'ALTER TABLE ' . PMA_backquote($selected[$i]) . ' RENAME ' . PMA_backquote($newtablename) ; // ADD PREFIX TO TABLE NAME
diff --git a/server_status.php b/server_status.php
index 70bd5d0..0ebfa0f 100644
--- a/server_status.php
+++ b/server_status.php
@@ -17,7 +17,7 @@ if (! defined('PMA_NO_VARIABLES_IMPORT')) {
require_once './libraries/common.inc.php';
-/**
+/**
* Ajax request
*/
@@ -27,7 +27,7 @@ if (isset($_REQUEST['ajax_request'])) {
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header_remove('Last-Modified');
- if (isset($_REQUEST["query_chart"])) {
+ if (isset($_REQUEST["query_chart"])) {
exit(createQueryChart());
}
if(isset($_REQUEST['chart_data'])) {
@@ -36,7 +36,7 @@ if (isset($_REQUEST['ajax_request'])) {
$c = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name="Connections"', 0, 1);
$result = PMA_DBI_query('SHOW PROCESSLIST');
$num_procs = PMA_DBI_num_rows($result);
-
+
$ret = Array('x'=>(microtime(true)*1000),'y_proc'=>$num_procs,'y_conn'=>$c['Connections']);
exit(json_encode($ret));
case 'queries':
@@ -44,15 +44,15 @@ if (isset($_REQUEST['ajax_request'])) {
cleanDeprecated($queries);
// admin commands are not queries
unset($queries['Com_admin_commands']);
-
+
$sum=array_sum($queries);
-
+
$ret = Array('x'=>(microtime(true)*1000),'y'=>$sum,'pointInfo'=>$queries,'numQueries'=>count($queries));
exit(json_encode($ret));
}
}
}
-
+
/**
* Replication library
@@ -63,7 +63,7 @@ require_once './libraries/replication_gui.lib.php';
/**
* JS Includes
*/
-
+
$GLOBALS['js_include'][] = 'server_status.js';
$GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js';
$GLOBALS['js_include'][] = 'jquery/jquery.tablesorter.js';
@@ -176,7 +176,7 @@ if (isset($server_status['Uptime_since_flush_status'])) {
$allocations = array(
// variable name => section
// variable names match when they begin with the given string
-
+
'Com_' => 'com',
'Innodb_' => 'innodb',
'Ndb_' => 'ndb',
@@ -321,7 +321,7 @@ if(isset($_REQUEST['show']) && isset($_REQUEST['ajax_request'])) {
// Prints the variables table
printVariablesTable();
exit();
-
+
default:
break;
}
@@ -330,7 +330,7 @@ if(isset($_REQUEST['show']) && isset($_REQUEST['ajax_request'])) {
/**
* start output
*/
-
+
/**
* Does the common work
*/
@@ -349,14 +349,14 @@ url_query = '<?php echo $url_query;?>';
pma_theme_image = '<?php echo $GLOBALS['pmaThemeImage']; ?>';
</script>
<div id="serverstatus">
- <h2><?
+ <h2><?php
/**
* Displays the sub-page heading
*/
if($GLOBALS['cfg']['MainPageIconic'])
echo '<img class="icon" src="' . $GLOBALS['pmaThemeImage'] . 's_status.png" width="16" height="16" alt="" />';
-
+
echo __('Runtime Information');
?></h2>
@@ -366,7 +366,7 @@ echo __('Runtime Information');
<li><a href="#statustabs_queries"><?php echo __('Query statistics'); ?></a></li>
<li><a href="#statustabs_allvars"><?php echo __('All status variables'); ?></a></li>
</ul>
-
+
<div id="statustabs_traffic">
<div class="statuslinks">
<a class="tabRefresh" href="<?php echo $PMA_PHP_SELF . '?show=server_traffic&' . PMA_generate_common_url(); ?>" >
@@ -386,11 +386,11 @@ echo __('Runtime Information');
<option value="300">5 <?php echo __('minutes'); ?></option>
<option value="600">10 <?php echo __('minutes'); ?></option>
</select>
-
+
<a class="tabChart" href="#">
<?php echo __('Realtime chart'); ?>
</a>
- </div>
+ </div>
<div class="tabInnerContent">
<?php printServerTraffic(); ?>
</div>
@@ -417,7 +417,7 @@ echo __('Runtime Information');
<a class="tabChart" href="#">
<?php echo __('Realtime chart'); ?>
</a>
- </div>
+ </div>
<div class="tabInnerContent">
<?php printQueryStatistics(); ?>
</div>
@@ -437,7 +437,7 @@ echo __('Runtime Information');
</div>
<div class="formelement">
<input type="checkbox" name="filterAlert" id="filterAlert">
- <label for="filterAlert"><?php echo __('Show only alert values'); ?></label>
+ <label for="filterAlert"><?php echo __('Show only alert values'); ?></label>
</div>
<div class="formelement">
<select id="filterCategory" name="filterCategory">
@@ -448,7 +448,7 @@ echo __('Runtime Information');
<option value='<?php echo $section_id; ?>'><?php echo $section_name; ?></option>
<?php
}
-
+
?>
</select>
</div>
@@ -485,9 +485,9 @@ echo __('Runtime Information');
function printQueryStatistics() {
global $server_status, $used_queries, $url_query, $PMA_PHP_SELF;
-
+
$hour_factor = 3600 / $server_status['Uptime'];
-
+
$total_queries = array_sum($used_queries);
?>
@@ -499,17 +499,17 @@ function printQueryStatistics() {
?>
<br>
<span style="font-size:60%; display:inline;">
- ø <?php echo __('per hour'); ?>:
+ ø <?php echo __('per hour'); ?>:
<?php echo PMA_formatNumber($total_queries * $hour_factor, 0); ?><br>
- ø <?php echo __('per minute'); ?>:
+ ø <?php echo __('per minute'); ?>:
<?php echo PMA_formatNumber( $total_queries * 60 / $server_status['Uptime'], 0); ?><br>
<?php if($total_queries / $server_status['Uptime'] >= 1) {
?>
- ø <?php echo __('per second'); ?>:
+ ø <?php echo __('per second'); ?>:
<?php echo PMA_formatNumber( $total_queries / $server_status['Uptime'], 0); ?><br>
-
+
<?php
}
@@ -527,7 +527,7 @@ function printQueryStatistics() {
<col class="valuecol" span="3" />
<thead>
<tr><th><?php echo __('Query type'); ?></th>
- <th><?php
+ <th><?php
/* l10n: # = Amount of queries */
echo __('#');
?>
@@ -548,7 +548,7 @@ function printQueryStatistics() {
// the number of connections is not an item of the Query types
// but is included in Questions. Then the total of the percentages is 100.
$name = str_replace(Array('Com_','_'), Array('',' '), $name);
-
+
if($value < $query_sum * 0.02)
$other_sum += $value;
else $chart_json[$name] = $value;
@@ -556,9 +556,9 @@ function printQueryStatistics() {
<tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; ?>">
<th class="name"><?php echo htmlspecialchars($name); ?></th>
<td class="value"><?php echo PMA_formatNumber($value, 5, 0, true); ?></td>
- <td class="value"><?php echo
+ <td class="value"><?php echo
PMA_formatNumber($value * $hour_factor, 4, 1, true); ?></td>
- <td class="value"><?php echo
+ <td class="value"><?php echo
PMA_formatNumber($value * $perc_factor, 0, 2); ?>%</td>
</tr>
<?php
@@ -566,9 +566,9 @@ function printQueryStatistics() {
?>
</tbody>
</table>
-
+
<div id="serverstatusquerieschart" style="width:500px; height:350px; ">
- <?php
+ <?php
/*// Generate the graph if this is an ajax request
if(isset($_REQUEST['ajax_request'])) {
echo createQueryChart();
@@ -576,12 +576,12 @@ function printQueryStatistics() {
echo '<a href="'.$PMA_PHP_SELF.'?'.$url_query.'&query_chart=1#serverstatusqueries"'
.'title="' . __('Show query chart') . '">['.__('Show query chart').']</a>';
}*/
-
+
if($other_sum>0)
$chart_json[__('Other')] = $other_sum;
-
+
echo json_encode($chart_json);
-
+
?>
</div>
<?php
@@ -590,9 +590,9 @@ function printQueryStatistics() {
function printServerTraffic() {
global $server_status,$PMA_PHP_SELF;
global $server_master_status, $server_slave_status;
-
+
$hour_factor = 3600 / $server_status['Uptime'];
-
+
/**
* starttime calculation
*/
@@ -600,7 +600,7 @@ function printServerTraffic() {
'SELECT UNIX_TIMESTAMP() - ' . $server_status['Uptime']);
?>
- <h3><?php /* echo __('<b>Server traffic</b>: These tables show the network traffic statistics of this MySQL server since its startup.');*/
+ <h3><?php /* echo __('<b>Server traffic</b>: These tables show the network traffic statistics of this MySQL server since its startup.');*/
echo sprintf(__('Network traffic since startup: %s'),
implode(' ', PMA_formatByteDown( $server_status['Bytes_received'] + $server_status['Bytes_sent'], 3, 1))
);
@@ -749,7 +749,7 @@ function printServerTraffic() {
</tr>
</tbody>
</table>
- <?
+ <?php
$url_params = array();
@@ -778,15 +778,15 @@ function printServerTraffic() {
<th><?php echo __('Command'); ?></th>
<th><?php echo __('Time'); ?></th>
<th><?php echo __('Status'); ?></th>
- <th><?php
- echo __('SQL query');
+ <th><?php
+ echo __('SQL query');
if (!PMA_DRIZZLE) { ?>
<a href="<?php echo $full_text_link; ?>"
title="<?php echo empty($full) ? __('Show Full Queries') : __('Truncate Shown Queries'); ?>">
<img src="<?php echo $GLOBALS['pmaThemeImage'] . 's_' . (empty($_REQUEST['full']) ? 'full' : 'partial'); ?>text.png"
alt="<?php echo empty($_REQUEST['full']) ? __('Show Full Queries') : __('Truncate Shown Queries'); ?>" />
</a>
- <? } ?>
+ <?php } ?>
</th>
</tr>
</thead>
@@ -942,7 +942,7 @@ function printVariablesTable() {
'Threads_created' => __('The number of threads created to handle connections. If Threads_created is big, you may want to increase the thread_cache_size value. (Normally this doesn\'t give a notable performance improvement if you have a good thread implementation.)'),
'Threads_running' => __('The number of threads that are not sleeping.')
);
-
+
/**
* define some alerts
*/
@@ -976,7 +976,7 @@ function printVariablesTable() {
'Opened_tables' => 0,
'Table_locks_waited' => 0,
'Qcache_lowmem_prunes' => 0,
-
+
'Qcache_free_blocks' => $server_status['Qcache_total_blocks'] / 5,
'Slow_launch_threads' => 0,
@@ -996,7 +996,7 @@ function printVariablesTable() {
// variable => min value
//'Handler read key' => '> ',
);
-
+
?>
<table class="data sortable" id="serverstatusvariables">
<col class="namecol" />
@@ -1014,10 +1014,10 @@ function printVariablesTable() {
<th colspan="3" class="tblFooters">
</th>
</tr>
- </tfoot>-->
+ </tfoot>-->
<tbody>
- <?
-
+ <?php
+
$odd_row = false;
foreach ($server_status as $name => $value) {
$odd_row = !$odd_row;
@@ -1083,17 +1083,17 @@ function createQueryChart($com_vars=FALSE) {
*/
require_once './libraries/chart.lib.php';
- if(!$com_vars)
+ if(!$com_vars)
$com_vars = PMA_DBI_fetch_result("SHOW GLOBAL STATUS LIKE 'Com\_%'", 0, 1);
-
+
// admin commands are not queries (e.g. they include COM_PING, which is excluded from $server_status['Questions'])
unset($com_vars['Com_admin_commands']);
-
+
arsort($com_vars);
-
+
$merge_minimum = array_sum($com_vars) * 0.005;
$merged_value = 0;
-
+
// remove zero values from the end, as well as merge together every value that is below 0.5%
// variable empty for Drizzle
if ($com_vars) {
@@ -1101,11 +1101,11 @@ function createQueryChart($com_vars=FALSE) {
array_pop($com_vars);
$merged_value += $last_element;
}
-
+
$com_vars['Other'] = $merged_value;
return PMA_chart_status($com_vars);
}
-
+
return '';
}
diff --git a/sql.php b/sql.php
index cb21b20..e8850fb 100644
--- a/sql.php
+++ b/sql.php
@@ -904,7 +904,7 @@ pma_token = '<?php echo $_SESSION[' PMA_token ']; ?>';
url_query = '<?php echo isset($url_query)?$url_query:PMA_generate_common_url($db);?>';
$(document).ready(createProfilingChart);
</script>
-<?
+<?php
echo '<fieldset><legend>' . __('Profiling') . '</legend>' . "\n";
echo '<div style="float: left;">';
echo '<table>' . "\n";
@@ -928,9 +928,9 @@ $(document).ready(createProfilingChart);
//PMA_chart_profiling($profiling_results);
echo json_encode($chart_json);
echo '</div>';
- echo '</fieldset>' . "\n";
+ echo '</fieldset>' . "\n";
}
-
+
// Displays the results in a table
if (empty($disp_mode)) {
// see the "PMA_setDisplayMode()" function in
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3524-gb2651b5
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, master has been updated
via b2651b53a350a1bb8104e68ef11801fb138da636 (commit)
from 9ac6131328e00b1883007d26919d74792c05337b (commit)
- Log -----------------------------------------------------------------
commit b2651b53a350a1bb8104e68ef11801fb138da636
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 14:04:27 2011 +0200
Changelog entries
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 60b046b..49c7305 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -14,6 +14,8 @@
+ rfe #3310562 Wording about Column
+ AJAX for Add a user in Database privileges
+ Patch #3271804 for rfe #3177495, new DisableMultiTableMaintenance directive
++ [interface] Reorganised server status page.
++ [interface] Changed way of generating charts.
3.4.3.0 (not yet released)
- bug #3311170 [sync] Missing helper icons in Synchronize
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin localized documentation branch, master, updated. c2726464132da46289b9a2fb8567122fc613750f
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, master has been updated
via c2726464132da46289b9a2fb8567122fc613750f (commit)
via a277a0fc6a54b0d4bda4d71addf94847e9190f27 (commit)
via 3c4cac8ebef46e0afbd44fd56c98fa9e09e6d6e5 (commit)
via c8399f89ed03fe0d369d25ca00d9f605e6f45141 (commit)
via 3bb950bc1154bfbf9946125f7fb71092dc524a25 (commit)
via 7210df5252f741dd9ce414da1f885f224c536090 (commit)
via 6e8989f86bc7040d6653a57f37b236027a0bd4b4 (commit)
via ce038abb49de23ca79ba8bd3e7e8f88493d255c6 (commit)
via b353ec2ad0f359d8f6701ae168ecf8a320c11fe9 (commit)
via 68afbefab983eb2856fcc0cfa076d3a14eeb487e (commit)
via 2fa31fe6b4fe0a743488713780ffdcd892bae8a9 (commit)
via 35ece63ef9d9d2a0e646ea4066469eba064ff3fd (commit)
via ab72e22513ee3ac5df7805eb249eedde3228db21 (commit)
via f4f681cc2b1c87d5dc5c531b5cff9312f209eaa2 (commit)
via f77637194d591aa0fd27d149e8df913c8644c83f (commit)
via 9b8d76e25d75baa8249733dabe57eb7fb28a475c (commit)
via 53e110132fd5e97fef88ef711becf3f8b0363fc7 (commit)
via b8f3325a834689e73fa63630676b96a0f64aba45 (commit)
via f88aefac763c547cd6eb153d65722235e9f7926c (commit)
via 627221f6c8b1c38650879349c439620e03f12ef4 (commit)
via 88f9b9cf6d975d0c25be66e7840c784c62c2485d (commit)
via a87a11523d78adb7b854248a3627520cbb3e6d93 (commit)
via f691441866892bcc823da7a187b2ab6fbffdd4cf (commit)
from 155a28320b52e8f2844521ccabddb52f51c4c00e (commit)
- Log -----------------------------------------------------------------
commit c2726464132da46289b9a2fb8567122fc613750f
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 14:01:38 2011 +0200
Update
commit a277a0fc6a54b0d4bda4d71addf94847e9190f27
Merge: f691441866892bcc823da7a187b2ab6fbffdd4cf 3c4cac8ebef46e0afbd44fd56c98fa9e09e6d6e5
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 14:01:20 2011 +0200
Merge remote-tracking branch 'pootle/master'
commit 3c4cac8ebef46e0afbd44fd56c98fa9e09e6d6e5
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 12:02:29 2011 +0200
Translation update done using Pootle.
commit c8399f89ed03fe0d369d25ca00d9f605e6f45141
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:58:34 2011 +0200
Translation update done using Pootle.
commit 3bb950bc1154bfbf9946125f7fb71092dc524a25
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:55:03 2011 +0200
Translation update done using Pootle.
commit 7210df5252f741dd9ce414da1f885f224c536090
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:54:16 2011 +0200
Translation update done using Pootle.
commit 6e8989f86bc7040d6653a57f37b236027a0bd4b4
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:53:58 2011 +0200
Translation update done using Pootle.
commit ce038abb49de23ca79ba8bd3e7e8f88493d255c6
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:52:42 2011 +0200
Translation update done using Pootle.
commit b353ec2ad0f359d8f6701ae168ecf8a320c11fe9
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:52:24 2011 +0200
Translation update done using Pootle.
commit 68afbefab983eb2856fcc0cfa076d3a14eeb487e
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:52:02 2011 +0200
Translation update done using Pootle.
commit 2fa31fe6b4fe0a743488713780ffdcd892bae8a9
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:51:44 2011 +0200
Translation update done using Pootle.
commit 35ece63ef9d9d2a0e646ea4066469eba064ff3fd
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:51:25 2011 +0200
Translation update done using Pootle.
commit ab72e22513ee3ac5df7805eb249eedde3228db21
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:51:01 2011 +0200
Translation update done using Pootle.
commit f4f681cc2b1c87d5dc5c531b5cff9312f209eaa2
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:50:25 2011 +0200
Translation update done using Pootle.
commit f77637194d591aa0fd27d149e8df913c8644c83f
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:50:01 2011 +0200
Translation update done using Pootle.
commit 9b8d76e25d75baa8249733dabe57eb7fb28a475c
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:49:05 2011 +0200
Translation update done using Pootle.
commit 53e110132fd5e97fef88ef711becf3f8b0363fc7
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:48:46 2011 +0200
Translation update done using Pootle.
commit b8f3325a834689e73fa63630676b96a0f64aba45
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:47:46 2011 +0200
Translation update done using Pootle.
commit f88aefac763c547cd6eb153d65722235e9f7926c
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:46:57 2011 +0200
Translation update done using Pootle.
commit 627221f6c8b1c38650879349c439620e03f12ef4
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:46:05 2011 +0200
Translation update done using Pootle.
commit 88f9b9cf6d975d0c25be66e7840c784c62c2485d
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:45:22 2011 +0200
Translation update done using Pootle.
commit a87a11523d78adb7b854248a3627520cbb3e6d93
Author: Yuichiro <yuichiro(a)pop07.odn.ne.jp>
Date: Tue Jun 14 11:45:01 2011 +0200
Translation update done using Pootle.
commit f691441866892bcc823da7a187b2ab6fbffdd4cf
Author: Michal Čihař <mcihar(a)novell.com>
Date: Mon Jun 13 14:26:08 2011 +0200
Regenerate
-----------------------------------------------------------------------
Summary of changes:
output/ja/Documentation.html | 121 ++++++++++++++++++------------------------
output/ja/README | 15 +++---
po/ja.po | 90 +++++++++++++------------------
3 files changed, 97 insertions(+), 129 deletions(-)
diff --git a/output/ja/Documentation.html b/output/ja/Documentation.html
index 72aa2a1..3c5ef38 100644
--- a/output/ja/Documentation.html
+++ b/output/ja/Documentation.html
@@ -1137,12 +1137,11 @@ id="cfg_Servers_ShowDatabasesCommand">$cfg['Servers'][$i]['ShowDatabasesCommand'
</dd>
<dt><span
id="cfg_Servers_SignonScript">$cfg['Servers'][$i]['SignonScript']</span> 文字列</dt>
- <dd>Name of PHP script to be sourced and executed to obtain login
-credentials. This is alternative approach to session based single
-signon. The script needs to provide function
-<code>get_login_credentials</code> which returns list of username and
-password, accepting single parameter of existing username (can be empty).
-See <code>scripts/signon-script.php</code> for an example.
+ <dd>ログイン資格情報を取得するために実行される PHP
+スクリプトの名前。これは、セッションに基づくシングルサインオンに代わる手法です。このスクリプトは、ユーザ名とパスワードの一覧を返す関数
+<code>get_login_credentials</code>
+を提供していて、単一のパラメータとして存在するユーザ名(空欄も可)を受け入れる必要があります。サンプルは、<code>scripts/signon-script.php</code>
+を参照してください。
</dd>
<dt><span
id="cfg_Servers_SignonSession">$cfg['Servers'][$i]['SignonSession']</span>
@@ -1371,7 +1370,7 @@ href="#cfg_Servers_verbose">$cfg['Servers'][$i]['verbose']</a></tt>
<dt id="cfg_NavigationBarIconic">$cfg['NavigationBarIconic'] 文字列</dt>
<dd>ナビゲーションバーのボタンと、右パネルのトップメニューにテキストを含めるか、シンボルのみにするかを定義します。値が TRUE
-のときはアイコンを、FALSE のときはテキストを、'both' のときはアイコンとテキストの双方を表示します。</dd>
+のときはアイコンを、FALSE のときはテキストを、「both」のときはアイコンとテキストの双方を表示します。</dd>
<dt id="cfg_ShowAll">$cfg['ShowAll'] 論理型</dt>
<dd>表示モードで「全て(のレコード)を表示する」ボタンを表示するかどうかを定義します。</dd>
@@ -1421,8 +1420,8 @@ class="configrule">$cfg['ForeignKeyDropdownOrder']</a>
の設定にあわせたスタイルで外部キーのドロップダウンボックスが表示されます。</dd>
<dt id="cfg_ForeignKeyDropdownOrder">$cfg['ForeignKeyDropdownOrder'] 配列</dt>
- <dd>外部キーのドロップダウンフィールドには、いくつかの表示方法がありますが、キーと値データの双方が表示されます。この配列には、<i>'content-id'</i>、<i>'id-content'</i>
-のいずれか、または双方が入ります。</dd>
+ <dd>外部キーのドロップダウンフィールドには、いくつかの表示方法がありますが、キーと値データの両方が表示されます。この配列には、<i>「content-id」</i>、<i>「id-content」</i>
+のいずれか、または両方が入ります。</dd>
<dt><span id="cfg_ZipDump">$cfg['ZipDump']</span> 論理型<br />
<span id="cfg_GZipDump">$cfg['GZipDump']</span> 論理型<br />
@@ -1660,8 +1659,7 @@ href="#cfg_CharEditing">$cfg['CharEditing']</a> でそのように設定され
<dt id="cfg_DefaultDisplay">$cfg['DefaultDisplay'] 文字列</dt>
<dd>表示モードには水平 (horizontal)、水平縦ヘッダ (horizontalflipped)、垂直
(vertical)の3つがありますが、どれをデフォルトの表示にするかを定義します。水平モードでは各行の内容を横一列に並べます。水平縦ヘッダモードでは、ヘッダを
-90
-度回転させることで、フィールドに小さな値しか入っていない場合でも詳細なヘッダを表示できるようにします。縦並びモードでは各行の内容を縦一列に並べます。
+90 度回転させることで、フィールドに小さな値しか入っていない場合でも詳細なヘッダを表示できるようにします。垂直モードでは各行の内容を縦一列に並べます。
</dd>
<dt id="cfg_RememberSorting">$cfg['RememberSorting'] 論理型</dt>
@@ -1890,7 +1888,7 @@ textarea
<dd>メインページのリストやメニュータブにアイコンを利用します。</dd>
<dt id="cfg_ReplaceHelpImg">$cfg['ReplaceHelpImg'] 論理型</dt>
- <dd>「ドキュメント(Documentation)」メッセージのかわりにヘルプボタンを表示します。
+ <dd>「ドキュメント」メッセージの代わりにヘルプボタンを表示します。
</dd>
<dt id="cfg_ThemePath">$cfg['ThemePath'] 文字列</dt>
@@ -1901,7 +1899,7 @@ textarea
2.7</a> をご覧ください。</dd>
<dt id="cfg_ThemeDefault">$cfg['ThemeDefault'] 文字列</dt>
- <dd>デフォルトテーマ(<tt>cfg['ThemePath']</tt> の下にあるサブディレクトリのひとつ)です。</dd>
+ <dd>デフォルトテーマ(<tt>cfg['ThemePath']</tt> の下にあるサブディレクトリの一つ)です。</dd>
<dt id="cfg_ThemePerServer">$cfg['ThemePerServer'] 論理型</dt>
<dd>サーバごとに別々のテーマを許可するかどうか。</dd>
@@ -2068,8 +2066,7 @@ href="http://www.phpmyadmin.net/home_page/docs.php">Link セクション</a>を
<ul><li>「MIME タイプ」フィールドはドロップダウンになっています。カラムの内容に対応した MIME タイプを選択してください。なお、MIME
タイプを選択しないと変換機能は有効になりませんので注意ください。</li>
- <li>「ブラウザ変換(Browser
-transformation)」フィールドはドロップダウンになっています。そのうち数が増えていくであろう定義済みの変換機能を選択できます。自前の変換機能の作り方については下記をご覧ください。<br />
+ <li>「ブラウザ変換機能」フィールドはドロップダウンになっています。そのうち数が増えていくであろう定義済みの変換機能を選択できます。自前の変換機能の作り方については以下をご覧ください。<br />
変換機能にはグローバルなものと、MIME タイプに固有のものとがあります。グローバルな変換機能はどの MIME タイプでも利用できます(必要がある場合は
MIME タイプも考慮します)。MIME タイプに固有の変換機能はふつう特定の MIME タイプのみを操作します。(「image」のように)MIME
@@ -2078,8 +2075,7 @@ MIME タイプも考慮します)。MIME タイプに固有の変換機能は
変換機能は関数が定義されていない MIME
タイプでも利用できますが、正しい変換機能を選択したかどうかというセキュリティチェックはありませんので、出力結果がどうなるかにはご注意ください。</li>
- <li>「変換オプション(Transformation
-options)」フィールドは自由に入力できるテキストフィールドです。変換用の関数特有のオプションはここに入力しなければなりません。ふつう変換機能はデフォルトのオプションで動作しますが、一般的に概略を読んでどんなオプションが必要になるか知っておくと便利です。<br />
+ <li>「変換オプション」フィールドは自由に入力できるテキストフィールドです。変換用の関数特有のオプションはここに入力しなければなりません。ふつう変換機能はデフォルトのオプションで動作しますが、一般的に概略を読んでどんなオプションが必要になるか知っておくと便利です。<br />
ENUM/SET フィールドの場合と同じように、複数のオプションがある場合は
'a','b','c',...(空白がないことに注意)のような形式で分割する必要があります。これは、内部的には配列に分解して、最初の要素には最初の値が入るようにするためです。<br />
@@ -3044,8 +3040,7 @@ XLSX/XLS ワークブックに対してのサポートは、将来追加され
<a href="#faq3_18">3.18 複数のテーブルを含む CSV
ファイルをインポートすると、それらが単一のテーブルにまとめられてしまいます。</a></h4>
<p>
- There is no reliable way to differentiate tables in CSV format. For the time
-being, you will have to break apart CSV files containing multiple tables.
+ CSV 形式でテーブルを区別する確実な方法はありません。当分の間は、複数のテーブルを含む CSV ファイルを分割する必要があります。
</p>
@@ -3707,15 +3702,14 @@ chmod o+rwx tmp
<a href="#faq6_24">6.24 phpMyAdmin が MySQL 4.1.x
ネイティブのカラムコメントをサポートするようになりましたが、pmadb に保存されているカラムコメントはどうなるのでしょうか?</a></h4>
-<p> あるテーブルの「構造(Structure)」ページに入ったら、自動的にそのテーブルの pmadb
-スタイルのカラムコメントがネイティブのカラムコメントに移行されます。</p>
+<p> あるテーブルの「構造」ページに入ったら、自動的にそのテーブルの pmadb スタイルのカラムコメントがネイティブのカラムコメントに移行されます。</p>
<h4 id="faq6_25">
- <a href="#faq6_25">6.25 How does BLOB streaming work in phpMyAdmin?</a></h4>
+ <a href="#faq6_25">6.25 どうしたら phpMyAdmin で BLOB ストリームを機能させられますか?</a</h4>
-<p> For general information about BLOB streaming on MySQL, visit <a
-href="http://blobstreaming.org">blobstreaming.org</a>. You need the
-following components:</p>
+<p> MySQL での BLOB ストリームについての一般的な情報は、<a
+href="http://blobstreaming.org">blobstreaming.org</a>
+をご覧になってください。以下の機能を持ったものが必要になります。</p>
<ul>
<li>MySQL 用の PBMS BLOB ストリーミングデーモン (0.5.15 以降)</li>
<li>ストリーミングが有効な MySQL 用の PBXT ストレージエンジン (1.0.11-6 以降)</li>
@@ -3723,21 +3717,16 @@ following components:</p>
<li>MySQL 用の PBMS PHP 拡張 (0.1.1 以降)</li>
</ul>
-<p>Here are details about configuration and operation:</p>
+<p>ここに、設定と操作についての詳細を上げておきます。</p>
<ol>
- <li>In <tt>config.inc.php</tt> your host should be defined with a FQDN (fully
-qualified domain name) instead of "localhost".</li>
- <li>Ensure that your target table is under the <tt>PBXT</tt> storage engine and
-has a <tt>LONGBLOB</tt> column (which must be nullable if you want to remove
-the BLOB reference from it).</li>
- <li>When you insert or update a row in this table, put a checkmark on the
-"Upload to BLOB repository" optional choice; otherwise, the upload
-will be done directly in your LONGBLOB column instead of the repository.</li>
- <li>Finally when you browse your table, you'll see in your column a link to
-stream your data, for example "View image". A header containing
-the correct MIME-type will be sent to your browser; this MIME-type was
-stored at upload time.</li>
+ <li><tt>config.inc.php</tt> でホストを、「localhost」ではなく、完全修飾ドメイン名 (FQDN) で定義する必要があります。</li>
+ <li>対象のテーブルが、<tt>PBXT</tt> ストレージエンジンであり、<tt>LONGBLOB</tt> カラム(BLOB 参照を削除する場合は
+NULL を許容する必要があります)があることを確認してください。</li>
+ <li>このテーブルに行の挿入/更新するときは、「BLOB 格納へアップロードする」オプションにチェックを入れます。それ以外の場合は、BLOB 格納ではなく
+LONGBLOB カラムに直接アップロードされます。</li>
+ <li>最後にこのテーブルを表示する場合ですが、「イメージの表示」というふうに、ストリームデータへリンクがカラムに表示されます。正しい MIME
+タイプを含むヘッダが、きちんとブラウザに送信されます。この MIME タイプは、アップロード時に格納されたものです。</li>
</ol>
<h4 id="faq6_26">
@@ -3777,33 +3766,26 @@ href="http://php.net/strftime">strftime</a>
<a href="#wysiwyg">6.28 どのようにしたら、エクスポート用のリレーショナルスキーマを簡単に編集することができますか?</a></h4>
<p>
- By clicking on the button 'toggle scratchboard' on the page where you edit
-x/y coordinates of those elements you can activate a scratchboard where all
-your elements are placed. By clicking on an element, you can move them
-around in the pre-defined area and the x/y coordinates will get updated
-dynamically. Likewise, when entering a new position directly into the input
-field, the new position in the scratchboard changes after your cursor leaves
-the input field.
+ 要素の x/y
+座標を修正するページで「スクラッチボードを切り替える」ボタンをクリックすると、スクラッチボードが有効になります。このスクラッチボードにはすべての要素が配置されています。要素をクリックすると、あらかじめ定義されている領域のなかを移動でき、x/y
+座標は動的に更新されます。同様に、入力フィールドに直接新しい座標を入力しておくと、入力フィールドからカーソルが移動したときにスクラッチボード内の要素も新しい位置に移動します。
</p>
<p>
- 新しい位置を保存するには、テーブルの下に「OK」ボタンをクリックする必要があります。新しい要素を配置する場合は、まず要素のテーブルに追加してください。新しい要素をドラッグできるようになります。
+ 新しい位置を保存するには、表の下に「OK」ボタンをクリックする必要があります。新しい要素を配置する場合は、まず要素のテーブルに追加してください。新しい要素をドラッグできるようになります。
</p>
<p>
用紙の大きさや向きを変えると、スクラッチボードの大きさも変わります。これは下のドロップダウンフィールドを変更するだけで行えます。スクラッチボードの大きさは自動的に変わります。要素の現在位置は変わりません。
</p>
<p>
- 要素が範囲外に出てしまった場合、用紙の大きさを拡大するか、「リセット(reset)」ボタンをクリックして、すべての要素を縦一列に並べてください。
+ 要素が範囲外に出てしまった場合、用紙の大きさを拡大するか、「リセット」ボタンをクリックして、すべての要素を縦一列に並べてください。
</p>
<h4 id="faq6_29">
- <a href="#faq6_29">6.29 Why can't I get a chart from my query result
-table?</a></h4>
+ <a href="#faq6_29">6.29 クエリの結果/テーブルからグラフが作れません。なぜですか?</a></h4>
-<p> Not every table can be put to the chart. Only tables with one, two or three
-columns can be visualised as a chart. Moreover the table must be in a
-special format for chart script to understand it. Currently supported
-formats can be found in the <a
-href="http://wiki.phpmyadmin.net/pma/Charts#Data_formats_for_query_results_chart">wiki</a>.</p>
+<p> すべてのテーブルグラフにすることはできません。カラムが1つ、2つ、3つのいずれかのテーブルだけが、グラフとして表現することができます。また、テーブルは、グラフスクリプトが理解できる特別な形式である必要があります。現在サポートされている形式は、<a
+href="http://wiki.phpmyadmin.net/pma/Charts#Data_formats_for_query_results_chart">wiki</a>
+に記載されています。</p>
<h3 id="faqproject">phpMyAdmin プロジェクト</h3>
@@ -4157,7 +4139,7 @@ PHPMyAdmin" (http://sf.net/projects/phpmysqlformgen/)
<li>カラム/データベースコメントの改善</li>
<li>(MIME)- カラム変換</li>
<li>左フレームのデータベースに独自の別名を利用</li>
- <li>テーブルの階層/ネスト表示</li>
+ <li>テーブルの階層/入れ子表示</li>
<li><abbr title="ポータブル・ドキュメント・フォーマット">PDF</abbr> リレーションを WYSIWYG 配布するための <abbr
title="ポータブル・ドキュメント・フォーマット">PDF</abbr> スクラッチボード</li>
<li>新しいアイコンセット</li>
@@ -4238,7 +4220,7 @@ href="http://www.acko.net/node/56">http://www.acko.net/node/56</a>)にも感
<li>Raj Kissu Rajandran (Google Summer of Code 2008)
<ul>
- <li>BLOBstreaming support</li>
+ <li>BLOB ストリームをサポート</li>
</ul></li>
<li>Piotr Przybylski (Google Summer of Code 2008 and 2010)
@@ -4281,7 +4263,7 @@ href="http://www.acko.net/node/56">http://www.acko.net/node/56</a>)にも感
<li>Ninad Pundalik (Google Summer of Code 2010)
<ul>
- <li>AJAXifying the interface</li>
+ <li>Ajax 化したインターフェース</li>
</ul></li>
<li>Martynas Mickevičius (Google Summer of Code 2010)
@@ -4291,7 +4273,7 @@ href="http://www.acko.net/node/56">http://www.acko.net/node/56</a>)にも感
<li>Barrie Leslie
<ul>
- <li>BLOBstreaming support with PBMS PHP extension</li>
+ <li>PBMS PHP 拡張による BLOB ストリームのサポート</li>
</ul></li>
<li>Ankit Gupta (Google Summer of Code 2010)
@@ -4409,7 +4391,7 @@ href="http://ja.wikipedia.org/wiki/%E5%B1%9E%E6%80%A7_%28%E3%83%87%E3%83%BC%E3%8
<li><a id="database"
href="http://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%…">データベース</a>
- 系統だったデータの集まり</li>
- <li>Engine - look at <a href="#glossar_storage_engine">Storage Engines</a>.</li>
+ <li>エンジン - →<a href="#glossar_storage_engine">ストレージエンジン</a>.</li>
<li><a
href="http://ja.wikipedia.org/wiki/%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%86%E3%83%B3%…">拡張(エクステンション)</a>
- 追加機能を PHP に拡張する PHP モジュール</li>
@@ -4512,8 +4494,8 @@ href="http://ja.wikipedia.org/wiki/%E7%B5%84_%28%E3%83%87%E3%83%BC%E3%82%BF%E3%8
<li><a href="http://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%BC%E3%83%90">サーバ</a> -
他のコンピュータシステムにネットワークを介してサービスを提供しているコンピュータシステムのこと</li>
<li><a id="glossar_storage_engine"
-href="http://dev.mysql.com/doc/en/storage-engines.html">Storage Engines</a>
-- handlers for different table types</li>
+href="http://dev.mysql.com/doc/refman/5.1/ja/storage-engines.html">ストレージエンジン</a>
+- 異なるテーブルタイプのハンドラ</li>
<li><a
href="http://ja.wikipedia.org/wiki/%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88_%28BSD%29">ソケット</a>
- プロセス間の通信形式</li>
@@ -4521,11 +4503,11 @@ href="http://ja.wikipedia.org/wiki/%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88_%28BSD%2
Sockets Layer)</a> - インターネット上のセキュアな通信を提供する暗号プロトコル</li>
<li><a href="http://ja.wikipedia.org/wiki/SQL">SQL</a> - リレーショナルデータベース用のデータベース言語
(Structured Query Language)</li>
- <li><a href="http://www.wikipedia.org/wiki/Table_%28database%29">table</a> - a
-set of data elements (cells) that is organized, defined and stored as
-horizontal rows and vertical columns where each item can be uniquely
-identified by a label or key or by it?s position in relation to other items.</li>
- <li>Table type</li>
+ <li><a
+href="http://ja.wikipedia.org/wiki/%E8%A1%A8_%28%E3%83%87%E3%83%BC%E3%82%BF%E3%83…">テーブル(表)</a>
+-
+横方向の行と縦方向のカラム(列)で構成、定義、保存されているデータ要素(セル)の組み合わせ。各項目は、ラベルかキーによって、もしくは他の項目との関係における位置によって、一意に識別することができる。</li>
+ <li>テーブルタイプ</li>
<li><a href="http://ja.wikipedia.org/wiki/Tar">tar</a> - アーカイブファイル形式の一種で Tape
ARchive format の略</li>
<li><a href="http://ja.wikipedia.org/wiki/Transmission_Control_Protocol">TCP
@@ -4537,10 +4519,9 @@ Locator)</a> -
<li><a
href="http://ja.wikipedia.org/wiki/Web%E3%82%B5%E3%83%BC%E3%83%90">ウェブサーバ</a>
- クライアントからの HTTP 要求の受け入れおよびそれらに対するウェブページの提供を行っているコンピュータ(プログラム)</li>
- <li><a href="http://www.wikipedia.org/wiki/XML">XML (Extensible Markup
-Language)</a> - a W3C-recommended general-purpose markup language for
-creating special-purpose markup languages, capable of describing many
-different kinds of data.</li>
+ <li><a href="http://ja.wikipedia.org/wiki/XML">XML (Extensible Markup
+Language)</a> - 個別の目的に応じたマークアップ言語作成のための、W3C
+勧告の汎用マークアップ言語。多くの異なる種類のデータを記述することが可能。</li>
<li><a
href="http://ja.wikipedia.org/wiki/ZIP_%28%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3…">ZIP</a>
- 一般的なデータ圧縮とアーカイブ形式</li>
diff --git a/output/ja/README b/output/ja/README
index 18c7ef2..f0eef6a 100644
--- a/output/ja/README
+++ b/output/ja/README
@@ -66,18 +66,17 @@ Documentation.txt/.html ファイルをご覧になってください。
http://www.phpmyadmin.net/ にあるサポートフォーラムへの案内をご覧ください。
-Enjoy!
-------
+あなたにとって最良のツールでありますように
+---------------------------------------------------------------
phpMyAdmin 開発チーム
-PS:
+追記
-Please, don't send us emails with question like "How do I compile PHP with
-MySQL-support". We just don't have the time to be your free help desk.
+「MySQL をサポートした PHP
+をコンパイルするにはどうしたらいいのか」というような質問のメールをしないでください。私達は、ただで使えるあなたのヘルプデスクではありませんし、そういうことに割ける時間もないのです。
-Please send your questions to the appropriate mailing lists / forums.
-Before contacting us, please read the Documentation.html (esp. the FAQ
-part).
+ご質問は、該当のメーリングリスト/フォーラムへお送りください。また、お問い合わせの前に、Documentation.html(特に FAQ
+(よくある質問) の部分)をお読みください。
diff --git a/po/ja.po b/po/ja.po
index ea3cf0b..768b1e0 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: phpMyAdmin-docs VERSION\n"
"Report-Msgid-Bugs-To: phpmyadmin-devel(a)lists.sourceforge.net\n"
"POT-Creation-Date: 2011-06-13 11:21+0300\n"
-"PO-Revision-Date: 2011-06-13 11:35+0200\n"
+"PO-Revision-Date: 2011-06-14 12:02+0200\n"
"Last-Translator: Yuichiro <yuichiro(a)pop07.odn.ne.jp>\n"
"Language-Team: Japan <jp(a)li.org>\n"
"Language: ja\n"
@@ -3439,7 +3439,6 @@ msgstr ""
#. type: Content of: <html><body><div><dl><dd>
#: orig-docs/Documentation.html:1328
-#, fuzzy
msgid ""
"Name of PHP script to be sourced and executed to obtain login credentials. "
"This is alternative approach to session based single signon. The script "
@@ -3448,11 +3447,11 @@ msgid ""
"username (can be empty). See <code>scripts/signon-script.php</code> for an "
"example."
msgstr ""
-"ログイン資格情報を取得するために実行される PHP スクリプトの名前。これは、セッ"
-"ションに基づくシングルサインオンに代わる手法です。このスクリプトは、ユーザ名"
-"とパスワードの一覧を返す関数 <code>get_login_credentials</code> を提供してい"
-"て、単一のパラメータとして存在するユーザ名(空欄も可)を受け入れる必要があり"
-"ます。サンプルは、<code>scripts/signon-script.php</code> を参照してください。"
+"ログイン資格情報を取得するために実行される PHP "
+"スクリプトの名前。これは、セッションに基づくシングルサインオンに代わる手法です。このスクリプトは、ユーザ名とパスワードの一覧を返す関数 "
+"<code>get_login_credentials</code> "
+"を提供していて、単一のパラメータとして存在するユーザ名(空欄も可)を受け入れる必要があります。サンプルは、<code>scripts/signon-"
+"script.php</code> を参照してください。"
#. type: Content of: <html><body><div><dl><dt>
#: orig-docs/Documentation.html:1335
@@ -5470,11 +5469,9 @@ msgid ""
"and still print them out. The vertical mode sorts each row on a vertical "
"lineup."
msgstr ""
-"表示モードには水平 (horizontal)、水平縦ヘッダ (horizontalflipped)、垂直 "
-"(vertical)の3つがありますが、どれをデフォルトの表示にするかを定義します。水"
-"平モードでは各行の内容を横一列に並べます。水平縦ヘッダモードでは、ヘッダを "
-"90 度回転させることで、フィールドに小さな値しか入っていない場合でも詳細なヘッ"
-"ダを表示できるようにします。縦並びモードでは各行の内容を縦一列に並べます。"
+"表示モードには水平 (horizontal)、水平縦ヘッダ (horizontalflipped)、垂直 (vertical)の3つがありますが、どれを"
+"デフォルトの表示にするかを定義します。水平モードでは各行の内容を横一列に並べます。水平縦ヘッダモードでは、ヘッダを 90 "
+"度回転させることで、フィールドに小さな値しか入っていない場合でも詳細なヘッダを表示できるようにします。垂直モードでは各行の内容を縦一列に並べます。"
#. type: Content of: <html><body><div><dl><dt>
#: orig-docs/Documentation.html:1977
@@ -6834,9 +6831,8 @@ msgid ""
"a hopefully growing amount of pre-defined transformations. See below for "
"information on how to build your own transformation."
msgstr ""
-"「ブラウザ変換(Browser transformation)」フィールドはドロップダウンになって"
-"います。そのうち数が増えていくであろう定義済みの変換機能を選択できます。自前"
-"の変換機能の作り方については下記をご覧ください。"
+"「ブラウザ変換機能」フィールドはドロップダウンになっています。そのうち数が増えていくであろう定義済みの変換機能を選択できます。自前の変換機能の作り方につ"
+"いては以下をご覧ください。"
#. type: Content of: <html><body><div><ul><li>
#: orig-docs/Documentation.html:2457
@@ -6875,10 +6871,8 @@ msgid ""
"operate with default options, but it is generally a good idea to look up the "
"overview to see which options are necessary."
msgstr ""
-"「変換オプション(Transformation options)」フィールドは自由に入力できるテキ"
-"ストフィールドです。変換用の関数特有のオプションはここに入力しなければなりま"
-"せん。ふつう変換機能はデフォルトのオプションで動作しますが、一般的に概略を読"
-"んでどんなオプションが必要になるか知っておくと便利です。"
+"「変換オプション」フィールドは自由に入力できるテキストフィールドです。変換用の関数特有のオプションはここに入力しなければなりません。ふつう変換機能はデフ"
+"ォルトのオプションで動作しますが、一般的に概略を読んでどんなオプションが必要になるか知っておくと便利です。"
#. type: Content of: <html><body><div><ul><li>
#: orig-docs/Documentation.html:2473
@@ -9641,13 +9635,10 @@ msgstr ""
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:3536
-#, fuzzy
msgid ""
"There is no reliable way to differentiate tables in CSV format. For the time "
"being, you will have to break apart CSV files containing multiple tables."
-msgstr ""
-"CSV 形式でテーブルを区別する確実な方法はありません。当分の間は、複数のテーブ"
-"ルを含む CSV ファイルを分割する必要があります。"
+msgstr "CSV 形式でテーブルを区別する確実な方法はありません。当分の間は、複数のテーブルを含む CSV ファイルを分割する必要があります。"
#. type: Content of: <html><body><div><h4>
#: orig-docs/Documentation.html:3543
@@ -11592,17 +11583,13 @@ msgid ""
"Automatic migration of a table's pmadb-style column comments to the native "
"ones is done whenever you enter Structure page for this table."
msgstr ""
-"あるテーブルの「構造(Structure)」ページに入ったら、自動的にそのテーブルの "
-"pmadb スタイルのカラムコメントがネイティブのカラムコメントに移行されます。"
+"あるテーブルの「構造」ページに入ったら、自動的にそのテーブルの pmadb スタイルのカラムコメントがネイティブのカラムコメントに移行されます。"
#. type: Content of: <html><body><div><h4>
#: orig-docs/Documentation.html:4306
-#, fuzzy
msgid ""
"<a href=\"#faq6_25\">6.25 How does BLOB streaming work in phpMyAdmin?</a>"
-msgstr ""
-"[<a href=\"#faq1_2\">1.2</a>] phpMyAdmin を使うと Apache サーバがクラッシュし"
-"ます。"
+msgstr "<a href=\"#faq6_25\">6.25 どうしたら phpMyAdmin で BLOB ストリームを機能させられますか?</a"
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:4308
@@ -11611,6 +11598,9 @@ msgid ""
"\"http://blobstreaming.org\">blobstreaming.org</a>. You need the following "
"components:"
msgstr ""
+"MySQL での BLOB ストリームについての一般的な情報は、<a "
+"href=\"http://blobstreaming.org\">blobstreaming.org</a> "
+"をご覧になってください。以下の機能を持ったものが必要になります。"
#. type: Content of: <html><body><div><ul><li>
#: orig-docs/Documentation.html:4310
@@ -11636,7 +11626,7 @@ msgstr "MySQL 用の PBMS PHP 拡張 (0.1.1 以降)"
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:4317
msgid "Here are details about configuration and operation:"
-msgstr ""
+msgstr "ここに、設定と操作についての詳細を上げておきます。"
#. type: Content of: <html><body><div><ol><li>
#: orig-docs/Documentation.html:4320
@@ -11644,6 +11634,7 @@ msgid ""
"In <tt>config.inc.php</tt> your host should be defined with a FQDN (fully "
"qualified domain name) instead of "localhost"."
msgstr ""
+"<tt>config.inc.php</tt> でホストを、「localhost」ではなく、完全修飾ドメイン名 (FQDN) で定義する必要があります。"
#. type: Content of: <html><body><div><ol><li>
#: orig-docs/Documentation.html:4321
@@ -11652,6 +11643,8 @@ msgid ""
"has a <tt>LONGBLOB</tt> column (which must be nullable if you want to remove "
"the BLOB reference from it)."
msgstr ""
+"対象のテーブルが、<tt>PBXT</tt> ストレージエンジンであり、<tt>LONGBLOB</tt> カラム(BLOB 参照を削除する場合は "
+"NULL を許容する必要があります)があることを確認してください。"
#. type: Content of: <html><body><div><ol><li>
#: orig-docs/Documentation.html:4322
@@ -11660,6 +11653,8 @@ msgid ""
"Upload to BLOB repository" optional choice; otherwise, the upload will "
"be done directly in your LONGBLOB column instead of the repository."
msgstr ""
+"このテーブルに行の挿入/更新するときは、「BLOB 格納へアップロードする」オプションにチェックを入れます。それ以外の場合は、BLOB 格納ではなく "
+"LONGBLOB カラムに直接アップロードされます。"
#. type: Content of: <html><body><div><ol><li>
#: orig-docs/Documentation.html:4323
@@ -11669,6 +11664,8 @@ msgid ""
"the correct MIME-type will be sent to your browser; this MIME-type was "
"stored at upload time."
msgstr ""
+"最後にこのテーブルを表示する場合ですが、「イメージの表示」というふうに、ストリームデータへリンクがカラムに表示されます。正しい MIME "
+"タイプを含むヘッダが、きちんとブラウザに送信されます。この MIME タイプは、アップロード時に格納されたものです。"
#. type: Content of: <html><body><div><h4>
#: orig-docs/Documentation.html:4327
@@ -11800,7 +11797,6 @@ msgstr ""
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:4365
-#, fuzzy
msgid ""
"By clicking on the button 'toggle scratchboard' on the page where you edit x/"
"y coordinates of those elements you can activate a scratchboard where all "
@@ -11810,14 +11806,9 @@ msgid ""
"field, the new position in the scratchboard changes after your cursor leaves "
"the input field."
msgstr ""
-"WYSIWYG 修正コントロールを利用して、PDF ページの要素を簡単に配置できるように"
-"します。PDF 要素の x/y 座標を修正するページで「スクラッチボードを有効/無効に"
-"する(toggle scratchboard)」ボタンをクリックすると、スクラッチボードが有効に"
-"なります。このスクラッチボードにはすべての要素が配置されています。要素をク"
-"リックすると、あらかじめ定義されている領域のなかを移動できます。x/y 座標は動"
-"的に更新されます。同様に、入力フィールドに直接新しい座標を入力しておくと、入"
-"力フィールドからカーソルが移動したときにスクラッチボード内の要素も新しい位置"
-"に移動します。"
+"要素の x/y 座標を修正するページで「スクラッチボードを切り替える」ボタンをクリックすると、スクラッチボードが有効になります。このスクラッチボードには"
+"すべての要素が配置されています。要素をクリックすると、あらかじめ定義されている領域のなかを移動でき、x/y 座標は動的に更新されます。同様に、入力フィー"
+"ルドに直接新しい座標を入力しておくと、入力フィールドからカーソルが移動したときにスクラッチボード内の要素も新しい位置に移動します。"
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:4374
@@ -11826,9 +11817,8 @@ msgid ""
"positions. If you want to place a new element, first add it to the table of "
"elements and then you can drag the new element around."
msgstr ""
-"新しい位置を保存するには、テーブルの下に「OK」ボタンをクリックする必要があり"
-"ます。新しい要素を配置する場合は、まず要素のテーブルに追加してください。新し"
-"い要素をドラッグできるようになります。"
+"新しい位置を保存するには、表の下に「OK」ボタンをクリックする必要があります。新しい要素を配置する場合は、まず要素のテーブルに追加してください。新しい要"
+"素をドラッグできるようになります。"
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:4379
@@ -11847,19 +11837,14 @@ msgstr ""
msgid ""
"If ever an element gets out of range you can either enlarge the paper size "
"or click on the 'reset' button to place all elements below each other."
-msgstr ""
-"要素が範囲外に出てしまった場合、用紙の大きさを拡大するか、「リセット"
-"(reset)」ボタンをクリックして、すべての要素を縦一列に並べてください。"
+msgstr "要素が範囲外に出てしまった場合、用紙の大きさを拡大するか、「リセット」ボタンをクリックして、すべての要素を縦一列に並べてください。"
#. type: Content of: <html><body><div><h4>
#: orig-docs/Documentation.html:4391
-#, fuzzy
msgid ""
"<a href=\"#faq6_29\">6.29 Why can't I get a chart from my query result table?"
"</a>"
-msgstr ""
-"[<a href=\"#faq6_3\">6.3</a>] テーブルにヌルの値を挿入するにはどうすればよい"
-"のでしょう。"
+msgstr "<a href=\"#faq6_29\">6.29 クエリの結果/テーブルからグラフが作れません。なぜですか?</a>"
#. type: Content of: <html><body><div><p>
#: orig-docs/Documentation.html:4393
@@ -11870,6 +11855,9 @@ msgid ""
"formats can be found in the <a href=\"http://wiki.phpmyadmin.net/pma/"
"Charts#Data_formats_for_query_results_chart\">wiki</a>."
msgstr ""
+"すべてのテーブルグラフにすることはできません。カラムが1つ、2つ、3つのいずれかのテーブルだけが、グラフとして表現することができます。また、テーブルは、"
+"グラフスクリプトが理解できる特別な形式である必要があります。現在サポートされている形式は、<a href=\"http://wiki.phpmyadmin"
+".net/pma/Charts#Data_formats_for_query_results_chart\">wiki</a> に記載されています。"
#. type: Content of: <html><body><div><h3>
#: orig-docs/Documentation.html:4395
@@ -13075,7 +13063,7 @@ msgstr "Raj Kissu Rajandran (Google Summer of Code 2008)"
#. type: Content of: <html><body><div><ul><li><ul><li>
#: orig-docs/Documentation.html:4868
msgid "BLOBstreaming support"
-msgstr ""
+msgstr "BLOB ストリームをサポート"
#. type: Content of: <html><body><div><ul><li>
#: orig-docs/Documentation.html:4871
@@ -13190,7 +13178,7 @@ msgstr "Barrie Leslie"
#. type: Content of: <html><body><div><ul><li><ul><li>
#: orig-docs/Documentation.html:4921
msgid "BLOBstreaming support with PBMS PHP extension"
-msgstr ""
+msgstr "PBMS PHP 拡張による BLOB ストリームのサポート"
#. type: Content of: <html><body><div><ul><li>
#: orig-docs/Documentation.html:4924
hooks/post-receive
--
phpMyAdmin localized documentation
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3522-g1f45e85
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, master has been updated
via 1f45e851f7520b56283a4da029a1150d1177a77a (commit)
via c9ea04a046304f9968858bb10edca60dfbc9d462 (commit)
via e6874ce0c71fe1a5d7d8f8be366f1f2443da4abc (commit)
via 5ee9a1fe1a91319f5e3f377b2eb4b43e5c10e717 (commit)
via f08b7d52ffc68e45ba78a024b0015ff4deb276e0 (commit)
via 5d5285e2899dd3071c6a53879b8c358dc8b0609a (commit)
via 21246070fa339ea87b3702aa11582eded2320b65 (commit)
via 9db1342b2a6896e13f48a042618ec3ec09d9ced5 (commit)
via 8d887f8dffc037af4e9a65635ca7d193e3291a0a (commit)
via b3c575d443521be8169d73f25361e4c20929a7e2 (commit)
via 2816b427ab4a9af286edfd44d7b27b61b52b107c (commit)
via 496b3814916414eb91c9a583f6211683eaf2d6fe (commit)
via 16749a04352f6da6e2635310d7289c1bd1328c86 (commit)
via 5d6d0d2e68d4e239bd56dd0a5304fccd673202b7 (commit)
via 5e0437cb2c575560cda883b8d799e21afbb9706f (commit)
via d00ea89935b5a7052aeee81c2b3f277b935ea118 (commit)
via da8124e689386bc53fc23cbcf87f087d7723c478 (commit)
via 316f1c6d235a53ecb6050997ddcb632ec9cbfec3 (commit)
via 3ce1e7f759daf957c7e0fcb55bc44d9f4d5646ee (commit)
via f4a2a92ea24336aa13730188ea03dee7671808cf (commit)
via 0f64babd3c49f69a985b445d1c06f365b570c84d (commit)
via 2f156ee6ec5c048d78da1df28662fc95289a1b56 (commit)
via 2bf553247b0efd42b7fbcf137a1367b71017b5fc (commit)
via bccbff41aed68bfdabaf5578d862ba6fb2929d07 (commit)
via b3c988157206c81c1c09c6720dbe8fe2797f98c7 (commit)
via 659252cd71eab4e6f7f648c5339e6a5f0910a938 (commit)
via 831da843c1f9d72d59f1fff8452a59188d9409bc (commit)
via dc433429e870da823130d63f08e5055e7d4fc1af (commit)
via 64a16e92023bdf4237280ffc27c6665396eccf0e (commit)
via 21b65e56f4decdce8c634272c59329fe95a47136 (commit)
via c928d53ef83438bbd1639a473dfcd4ecaa43101d (commit)
via f4fa0d9f797d36ccf648f32dcf4738b09e1ec457 (commit)
via 9410a4cfd9d92342e9c30ae3c43c64ceb45462b0 (commit)
via 0d1872595f6f1c14602a7edd1d652bc84ffe0b3f (commit)
via fcb3273c64d588f32b733aae0e8b95aa2d920523 (commit)
via 902fc1d95da7c912993b893ce8f883569fb41da0 (commit)
via e913dcc6e115a52b2a3305e53cc74fcc3d4d1a46 (commit)
via 86323a781560b4526b5f5010a1c93adeb410ba51 (commit)
via f47ce5dde3ba322f2045c3968354d951c36683f4 (commit)
via 855532ea2eddd7f669a52adc6838e716e19fe902 (commit)
via ed40e7f7bb6172607294c9a769ae7d5b4b6029b2 (commit)
via 14001bcc3b8202636c29e1bb66fdff342e6107b2 (commit)
via 775beaca2254615341624b5e3a4f047c4cb59be5 (commit)
via cbc5a582b637d680054c88ef00bd9600e55daa91 (commit)
via fed61fba72498d369e720c37ff0c9c0c72389d84 (commit)
via 5e094266669b0ec306e7a836154373a52766fd98 (commit)
via 9abc87d3a5915783f6e71ea56eefbce4c3196616 (commit)
via fe9844cb138202257bc0ebf6c5aac3062555f0ca (commit)
via 01511d1a9b6f93e1cb657c32a43aa314e42ec2a0 (commit)
via e551c09604a3147b88ad5b8ef655f3563e110029 (commit)
via 9fdbb850bb4bb6e13856d1124f5c360a8c02d892 (commit)
via 207bfd47e30d812126751440dc5ff8d9cb46fb2b (commit)
via 948b85825313818654df7d6c5907f488dbcd8efc (commit)
via 349a650357bc0adacc139c3d5ffabfb0ad475bbd (commit)
via 3b6e1926711aecfb2997b685edbb5613d9358c92 (commit)
via 71cc8fec3d2942871251918f38453d9ee65699f9 (commit)
via f76b91fdb1b9e77a58fcdce8145a99cc71a2fb2e (commit)
via 8d5850674b426aa2401dbdef975d09e6c42f38a1 (commit)
via 6938866d8175a2fe54f3f75038a1cea46a2e2797 (commit)
via 226ca36c9cfa6f15b79ec058f92bc5c36675541f (commit)
via cbaecced9fbf52e10e924f24d7ce3ca8e9512fc5 (commit)
via 3adc27ec81a4eb4bd54465056d06009a7f410800 (commit)
via d1d495fdda025859ff0e340c1b59b4145df29952 (commit)
via 9385012b97b4c188618ee8658d4e7a3b9200aa5d (commit)
via 3d62f95c89dbec80690fee0d92729317e5aff6c2 (commit)
via b366c76f422f875d880a66b2c33cdfd3608ba0ef (commit)
via 4885b174da8c9dca2b0bea9f4ea38fb63bc76eb9 (commit)
via ba20375ba6c8461987902bd54139536c9d009215 (commit)
via 58da05381ca6daf95d377c684f6f4b13aa467ec6 (commit)
via 4a788b150596a84f34012cae1a5d62712e9b5bac (commit)
via 99775c9bad552aac8b904996d673f1023821ab13 (commit)
via b161f43ec9089e0d2d5fb8a619056e6f9577a380 (commit)
via c0270352d8dbe5573589d95ef87aed5983f6fde7 (commit)
via 1a4fc3b87901e7d1edaefb3681a9619e890ec9d6 (commit)
via 7a82a65c4ecfd513448a1be9e1b11c77d51292be (commit)
via b37f788a0bba35521a693da8651ff535daa56a25 (commit)
via df9bcddd39e9fd66c0002bdd4e5496616dc5bc60 (commit)
via c6c0b451a7e855460efb77b3f8e9bf92dceaf118 (commit)
via cd17b9721b2a85363aad25be54a7e73232daaf88 (commit)
via 573da119a4a238d1bbd2b675dbfdebd1d17f0188 (commit)
via c3dda3823a09d6cac72cbb6bed72909b7cff5a3d (commit)
via a0302c666c1bf9f9715364a6301238b474aedac1 (commit)
via e52393015ffdfbfaf6941c6e0b8936750cede9ee (commit)
via 9060108adccba661409ebd9cc7d6f59350cdc83f (commit)
via 58f6523c57cac23ba5d2e0d41e254834b8c8963a (commit)
via 00515b13ba5cf90d387f7fc7588987084412b7ab (commit)
via d10e62ca37817c251cc3483b5d8ceea035759f9b (commit)
via c0749d556378341727982e9ea08bb1de08e7c5ea (commit)
via f8656fb0f8e9ecffa8abdc5dff1ab7c1b7e706db (commit)
via 014a054c7f740aa6e172059ed440928685de6fd5 (commit)
via 6d7358abb34a229bc138ec9381dceb1eedd6d498 (commit)
via 0839be45a5ac3281a3e0bb863ae804780bb87d9d (commit)
via 5ff65c4b54dfe8e0da176ca6f0cbb80cf3e0dfcf (commit)
via 9dc2cbd19cf68807a0994e80a4c1edba049f21e0 (commit)
from 8cd155d2e31d13d32f2779d15f7b39265657c50a (commit)
- Log -----------------------------------------------------------------
commit 1f45e851f7520b56283a4da029a1150d1177a77a
Merge: 8cd155d2e31d13d32f2779d15f7b39265657c50a c9ea04a046304f9968858bb10edca60dfbc9d462
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 13:49:12 2011 +0200
Merge remote-tracking branch 'tyron/master'
commit c9ea04a046304f9968858bb10edca60dfbc9d462
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 21:28:27 2011 +0200
Status variables refresh link once again broken
commit e6874ce0c71fe1a5d7d8f8be366f1f2443da4abc
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 18:53:45 2011 +0200
Clearer comment on PMA_formatNumber()
commit 5ee9a1fe1a91319f5e3f377b2eb4b43e5c10e717
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 18:14:57 2011 +0200
Assures that process table is below traffic/connection tables, not beside
commit f08b7d52ffc68e45ba78a024b0015ff4deb276e0
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 18:00:33 2011 +0200
3 more bugfixes
commit 5d5285e2899dd3071c6a53879b8c358dc8b0609a
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 17:59:58 2011 +0200
When exporting a chart as png, canvg renders hidden elements. In the case of pie chart it renders overlapping texts.
This patch fixes this for text and path elements. This bug is also reported to the original canv author.
commit 21246070fa339ea87b3702aa11582eded2320b65
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 16:55:49 2011 +0200
Some style fixes mostly for original theme
commit 9db1342b2a6896e13f48a042618ec3ec09d9ced5
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 16:45:31 2011 +0200
Bug Fixes
commit 8d887f8dffc037af4e9a65635ca7d193e3291a0a
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 15:29:45 2011 +0200
git being a pain in the...
commit b3c575d443521be8169d73f25361e4c20929a7e2
Merge: 2816b427ab4a9af286edfd44d7b27b61b52b107c ca6f3b0ac27ab2124c196dda8484397d99656e0b
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 15:27:09 2011 +0200
Merge remote-tracking branch 'origin/master'
Conflicts:
tbl_chart.php
commit 2816b427ab4a9af286edfd44d7b27b61b52b107c
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 15:00:11 2011 +0200
Merge remote-tracking branch 'origin/master'
commit 496b3814916414eb91c9a583f6211683eaf2d6fe
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 14:57:10 2011 +0200
hide json code
commit 16749a04352f6da6e2635310d7289c1bd1328c86
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 13 14:56:31 2011 +0200
2 more points in graphs
commit 5d6d0d2e68d4e239bd56dd0a5304fccd673202b7
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 21:15:22 2011 +0200
And another codeline missing :D
commit 5e0437cb2c575560cda883b8d799e21afbb9706f
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 21:13:24 2011 +0200
More old code
commit d00ea89935b5a7052aeee81c2b3f277b935ea118
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 19:55:05 2011 +0200
Fix: Reinitialize pie chart when refreshing the query tab
commit da8124e689386bc53fc23cbcf87f087d7723c478
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 19:44:33 2011 +0200
Fixed refresh in variables tab
commit 316f1c6d235a53ecb6050997ddcb632ec9cbfec3
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 19:36:30 2011 +0200
Connections and Traffic table side by side @server status page
commit 3ce1e7f759daf957c7e0fcb55bc44d9f4d5646ee
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 19:26:29 2011 +0200
Small improvements
commit f4a2a92ea24336aa13730188ea03dee7671808cf
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 19:26:03 2011 +0200
Fixed PMA_formatNumber() sometimes choosing too high SI preifx
commit 0f64babd3c49f69a985b445d1c06f365b570c84d
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 19:25:07 2011 +0200
Removed pChart references
commit 2f156ee6ec5c048d78da1df28662fc95289a1b56
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 16:06:51 2011 +0200
- SVG Export working
- Smaller chart markers
commit 2bf553247b0efd42b7fbcf137a1367b71017b5fc
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 15:35:51 2011 +0200
Clear timeout once refresh rate has changed and display what the select list is for
commit bccbff41aed68bfdabaf5578d862ba6fb2929d07
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 15:17:52 2011 +0200
Configurable refresh rate for real-time charts in the status page
commit b3c988157206c81c1c09c6720dbe8fe2797f98c7
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 10 14:23:46 2011 +0200
Better realtime chart behaviour when the chart hasn't been filled with data yet
commit 659252cd71eab4e6f7f648c5339e6a5f0910a938
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 20:08:38 2011 +0200
pChart is not needed anymore
commit 831da843c1f9d72d59f1fff8452a59188d9409bc
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 20:08:17 2011 +0200
Fixes of some bugs
commit dc433429e870da823130d63f08e5055e7d4fc1af
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 16:06:04 2011 +0200
Don't add duplicates to xaxis
commit 64a16e92023bdf4237280ffc27c6665396eccf0e
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 15:49:15 2011 +0200
undefined variable fix on single column result
commit 21b65e56f4decdce8c634272c59329fe95a47136
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 15:36:41 2011 +0200
A currently rather strange series selection to behave similar to the old charting system
commit c928d53ef83438bbd1639a473dfcd4ecaa43101d
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 12:33:07 2011 +0200
Fix that single column results work too
commit f4fa0d9f797d36ccf648f32dcf4738b09e1ec457
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 9 00:53:32 2011 +0200
- Dynamic chart with for the server status chart
- Table chart replaced with a Highcharts generated chart that also behaves somewhat more intellegently
commit 9410a4cfd9d92342e9c30ae3c43c64ceb45462b0
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 8 10:31:22 2011 +0200
Forgot 2 lines in PMA_formatNumber()
commit 0d1872595f6f1c14602a7edd1d652bc84ffe0b3f
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 8 10:19:59 2011 +0200
Math to the resuce! Nicer calculation of PMA_formatNumber() without any loops
commit fcb3273c64d588f32b733aae0e8b95aa2d920523
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 8 01:53:21 2011 +0200
-Tabs +Spaces
commit 902fc1d95da7c912993b893ce8f883569fb41da0
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 7 21:32:05 2011 +0200
- Replaced pChart profiling chart with highchart chart
- Moved PMA_profilingResults() function code inline in sql.php as its used at only one place
commit e913dcc6e115a52b2a3305e53cc74fcc3d4d1a46
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 7 20:58:56 2011 +0200
-Tabs +Spaces
commit 86323a781560b4526b5f5010a1c93adeb410ba51
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 7 20:50:38 2011 +0200
Better PMA_formatNumber() behaviour.
commit f47ce5dde3ba322f2045c3968354d951c36683f4
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 7 14:56:34 2011 +0200
Fixed refreshrate
commit 855532ea2eddd7f669a52adc6838e716e19fe902
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue Jun 7 14:54:51 2011 +0200
replace pChart with highchart for the query statistics
commit ed40e7f7bb6172607294c9a769ae7d5b4b6029b2
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 15:46:01 2011 +0200
Small changes/fixes
commit 14001bcc3b8202636c29e1bb66fdff342e6107b2
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 15:29:35 2011 +0200
-tabs +spaces
commit 775beaca2254615341624b5e3a4f047c4cb59be5
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 15:26:06 2011 +0200
More secure chart export
commit cbc5a582b637d680054c88ef00bd9600e55daa91
Merge: fed61fba72498d369e720c37ff0c9c0c72389d84 f2fe4db5f1d100f29152445e4367789707007800
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 14:41:44 2011 +0200
Merge remote-tracking branch 'origin/master'
commit fed61fba72498d369e720c37ff0c9c0c72389d84
Merge: 5e094266669b0ec306e7a836154373a52766fd98 d14e49f8271c25b11cdc98962e6b24338aaef90e
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 14:38:58 2011 +0200
Merge remote-tracking branch 'origin/master'
commit 5e094266669b0ec306e7a836154373a52766fd98
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 14:38:56 2011 +0200
Merge
commit 9abc87d3a5915783f6e71ea56eefbce4c3196616
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 13:50:17 2011 +0200
Connection / Process charting on the server traffic tab
commit fe9844cb138202257bc0ebf6c5aac3062555f0ca
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon Jun 6 13:16:35 2011 +0200
Small code improvements
commit 01511d1a9b6f93e1cb657c32a43aa314e42ec2a0
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 3 12:20:36 2011 +0200
Some commments
commit e551c09604a3147b88ad5b8ef655f3563e110029
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Fri Jun 3 12:10:53 2011 +0200
js error fix when exporting a chart
commit 9fdbb850bb4bb6e13856d1124f5c360a8c02d892
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 2 22:00:42 2011 +0200
Proof of concept for svg->canvas->image export of charts
commit 207bfd47e30d812126751440dc5ff8d9cb46fb2b
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Thu Jun 2 15:22:28 2011 +0200
- Fix: Hide refresh button only for current tab
- Fix: Tablesorter sorts thousands-seperated values correctly
commit 948b85825313818654df7d6c5907f488dbcd8efc
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 1 18:34:43 2011 +0200
Default refreshrate 3sec => 5sec
commit 349a650357bc0adacc139c3d5ffabfb0ad475bbd
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 1 18:31:57 2011 +0200
clear timeouts properly
commit 3b6e1926711aecfb2997b685edbb5613d9358c92
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 1 18:01:39 2011 +0200
- Don't loose "sortability" when switching between realtime and static data
- Chart: display 0 as first value
commit 71cc8fec3d2942871251918f38453d9ee65699f9
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 1 17:49:27 2011 +0200
- Query chart:
- Point tooltip now shows what type of queries have been issued
- Changed data from 'Seconds per Query' to 'Seconds since last refresh'
commit f76b91fdb1b9e77a58fcdce8145a99cc71a2fb2e
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Wed Jun 1 11:21:45 2011 +0200
Reinitalize tablesorter after refresh
commit 8d5850674b426aa2401dbdef975d09e6c42f38a1
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 15:50:50 2011 +0200
Sorting indicators in the column headers
commit 6938866d8175a2fe54f3f75038a1cea46a2e2797
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 15:50:03 2011 +0200
+source version, -minified version
commit 226ca36c9cfa6f15b79ec058f92bc5c36675541f
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 14:05:32 2011 +0200
Display 0 for actual 0 values
commit cbaecced9fbf52e10e924f24d7ce3ca8e9512fc5
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 12:14:14 2011 +0200
Replaced minified highchart files with source files
commit 3adc27ec81a4eb4bd54465056d06009a7f410800
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 12:03:57 2011 +0200
A bit better number formatting for the query statistics table
commit d1d495fdda025859ff0e340c1b59b4145df29952
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 12:03:17 2011 +0200
Highcarts (unminivied) source files
commit 9385012b97b4c188618ee8658d4e7a3b9200aa5d
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Tue May 31 10:33:37 2011 +0200
If the PMA_formatNumber() parameters would result in 0.0, it displays <0.1 instead
commit 3d62f95c89dbec80690fee0d92729317e5aff6c2
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon May 30 22:57:08 2011 +0200
Revert "timepicker.js is redundant, its already in the jquery ui js file"
This reverts commit a0302c666c1bf9f9715364a6301238b474aedac1.
commit b366c76f422f875d880a66b2c33cdfd3608ba0ef
Merge: 4885b174da8c9dca2b0bea9f4ea38fb63bc76eb9 b5c1ee65092ebf67537931dfc1e396acfb1b10c2
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon May 30 22:46:02 2011 +0200
Merge remote-tracking branch 'origin/master'
Conflicts:
themes/pmahomme/css/theme_right.css.php
commit 4885b174da8c9dca2b0bea9f4ea38fb63bc76eb9
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon May 30 22:37:18 2011 +0200
- Implemented more reusable code for realtime charting
- added 'queries per second' chart in the query statistics tab
- removed test chart
- fixed kill process url
commit ba20375ba6c8461987902bd54139536c9d009215
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon May 30 17:02:36 2011 +0200
Merged processes page into server status page
commit 58da05381ca6daf95d377c684f6f4b13aa467ec6
Author: Tyron Madlener <tyronx(a)gmail.com>
Date: Mon May 30 17:01:09 2011 +0200
Added realtime charting of processes with the highcart lib for testing
commit 4a788b150596a84f34012cae1a5d62712e9b5bac
Author: unknown <Tyron@.(none)>
Date: Fri May 27 19:34:53 2011 +0200
Small adjustments
commit 99775c9bad552aac8b904996d673f1023821ab13
Author: unknown <Tyron@.(none)>
Date: Fri May 27 19:30:13 2011 +0200
Don't show initials table for 20 privs or less
commit b161f43ec9089e0d2d5fb8a619056e6f9577a380
Author: unknown <Tyron@.(none)>
Date: Fri May 27 19:25:33 2011 +0200
Don't show 5 second long success message when reloading privs
commit c0270352d8dbe5573589d95ef87aed5983f6fde7
Author: unknown <Tyron@.(none)>
Date: Fri May 27 19:00:09 2011 +0200
Fixed expanded 'More menu' being behind jQuery tabs.
commit 1a4fc3b87901e7d1edaefb3681a9619e890ec9d6
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 20:48:21 2011 +0200
More wrong variable scoping
commit 7a82a65c4ecfd513448a1be9e1b11c77d51292be
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 20:45:44 2011 +0200
Fixed wrong variable scoping
commit b37f788a0bba35521a693da8651ff535daa56a25
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 20:41:07 2011 +0200
Fix for bug #3242060. Total sum of queries sums up all Com_ vars instead of using the Questions var
commit df9bcddd39e9fd66c0002bdd4e5496616dc5bc60
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 19:30:42 2011 +0200
re-initialize ajax loaded tooltip
commit c6c0b451a7e855460efb77b3f8e9bf92dceaf118
Merge: cd17b9721b2a85363aad25be54a7e73232daaf88 84006bfeb21723a510d42dff929f5cf290b39d9b
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 18:31:22 2011 +0200
Merge remote-tracking branch 'origin/master'
commit cd17b9721b2a85363aad25be54a7e73232daaf88
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 18:29:27 2011 +0200
Ajax refresh for all tabs now
commit 573da119a4a238d1bbd2b675dbfdebd1d17f0188
Merge: c3dda3823a09d6cac72cbb6bed72909b7cff5a3d 1835d93534d7e82b10c4e08805204b5d10459e63
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 16:18:01 2011 +0200
Merge remote-tracking branch 'origin/master'
commit c3dda3823a09d6cac72cbb6bed72909b7cff5a3d
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 15:58:19 2011 +0200
Fixed caching bug, added ajax refresh for variables table
commit a0302c666c1bf9f9715364a6301238b474aedac1
Author: tyron <tyronx(a)gmail.com>
Date: Tue May 24 15:56:55 2011 +0200
timepicker.js is redundant, its already in the jquery ui js file
commit e52393015ffdfbfaf6941c6e0b8936750cede9ee
Author: tyron <tyronx(a)gmail.com>
Date: Fri May 20 00:24:52 2011 +0200
2 Bugfixes
commit 9060108adccba661409ebd9cc7d6f59350cdc83f
Author: tyron <tyronx(a)gmail.com>
Date: Thu May 19 23:21:15 2011 +0200
This CSS Selectors breaks the style default link colors in jquery ui widgets, such as tabs
commit 58f6523c57cac23ba5d2e0d41e254834b8c8963a
Author: tyron <tyronx(a)gmail.com>
Date: Thu May 19 23:19:59 2011 +0200
- Server variable Filtering by category
- Some fixes and style improvements
- Tablesorting (sorting indicators are currently missing though)
commit 00515b13ba5cf90d387f7fc7588987084412b7ab
Author: tyron <tyronx(a)gmail.com>
Date: Thu May 19 23:15:53 2011 +0200
Added for cookie persistence on jquery ui tabs
commit d10e62ca37817c251cc3483b5d8ceea035759f9b
Author: tyron <tyronx(a)gmail.com>
Date: Wed May 18 14:25:15 2011 +0200
Added variable filtering for the 'Variables' Page
commit c0749d556378341727982e9ea08bb1de08e7c5ea
Author: tyron <tyronx(a)gmail.com>
Date: Wed May 18 13:17:47 2011 +0200
Organized tables into jQuery tabs to reduce odd scrolling effects when filtering variables
commit f8656fb0f8e9ecffa8abdc5dff1ab7c1b7e706db
Author: tyron <tyronx(a)gmail.com>
Date: Mon May 16 20:22:22 2011 +0200
Ajax loaded chart didn't remove zero values; filtered values have correct odd/even colored rows
commit 014a054c7f740aa6e172059ed440928685de6fd5
Merge: 6d7358abb34a229bc138ec9381dceb1eedd6d498 884ba99afccf9d67197a1d00e49e353baa5e00d0
Author: tyron <tyronx(a)gmail.com>
Date: Mon May 16 15:48:03 2011 +0200
Merge remote-tracking branch 'origin/master'
commit 6d7358abb34a229bc138ec9381dceb1eedd6d498
Author: tyron <tyronx(a)gmail.com>
Date: Mon May 16 15:41:59 2011 +0200
CSS changes for the single status variables table
commit 0839be45a5ac3281a3e0bb863ae804780bb87d9d
Author: tyron <tyronx(a)gmail.com>
Date: Mon May 16 15:34:57 2011 +0200
Status variables in one table, that can be filtered as-you-type with javascript also the query chart is loaded with ajax now. Both features degrade gracefully for non JS users.
commit 5ff65c4b54dfe8e0da176ca6f0cbb80cf3e0dfcf
Merge: 9dc2cbd19cf68807a0994e80a4c1edba049f21e0 cd1b2817b6238491df1a84405ccda7c78499f21f
Author: tyron <tyronx(a)gmail.com>
Date: Fri May 13 14:59:31 2011 +0200
Merge remote-tracking branch 'origin/master'
commit 9dc2cbd19cf68807a0994e80a4c1edba049f21e0
Author: tyron <tyronx(a)gmail.com>
Date: Thu May 5 10:15:27 2011 +0200
Merge branches 'tyronm' and 'master'
-----------------------------------------------------------------------
Summary of changes:
chart_export.php | 34 +
js/canvg/MIT-LICENSE.txt | 22 +
js/canvg/canvg.js | 2221 +++++
js/canvg/rgbcolor.js | 288 +
js/functions.js | 131 +-
js/highcharts/exporting.js | 690 ++
js/highcharts/highcharts.js |10671 +++++++++++++++++++++++
js/jquery/jquery.cookie.js | 91 +
js/jquery/jquery.tablesorter.js | 1031 +++
js/messages.php | 16 +
js/pMap.js | 164 -
js/server_status.js | 373 +
js/server_variables.js | 42 +
js/sql.js | 47 +-
js/tbl_chart.js | 236 +
libraries/chart.lib.php | 256 -
libraries/chart/pChart/fonts/DejaVuSans.ttf | Bin 683528 -> 0 bytes
libraries/chart/pChart/fonts/LICENSE | 99 -
libraries/chart/pChart/fonts/README | 6 -
libraries/chart/pChart/pCache.class | 119 -
libraries/chart/pChart/pChart.class | 3626 --------
libraries/chart/pChart/pData.class | 260 -
libraries/chart/pma_chart.php | 183 -
libraries/chart/pma_pchart_chart.php | 402 -
libraries/chart/pma_pchart_multi.php | 117 -
libraries/chart/pma_pchart_multi_bar.php | 37 -
libraries/chart/pma_pchart_multi_line.php | 38 -
libraries/chart/pma_pchart_multi_radar.php | 107 -
libraries/chart/pma_pchart_pie.php | 109 -
libraries/chart/pma_pchart_single.php | 56 -
libraries/chart/pma_pchart_single_bar.php | 34 -
libraries/chart/pma_pchart_single_line.php | 34 -
libraries/chart/pma_pchart_single_radar.php | 96 -
libraries/chart/pma_pchart_stacked_bar.php | 35 -
libraries/common.lib.php | 110 +-
libraries/server_links.inc.php | 4 +-
server_processlist.php | 114 -
server_status.php | 1398 ++--
server_variables.php | 11 +-
sql.php | 44 +-
tbl_chart.php | 180 +-
themes/original/css/theme_right.css.php | 67 +-
themes/original/img/cleardot.gif | Bin 0 -> 43 bytes
themes/original/jquery/jquery-ui-1.8.custom.css | 2 +-
themes/pmahomme/css/theme_right.css.php | 193 +-
themes/pmahomme/img/cleardot.gif | Bin 0 -> 43 bytes
themes/pmahomme/img/s_sortable.png | Bin 0 -> 1107 bytes
themes/pmahomme/jquery/jquery-ui-1.8.custom.css | 2 +-
48 files changed, 17048 insertions(+), 6748 deletions(-)
create mode 100644 chart_export.php
create mode 100644 js/canvg/MIT-LICENSE.txt
create mode 100644 js/canvg/canvg.js
create mode 100644 js/canvg/rgbcolor.js
create mode 100644 js/highcharts/exporting.js
create mode 100644 js/highcharts/highcharts.js
create mode 100644 js/jquery/jquery.cookie.js
create mode 100644 js/jquery/jquery.tablesorter.js
delete mode 100644 js/pMap.js
create mode 100644 js/server_status.js
create mode 100644 js/server_variables.js
create mode 100644 js/tbl_chart.js
delete mode 100644 libraries/chart.lib.php
delete mode 100644 libraries/chart/pChart/fonts/DejaVuSans.ttf
delete mode 100644 libraries/chart/pChart/fonts/LICENSE
delete mode 100644 libraries/chart/pChart/fonts/README
delete mode 100644 libraries/chart/pChart/pCache.class
delete mode 100644 libraries/chart/pChart/pChart.class
delete mode 100644 libraries/chart/pChart/pData.class
delete mode 100644 libraries/chart/pma_chart.php
delete mode 100644 libraries/chart/pma_pchart_chart.php
delete mode 100644 libraries/chart/pma_pchart_multi.php
delete mode 100644 libraries/chart/pma_pchart_multi_bar.php
delete mode 100644 libraries/chart/pma_pchart_multi_line.php
delete mode 100644 libraries/chart/pma_pchart_multi_radar.php
delete mode 100644 libraries/chart/pma_pchart_pie.php
delete mode 100644 libraries/chart/pma_pchart_single.php
delete mode 100644 libraries/chart/pma_pchart_single_bar.php
delete mode 100644 libraries/chart/pma_pchart_single_line.php
delete mode 100644 libraries/chart/pma_pchart_single_radar.php
delete mode 100644 libraries/chart/pma_pchart_stacked_bar.php
delete mode 100644 server_processlist.php
create mode 100644 themes/original/img/cleardot.gif
create mode 100644 themes/pmahomme/img/cleardot.gif
create mode 100644 themes/pmahomme/img/s_sortable.png
diff --git a/chart_export.php b/chart_export.php
new file mode 100644
index 0000000..87ab3a4
--- /dev/null
+++ b/chart_export.php
@@ -0,0 +1,34 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * "Echo" service to allow force downloading of exported charts (png or svg)
+ *
+ * @package phpMyAdmin
+ */
+
+
+define('PMA_MINIMUM_COMMON',true);
+
+require_once './libraries/common.inc.php';
+
+if(isset($_REQUEST['filename']) && isset($_REQUEST['image'])) {
+ $allowed = Array( 'image/png'=>'png', 'image/svg+xml'=>'svg');
+
+ if(!isset($allowed[$_REQUEST['type']])) exit('Invalid export type');
+
+ if(!preg_match("/(".implode("|",$allowed).")$/i",$_REQUEST['filename']))
+ $_REQUEST['filename'].='.'.$allowed[$_REQUEST['type']];
+
+ header("Cache-Control: public");
+ header("Content-Description: File Transfer");
+ header("Content-Disposition: attachment; filename=".$_REQUEST['filename']);
+ header("Content-Type: ".$_REQUEST['type']);
+ header("Content-Transfer-Encoding: binary");
+
+ if($allowed[$_REQUEST['type']]!='svg')
+ echo base64_decode(substr($_REQUEST['image'],strpos($_REQUEST['image'],',')+1));
+ else
+ echo $_REQUEST['image'];
+
+} else exit('Invalid request');
+?>
\ No newline at end of file
diff --git a/js/canvg/MIT-LICENSE.txt b/js/canvg/MIT-LICENSE.txt
new file mode 100644
index 0000000..40f19bd
--- /dev/null
+++ b/js/canvg/MIT-LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2010-2011 Gabe Lerner (gabelerner(a)gmail.com) - http://code.google.com/p/canvg/
+
+ 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.
\ No newline at end of file
diff --git a/js/canvg/canvg.js b/js/canvg/canvg.js
new file mode 100644
index 0000000..83c91b4
--- /dev/null
+++ b/js/canvg/canvg.js
@@ -0,0 +1,2221 @@
+/*
+ * canvg.js - Javascript SVG parser and renderer on Canvas
+ * MIT Licensed
+ * Gabe Lerner (gabelerner(a)gmail.com)
+ * http://code.google.com/p/canvg/
+ *
+ * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
+ */
+if(!window.console) {
+ window.console = {};
+ window.console.log = function(str) {};
+ window.console.dir = function(str) {};
+}
+
+// <3 IE
+if(!Array.indexOf){
+ Array.prototype.indexOf = function(obj){
+ for(var i=0; i<this.length; i++){
+ if(this[i]==obj){
+ return i;
+ }
+ }
+ return -1;
+ }
+}
+
+(function(){
+ // canvg(target, s)
+ // empty parameters: replace all 'svg' elements on page with 'canvas' elements
+ // target: canvas element or the id of a canvas element
+ // s: svg string or url to svg file
+ // opts: optional hash of options
+ // ignoreMouse: true => ignore mouse events
+ // ignoreAnimation: true => ignore animations
+ // ignoreDimensions: true => does not try to resize canvas
+ // ignoreClear: true => does not clear canvas
+ // offsetX: int => draws at a x offset
+ // offsetY: int => draws at a y offset
+ // scaleWidth: int => scales horizontally to width
+ // scaleHeight: int => scales vertically to height
+ // renderCallback: function => will call the function after the first render is completed
+ // forceRedraw: function => will call the function on every frame, if it returns true, will redraw
+ this.canvg = function (target, s, opts) {
+ // no parameters
+ if (target == null && s == null && opts == null) {
+ var svgTags = document.getElementsByTagName('svg');
+ for (var i=0; i<svgTags.length; i++) {
+ var svgTag = svgTags[i];
+ var c = document.createElement('canvas');
+ c.width = svgTag.clientWidth;
+ c.height = svgTag.clientHeight;
+ svgTag.parentNode.insertBefore(c, svgTag);
+ svgTag.parentNode.removeChild(svgTag);
+ var div = document.createElement('div');
+ div.appendChild(svgTag);
+ canvg(c, div.innerHTML);
+ }
+ return;
+ }
+
+ if (typeof target == 'string') {
+ target = document.getElementById(target);
+ }
+
+ // reuse class per canvas
+ var svg;
+ if (target.svg == null) {
+ svg = build();
+ target.svg = svg;
+ }
+ else {
+ svg = target.svg;
+ svg.stop();
+ }
+ svg.opts = opts;
+
+ var ctx = target.getContext('2d');
+ if (s.substr(0,1) == '<') {
+ // load from xml string
+ svg.loadXml(ctx, s);
+ }
+ else {
+ // load from url
+ svg.load(ctx, s);
+ }
+ }
+
+ function build() {
+ var svg = { };
+
+ svg.FRAMERATE = 30;
+
+ // globals
+ svg.init = function(ctx) {
+ svg.Definitions = {};
+ svg.Styles = {};
+ svg.Animations = [];
+ svg.Images = [];
+ svg.ctx = ctx;
+ svg.ViewPort = new (function () {
+ this.viewPorts = [];
+ this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
+ this.RemoveCurrent = function() { this.viewPorts.pop(); }
+ this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
+ this.width = function() { return this.Current().width; }
+ this.height = function() { return this.Current().height; }
+ this.ComputeSize = function(d) {
+ if (d != null && typeof(d) == 'number') return d;
+ if (d == 'x') return this.width();
+ if (d == 'y') return this.height();
+ return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);
+ }
+ });
+ }
+ svg.init();
+
+ // images loaded
+ svg.ImagesLoaded = function() {
+ for (var i=0; i<svg.Images.length; i++) {
+ if (!svg.Images[i].loaded) return false;
+ }
+ return true;
+ }
+
+ // trim
+ svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
+
+ // compress spaces
+ svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
+
+ // ajax
+ svg.ajax = function(url) {
+ var AJAX;
+ if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
+ else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
+ if(AJAX){
+ AJAX.open('GET',url,false);
+ AJAX.send(null);
+ return AJAX.responseText;
+ }
+ return null;
+ }
+
+ // parse xml
+ svg.parseXml = function(xml) {
+ if (window.DOMParser)
+ {
+ var parser = new DOMParser();
+ return parser.parseFromString(xml, 'text/xml');
+ }
+ else
+ {
+ xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
+ var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
+ xmlDoc.async = 'false';
+ xmlDoc.loadXML(xml);
+ return xmlDoc;
+ }
+ }
+
+ svg.Property = function(name, value) {
+ this.name = name;
+ this.value = value;
+
+ this.hasValue = function() {
+ return (this.value != null && this.value != '');
+ }
+
+ // return the numerical value of the property
+ this.numValue = function() {
+ if (!this.hasValue()) return 0;
+
+ var n = parseFloat(this.value);
+ if ((this.value + '').match(/%$/)) {
+ n = n / 100.0;
+ }
+ return n;
+ }
+
+ this.valueOrDefault = function(def) {
+ if (this.hasValue()) return this.value;
+ return def;
+ }
+
+ this.numValueOrDefault = function(def) {
+ if (this.hasValue()) return this.numValue();
+ return def;
+ }
+
+ /* EXTENSIONS */
+ var that = this;
+
+ // color extensions
+ this.Color = {
+ // augment the current color value with the opacity
+ addOpacity: function(opacity) {
+ var newValue = that.value;
+ if (opacity != null && opacity != '') {
+ var color = new RGBColor(that.value);
+ if (color.ok) {
+ newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
+ }
+ }
+ return new svg.Property(that.name, newValue);
+ }
+ }
+
+ // definition extensions
+ this.Definition = {
+ // get the definition from the definitions table
+ getDefinition: function() {
+ var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
+ return svg.Definitions[name];
+ },
+
+ isUrl: function() {
+ return that.value.indexOf('url(') == 0
+ },
+
+ getFillStyle: function(e) {
+ var def = this.getDefinition();
+
+ // gradient
+ if (def != null && def.createGradient) {
+ return def.createGradient(svg.ctx, e);
+ }
+
+ // pattern
+ if (def != null && def.createPattern) {
+ return def.createPattern(svg.ctx, e);
+ }
+
+ return null;
+ }
+ }
+
+ // length extensions
+ this.Length = {
+ DPI: function(viewPort) {
+ return 96.0; // TODO: compute?
+ },
+
+ EM: function(viewPort) {
+ var em = 12;
+
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+ if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
+
+ return em;
+ },
+
+ // get the length as pixels
+ toPixels: function(viewPort) {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
+ if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
+ if (s.match(/px$/)) return that.numValue();
+ if (s.match(/pt$/)) return that.numValue() * 1.25;
+ if (s.match(/pc$/)) return that.numValue() * 15;
+ if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
+ if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
+ if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
+ if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
+ return that.numValue();
+ }
+ }
+
+ // time extensions
+ this.Time = {
+ // get the time as milliseconds
+ toMilliseconds: function() {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/s$/)) return that.numValue() * 1000;
+ if (s.match(/ms$/)) return that.numValue();
+ return that.numValue();
+ }
+ }
+
+ // angle extensions
+ this.Angle = {
+ // get the angle as radians
+ toRadians: function() {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
+ if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
+ if (s.match(/rad$/)) return that.numValue();
+ return that.numValue() * (Math.PI / 180.0);
+ }
+ }
+ }
+
+ // fonts
+ svg.Font = new (function() {
+ this.Styles = ['normal','italic','oblique','inherit'];
+ this.Variants = ['normal','small-caps','inherit'];
+ this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
+
+ this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
+ var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
+ return {
+ fontFamily: fontFamily || f.fontFamily,
+ fontSize: fontSize || f.fontSize,
+ fontStyle: fontStyle || f.fontStyle,
+ fontWeight: fontWeight || f.fontWeight,
+ fontVariant: fontVariant || f.fontVariant,
+ toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
+ }
+ }
+
+ var that = this;
+ this.Parse = function(s) {
+ var f = {};
+ var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
+ var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
+ var ff = '';
+ for (var i=0; i<d.length; i++) {
+ if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
+ else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
+ else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
+ else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
+ else { if (d[i] != 'inherit') ff += d[i]; }
+ } if (ff != '') f.fontFamily = ff;
+ return f;
+ }
+ });
+
+ // points and paths
+ svg.ToNumberArray = function(s) {
+ var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
+ for (var i=0; i<a.length; i++) {
+ a[i] = parseFloat(a[i]);
+ }
+ return a;
+ }
+ svg.Point = function(x, y) {
+ this.x = x;
+ this.y = y;
+
+ this.angleTo = function(p) {
+ return Math.atan2(p.y - this.y, p.x - this.x);
+ }
+
+ this.applyTransform = function(v) {
+ var xp = this.x * v[0] + this.y * v[2] + v[4];
+ var yp = this.x * v[1] + this.y * v[3] + v[5];
+ this.x = xp;
+ this.y = yp;
+ }
+ }
+ svg.CreatePoint = function(s) {
+ var a = svg.ToNumberArray(s);
+ return new svg.Point(a[0], a[1]);
+ }
+ svg.CreatePath = function(s) {
+ var a = svg.ToNumberArray(s);
+ var path = [];
+ for (var i=0; i<a.length; i+=2) {
+ path.push(new svg.Point(a[i], a[i+1]));
+ }
+ return path;
+ }
+
+ // bounding box
+ svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
+ this.x1 = Number.NaN;
+ this.y1 = Number.NaN;
+ this.x2 = Number.NaN;
+ this.y2 = Number.NaN;
+
+ this.x = function() { return this.x1; }
+ this.y = function() { return this.y1; }
+ this.width = function() { return this.x2 - this.x1; }
+ this.height = function() { return this.y2 - this.y1; }
+
+ this.addPoint = function(x, y) {
+ if (x != null) {
+ if (isNaN(this.x1) || isNaN(this.x2)) {
+ this.x1 = x;
+ this.x2 = x;
+ }
+ if (x < this.x1) this.x1 = x;
+ if (x > this.x2) this.x2 = x;
+ }
+
+ if (y != null) {
+ if (isNaN(this.y1) || isNaN(this.y2)) {
+ this.y1 = y;
+ this.y2 = y;
+ }
+ if (y < this.y1) this.y1 = y;
+ if (y > this.y2) this.y2 = y;
+ }
+ }
+ this.addX = function(x) { this.addPoint(x, null); }
+ this.addY = function(y) { this.addPoint(null, y); }
+
+ this.addBoundingBox = function(bb) {
+ this.addPoint(bb.x1, bb.y1);
+ this.addPoint(bb.x2, bb.y2);
+ }
+
+ this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
+ var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
+ var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
+ var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
+ var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
+ this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
+ }
+
+ this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
+ // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-boundin…
+ var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
+ this.addPoint(p0[0], p0[1]);
+ this.addPoint(p3[0], p3[1]);
+
+ for (i=0; i<=1; i++) {
+ var f = function(t) {
+ return Math.pow(1-t, 3) * p0[i]
+ + 3 * Math.pow(1-t, 2) * t * p1[i]
+ + 3 * (1-t) * Math.pow(t, 2) * p2[i]
+ + Math.pow(t, 3) * p3[i];
+ }
+
+ var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
+ var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
+ var c = 3 * p1[i] - 3 * p0[i];
+
+ if (a == 0) {
+ if (b == 0) continue;
+ var t = -c / b;
+ if (0 < t && t < 1) {
+ if (i == 0) this.addX(f(t));
+ if (i == 1) this.addY(f(t));
+ }
+ continue;
+ }
+
+ var b2ac = Math.pow(b, 2) - 4 * c * a;
+ if (b2ac < 0) continue;
+ var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
+ if (0 < t1 && t1 < 1) {
+ if (i == 0) this.addX(f(t1));
+ if (i == 1) this.addY(f(t1));
+ }
+ var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
+ if (0 < t2 && t2 < 1) {
+ if (i == 0) this.addX(f(t2));
+ if (i == 1) this.addY(f(t2));
+ }
+ }
+ }
+
+ this.isPointInBox = function(x, y) {
+ return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
+ }
+
+ this.addPoint(x1, y1);
+ this.addPoint(x2, y2);
+ }
+
+ // transforms
+ svg.Transform = function(v) {
+ var that = this;
+ this.Type = {}
+
+ // translate
+ this.Type.translate = function(s) {
+ this.p = svg.CreatePoint(s);
+ this.apply = function(ctx) {
+ ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+ }
+ }
+
+ // rotate
+ this.Type.rotate = function(s) {
+ var a = svg.ToNumberArray(s);
+ this.angle = new svg.Property('angle', a[0]);
+ this.cx = a[1] || 0;
+ this.cy = a[2] || 0;
+ this.apply = function(ctx) {
+ ctx.translate(this.cx, this.cy);
+ ctx.rotate(this.angle.Angle.toRadians());
+ ctx.translate(-this.cx, -this.cy);
+ }
+ this.applyToPoint = function(p) {
+ var a = this.angle.Angle.toRadians();
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+ p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
+ p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
+ }
+ }
+
+ this.Type.scale = function(s) {
+ this.p = svg.CreatePoint(s);
+ this.apply = function(ctx) {
+ ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
+ }
+ }
+
+ this.Type.matrix = function(s) {
+ this.m = svg.ToNumberArray(s);
+ this.apply = function(ctx) {
+ ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform(this.m);
+ }
+ }
+
+ this.Type.SkewBase = function(s) {
+ this.base = that.Type.matrix;
+ this.base(s);
+ this.angle = new svg.Property('angle', s);
+ }
+ this.Type.SkewBase.prototype = new this.Type.matrix;
+
+ this.Type.skewX = function(s) {
+ this.base = that.Type.SkewBase;
+ this.base(s);
+ this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
+ }
+ this.Type.skewX.prototype = new this.Type.SkewBase;
+
+ this.Type.skewY = function(s) {
+ this.base = that.Type.SkewBase;
+ this.base(s);
+ this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
+ }
+ this.Type.skewY.prototype = new this.Type.SkewBase;
+
+ this.transforms = [];
+
+ this.apply = function(ctx) {
+ for (var i=0; i<this.transforms.length; i++) {
+ this.transforms[i].apply(ctx);
+ }
+ }
+
+ this.applyToPoint = function(p) {
+ for (var i=0; i<this.transforms.length; i++) {
+ this.transforms[i].applyToPoint(p);
+ }
+ }
+
+ var data = v.split(/\s(?=[a-z])/);
+ for (var i=0; i<data.length; i++) {
+ var type = data[i].split('(')[0];
+ var s = data[i].split('(')[1].replace(')','');
+ var transform = new this.Type[type](s);
+ this.transforms.push(transform);
+ }
+ }
+
+ // aspect ratio
+ svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
+ // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
+ aspectRatio = svg.compressSpaces(aspectRatio);
+ aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
+ var align = aspectRatio.split(' ')[0] || 'xMidYMid';
+ var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';
+
+ // calculate scale
+ var scaleX = width / desiredWidth;
+ var scaleY = height / desiredHeight;
+ var scaleMin = Math.min(scaleX, scaleY);
+ var scaleMax = Math.max(scaleX, scaleY);
+ if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
+ if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }
+
+ refX = new svg.Property('refX', refX);
+ refY = new svg.Property('refY', refY);
+ if (refX.hasValue() && refY.hasValue()) {
+ ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
+ }
+ else {
+ // align
+ if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);
+ if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0);
+ if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0);
+ if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight);
+ }
+
+ // scale
+ if (align == 'none') ctx.scale(scaleX, scaleY);
+ else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
+ else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
+
+ // translate
+ ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
+ }
+
+ // elements
+ svg.Element = {}
+
+ svg.Element.ElementBase = function(node) {
+ this.attributes = {};
+ this.styles = {};
+ this.children = [];
+
+ // get or create attribute
+ this.attribute = function(name, createIfNotExists) {
+ var a = this.attributes[name];
+ if (a != null) return a;
+
+ a = new svg.Property(name, '');
+ if (createIfNotExists == true) this.attributes[name] = a;
+ return a;
+ }
+
+ // get or create style
+ this.style = function(name, createIfNotExists) {
+ var s = this.styles[name];
+ if (s != null) return s;
+
+ var a = this.attribute(name);
+ if (a != null && a.hasValue()) {
+ return a;
+ }
+
+ s = new svg.Property(name, '');
+ if (createIfNotExists == true) this.styles[name] = s;
+ return s;
+ }
+
+ // base render
+ this.render = function(ctx) {
+ // don't render display=none
+ if (this.attribute('display').value == 'none') return;
+
+ ctx.save();
+ this.setContext(ctx);
+ this.renderChildren(ctx);
+ this.clearContext(ctx);
+ ctx.restore();
+ }
+
+ // base set context
+ this.setContext = function(ctx) {
+ // OVERRIDE ME!
+ }
+
+ // base clear context
+ this.clearContext = function(ctx) {
+ // OVERRIDE ME!
+ }
+
+ // base render children
+ this.renderChildren = function(ctx) {
+ for (var i=0; i<this.children.length; i++) {
+ this.children[i].render(ctx);
+ }
+ }
+
+ this.addChild = function(childNode, create) {
+ var child = childNode;
+ if (create) child = svg.CreateElement(childNode);
+ child.parent = this;
+ this.children.push(child);
+ }
+
+ if (node != null && node.nodeType == 1) { //ELEMENT_NODE
+ // add children
+ for (var i=0; i<node.childNodes.length; i++) {
+ var childNode = node.childNodes[i];
+ if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
+ }
+
+ // add attributes
+ for (var i=0; i<node.attributes.length; i++) {
+ var attribute = node.attributes[i];
+ this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
+ }
+
+ // add tag styles
+ var styles = svg.Styles[this.type];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+
+ // add class styles
+ if (this.attribute('class').hasValue()) {
+ var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
+ for (var j=0; j<classes.length; j++) {
+ styles = svg.Styles['.'+classes[j]];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+ }
+ }
+
+ // add inline styles
+ if (this.attribute('style').hasValue()) {
+ var styles = this.attribute('style').value.split(';');
+ for (var i=0; i<styles.length; i++) {
+ if (svg.trim(styles[i]) != '') {
+ var style = styles[i].split(':');
+ var name = svg.trim(style[0]);
+ var value = svg.trim(style[1]);
+ this.styles[name] = new svg.Property(name, value);
+ }
+ }
+ }
+
+ // add id
+ if (this.attribute('id').hasValue()) {
+ if (svg.Definitions[this.attribute('id').value] == null) {
+ svg.Definitions[this.attribute('id').value] = this;
+ }
+ }
+ }
+ }
+
+ svg.Element.RenderedElementBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.setContext = function(ctx) {
+ // fill
+ if (this.style('fill').Definition.isUrl()) {
+ var fs = this.style('fill').Definition.getFillStyle(this);
+ if (fs != null) ctx.fillStyle = fs;
+ }
+ else if (this.style('fill').hasValue()) {
+ var fillStyle = this.style('fill');
+ if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
+ ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
+ }
+
+ // stroke
+ if (this.style('stroke').Definition.isUrl()) {
+ var fs = this.style('stroke').Definition.getFillStyle(this);
+ if (fs != null) ctx.strokeStyle = fs;
+ }
+ else if (this.style('stroke').hasValue()) {
+ var strokeStyle = this.style('stroke');
+ if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
+ ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
+ }
+ if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
+ if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
+ if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
+ if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
+
+ // font
+ if (typeof(ctx.font) != 'undefined') {
+ ctx.font = svg.Font.CreateFont(
+ this.style('font-style').value,
+ this.style('font-variant').value,
+ this.style('font-weight').value,
+ this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '',
+ this.style('font-family').value).toString();
+ }
+
+ // transform
+ if (this.attribute('transform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('transform').value);
+ transform.apply(ctx);
+ }
+
+ // clip
+ if (this.attribute('clip-path').hasValue()) {
+ var clip = this.attribute('clip-path').Definition.getDefinition();
+ if (clip != null) clip.apply(ctx);
+ }
+
+ // opacity
+ if (this.style('opacity').hasValue()) {
+ ctx.globalAlpha = this.style('opacity').numValue();
+ }
+ }
+ }
+ svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
+
+ svg.Element.PathElementBase = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ if (ctx != null) ctx.beginPath();
+ return new svg.BoundingBox();
+ }
+
+ this.renderChildren = function(ctx) {
+ this.path(ctx);
+ svg.Mouse.checkPath(this, ctx);
+ if (ctx.fillStyle != '') ctx.fill();
+ if (ctx.strokeStyle != '') ctx.stroke();
+
+ var markers = this.getMarkers();
+ if (markers != null) {
+ if (this.attribute('marker-start').Definition.isUrl()) {
+ var marker = this.attribute('marker-start').Definition.getDefinition();
+ marker.render(ctx, markers[0][0], markers[0][1]);
+ }
+ if (this.attribute('marker-mid').Definition.isUrl()) {
+ var marker = this.attribute('marker-mid').Definition.getDefinition();
+ for (var i=1;i<markers.length-1;i++) {
+ marker.render(ctx, markers[i][0], markers[i][1]);
+ }
+ }
+ if (this.attribute('marker-end').Definition.isUrl()) {
+ var marker = this.attribute('marker-end').Definition.getDefinition();
+ marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
+ }
+ }
+ }
+
+ this.getBoundingBox = function() {
+ return this.path();
+ }
+
+ this.getMarkers = function() {
+ return null;
+ }
+ }
+ svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
+
+ // svg element
+ svg.Element.svg = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseClearContext = this.clearContext;
+ this.clearContext = function(ctx) {
+ this.baseClearContext(ctx);
+ svg.ViewPort.RemoveCurrent();
+ }
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ // initial values
+ ctx.strokeStyle = 'rgba(0,0,0,0)';
+ ctx.lineCap = 'butt';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 4;
+
+ this.baseSetContext(ctx);
+
+ // create new view port
+ if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
+ ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
+ }
+
+ var width = svg.ViewPort.width();
+ var height = svg.ViewPort.height();
+ if (this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
+ width = this.attribute('width').Length.toPixels('x');
+ height = this.attribute('height').Length.toPixels('y');
+
+ var x = 0;
+ var y = 0;
+ if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
+ x = -this.attribute('refX').Length.toPixels('x');
+ y = -this.attribute('refY').Length.toPixels('y');
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(width, y);
+ ctx.lineTo(width, height);
+ ctx.lineTo(x, height);
+ ctx.closePath();
+ ctx.clip();
+ }
+ svg.ViewPort.SetCurrent(width, height);
+
+ // viewbox
+ if (this.attribute('viewBox').hasValue()) {
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+ var minX = viewBox[0];
+ var minY = viewBox[1];
+ width = viewBox[2];
+ height = viewBox[3];
+
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ svg.ViewPort.width(),
+ width,
+ svg.ViewPort.height(),
+ height,
+ minX,
+ minY,
+ this.attribute('refX').value,
+ this.attribute('refY').value);
+
+ svg.ViewPort.RemoveCurrent();
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+ }
+ }
+ }
+ svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
+
+ // rect element
+ svg.Element.rect = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ var rx = this.attribute('rx').Length.toPixels('x');
+ var ry = this.attribute('ry').Length.toPixels('y');
+ if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
+ if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(x + rx, y);
+ ctx.lineTo(x + width - rx, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
+ ctx.lineTo(x + width, y + height - ry);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
+ ctx.lineTo(x + rx, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
+ ctx.lineTo(x, y + ry);
+ ctx.quadraticCurveTo(x, y, x + rx, y)
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(x, y, x + width, y + height);
+ }
+ }
+ svg.Element.rect.prototype = new svg.Element.PathElementBase;
+
+ // circle element
+ svg.Element.circle = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var cx = this.attribute('cx').Length.toPixels('x');
+ var cy = this.attribute('cy').Length.toPixels('y');
+ var r = this.attribute('r').Length.toPixels();
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
+ }
+ }
+ svg.Element.circle.prototype = new svg.Element.PathElementBase;
+
+ // ellipse element
+ svg.Element.ellipse = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
+ var rx = this.attribute('rx').Length.toPixels('x');
+ var ry = this.attribute('ry').Length.toPixels('y');
+ var cx = this.attribute('cx').Length.toPixels('x');
+ var cy = this.attribute('cy').Length.toPixels('y');
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(cx, cy - ry);
+ ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
+ ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
+ ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
+ ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
+ }
+ }
+ svg.Element.ellipse.prototype = new svg.Element.PathElementBase;
+
+ // line element
+ svg.Element.line = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.getPoints = function() {
+ return [
+ new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
+ new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
+ }
+
+ this.path = function(ctx) {
+ var points = this.getPoints();
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(points[0].x, points[0].y);
+ ctx.lineTo(points[1].x, points[1].y);
+ }
+
+ return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
+ }
+
+ this.getMarkers = function() {
+ var points = this.getPoints();
+ var a = points[0].angleTo(points[1]);
+ return [[points[0], a], [points[1], a]];
+ }
+ }
+ svg.Element.line.prototype = new svg.Element.PathElementBase;
+
+ // polyline element
+ svg.Element.polyline = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.points = svg.CreatePath(this.attribute('points').value);
+ this.path = function(ctx) {
+ var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ }
+ for (var i=1; i<this.points.length; i++) {
+ bb.addPoint(this.points[i].x, this.points[i].y);
+ if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
+ }
+ return bb;
+ }
+
+ this.getMarkers = function() {
+ var markers = [];
+ for (var i=0; i<this.points.length - 1; i++) {
+ markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
+ }
+ markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
+ return markers;
+ }
+ }
+ svg.Element.polyline.prototype = new svg.Element.PathElementBase;
+
+ // polygon element
+ svg.Element.polygon = function(node) {
+ this.base = svg.Element.polyline;
+ this.base(node);
+
+ this.basePath = this.path;
+ this.path = function(ctx) {
+ var bb = this.basePath(ctx);
+ if (ctx != null) {
+ ctx.lineTo(this.points[0].x, this.points[0].y);
+ ctx.closePath();
+ }
+ return bb;
+ }
+ }
+ svg.Element.polygon.prototype = new svg.Element.polyline;
+
+ // path element
+ svg.Element.path = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ var d = this.attribute('d').value;
+ // TODO: floating points, convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
+ d = d.replace(/,/gm,' '); // get rid of all commas
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
+ d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
+ d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
+ d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
+ d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
+ d = svg.compressSpaces(d); // compress multiple spaces
+ d = svg.trim(d);
+ this.PathParser = new (function(d) {
+ this.tokens = d.split(' ');
+
+ this.reset = function() {
+ this.i = -1;
+ this.command = '';
+ this.previousCommand = '';
+ this.start = new svg.Point(0, 0);
+ this.control = new svg.Point(0, 0);
+ this.current = new svg.Point(0, 0);
+ this.points = [];
+ this.angles = [];
+ }
+
+ this.isEnd = function() {
+ return this.i >= this.tokens.length - 1;
+ }
+
+ this.isCommandOrEnd = function() {
+ if (this.isEnd()) return true;
+ return this.tokens[this.i + 1].match(/[A-Za-z]/) != null;
+ }
+
+ this.isRelativeCommand = function() {
+ return this.command == this.command.toLowerCase();
+ }
+
+ this.getToken = function() {
+ this.i = this.i + 1;
+ return this.tokens[this.i];
+ }
+
+ this.getScalar = function() {
+ return parseFloat(this.getToken());
+ }
+
+ this.nextCommand = function() {
+ this.previousCommand = this.command;
+ this.command = this.getToken();
+ }
+
+ this.getPoint = function() {
+ var p = new svg.Point(this.getScalar(), this.getScalar());
+ return this.makeAbsolute(p);
+ }
+
+ this.getAsControlPoint = function() {
+ var p = this.getPoint();
+ this.control = p;
+ return p;
+ }
+
+ this.getAsCurrentPoint = function() {
+ var p = this.getPoint();
+ this.current = p;
+ return p;
+ }
+
+ this.getReflectedControlPoint = function() {
+ if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
+ return this.current;
+ }
+
+ // reflect point
+ var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);
+ return p;
+ }
+
+ this.makeAbsolute = function(p) {
+ if (this.isRelativeCommand()) {
+ p.x = this.current.x + p.x;
+ p.y = this.current.y + p.y;
+ }
+ return p;
+ }
+
+ this.addMarker = function(p, from) {
+ this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
+ }
+
+ this.addMarkerAngle = function(p, a) {
+ this.points.push(p);
+ this.angles.push(a);
+ }
+
+ this.getMarkerPoints = function() { return this.points; }
+ this.getMarkerAngles = function() {
+ for (var i=0; i<this.angles.length; i++) {
+ if (this.angles[i] == null) {
+ for (var j=i+1; j<this.angles.length; j++) {
+ if (this.angles[j] != null) {
+ this.angles[i] = this.angles[j];
+ break;
+ }
+ }
+ }
+ }
+ return this.angles;
+ }
+ })(d);
+
+ this.path = function(ctx) {
+ var pp = this.PathParser;
+ pp.reset();
+
+ var bb = new svg.BoundingBox();
+
+ if(this.attribute('visibility').value=='hidden') return;
+
+ if (ctx != null) ctx.beginPath();
+ while (!pp.isEnd()) {
+ pp.nextCommand();
+ switch (pp.command.toUpperCase()) {
+ case 'M':
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.moveTo(p.x, p.y);
+ pp.start = pp.current;
+ while (!pp.isCommandOrEnd()) {
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.lineTo(p.x, p.y);
+ }
+ break;
+ case 'L':
+ while (!pp.isCommandOrEnd()) {
+ var c = pp.current;
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p, c);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.lineTo(p.x, p.y);
+ }
+ break;
+ case 'H':
+ while (!pp.isCommandOrEnd()) {
+ var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
+ pp.addMarker(newP, pp.current);
+ pp.current = newP;
+ bb.addPoint(pp.current.x, pp.current.y);
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+ }
+ break;
+ case 'V':
+ while (!pp.isCommandOrEnd()) {
+ var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
+ pp.addMarker(newP, pp.current);
+ pp.current = newP;
+ bb.addPoint(pp.current.x, pp.current.y);
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+ }
+ break;
+ case 'C':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var p1 = pp.getPoint();
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'S':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var p1 = pp.getReflectedControlPoint();
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'Q':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'T':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var cntrl = pp.getReflectedControlPoint();
+ pp.control = cntrl;
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl);
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'A':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var rx = pp.getScalar();
+ var ry = pp.getScalar();
+ var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
+ var largeArcFlag = pp.getScalar();
+ var sweepFlag = pp.getScalar();
+ var cp = pp.getAsCurrentPoint();
+
+ // Conversion from endpoint to center parameterization
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ // x1', y1'
+ var currp = new svg.Point(
+ Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
+ -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
+ );
+ // adjust radii
+ var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
+ if (l > 1) {
+ rx *= Math.sqrt(l);
+ ry *= Math.sqrt(l);
+ }
+ // cx', cy'
+ var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
+ ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
+ (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
+ );
+ if (isNaN(s)) s = 0;
+ var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
+ // cx, cy
+ var centp = new svg.Point(
+ (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
+ (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
+ );
+ // vector magnitude
+ var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
+ // ratio between two vectors
+ var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
+ // angle between two vectors
+ var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
+ // initial angle
+ var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
+ // angle delta
+ var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
+ var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
+ var ad = a(u, v);
+ if (r(u,v) <= -1) ad = Math.PI;
+ if (r(u,v) >= 1) ad = 0;
+
+ if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
+ if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
+
+ // for markers
+ var halfWay = new svg.Point(
+ centp.x - rx * Math.cos((a1 + ad) / 2),
+ centp.y - ry * Math.sin((a1 + ad) / 2)
+ );
+ pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
+ pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
+
+ bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
+ if (ctx != null) {
+ var r = rx > ry ? rx : ry;
+ var sx = rx > ry ? 1 : rx / ry;
+ var sy = rx > ry ? ry / rx : 1;
+
+ ctx.translate(centp.x, centp.y);
+ ctx.rotate(xAxisRotation);
+ ctx.scale(sx, sy);
+ ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
+ ctx.scale(1/sx, 1/sy);
+ ctx.rotate(-xAxisRotation);
+ ctx.translate(-centp.x, -centp.y);
+ }
+ }
+ break;
+ case 'Z':
+ if (ctx != null) ctx.closePath();
+ pp.current = pp.start;
+ }
+ }
+
+ return bb;
+ }
+
+ this.getMarkers = function() {
+ var points = this.PathParser.getMarkerPoints();
+ var angles = this.PathParser.getMarkerAngles();
+
+ var markers = [];
+ for (var i=0; i<points.length; i++) {
+ markers.push([points[i], angles[i]]);
+ }
+ return markers;
+ }
+ }
+ svg.Element.path.prototype = new svg.Element.PathElementBase;
+
+ // pattern element
+ svg.Element.pattern = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.createPattern = function(ctx, element) {
+ // render me using a temporary svg element
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+ tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
+ tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
+ tempSvg.children = this.children;
+
+ var c = document.createElement('canvas');
+ c.width = this.attribute('width').Length.toPixels();
+ c.height = this.attribute('height').Length.toPixels();
+ tempSvg.render(c.getContext('2d'));
+ return ctx.createPattern(c, 'repeat');
+ }
+ }
+ svg.Element.pattern.prototype = new svg.Element.ElementBase;
+
+ // marker element
+ svg.Element.marker = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.baseRender = this.render;
+ this.render = function(ctx, point, angle) {
+ ctx.translate(point.x, point.y);
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
+ ctx.save();
+
+ // render me using a temporary svg element
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+ tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
+ tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
+ tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
+ tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
+ tempSvg.children = this.children;
+ tempSvg.render(ctx);
+
+ ctx.restore();
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
+ ctx.translate(-point.x, -point.y);
+ }
+ }
+ svg.Element.marker.prototype = new svg.Element.ElementBase;
+
+ // definitions element
+ svg.Element.defs = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.render = function(ctx) {
+ // NOOP
+ }
+ }
+ svg.Element.defs.prototype = new svg.Element.ElementBase;
+
+ // base for gradients
+ svg.Element.GradientBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
+
+ this.stops = [];
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+ this.stops.push(child);
+ }
+
+ this.getGradient = function() {
+ // OVERRIDE ME!
+ }
+
+ this.createGradient = function(ctx, element) {
+ var stopsContainer = this;
+ if (this.attribute('xlink:href').hasValue()) {
+ stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
+ }
+
+ var g = this.getGradient(ctx, element);
+ for (var i=0; i<stopsContainer.stops.length; i++) {
+ g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
+ }
+ return g;
+ }
+ }
+ svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
+
+ // linear gradient element
+ svg.Element.linearGradient = function(node) {
+ this.base = svg.Element.GradientBase;
+ this.base(node);
+
+ this.getGradient = function(ctx, element) {
+ var bb = element.getBoundingBox();
+
+ var x1 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('x1').numValue()
+ : this.attribute('x1').Length.toPixels('x'));
+ var y1 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('y1').numValue()
+ : this.attribute('y1').Length.toPixels('y'));
+ var x2 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('x2').numValue()
+ : this.attribute('x2').Length.toPixels('x'));
+ var y2 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('y2').numValue()
+ : this.attribute('y2').Length.toPixels('y'));
+
+ var p1 = new svg.Point(x1, y1);
+ var p2 = new svg.Point(x2, y2);
+ if (this.attribute('gradientTransform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('gradientTransform').value);
+ transform.applyToPoint(p1);
+ transform.applyToPoint(p2);
+ }
+
+ return ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+ svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
+
+ // radial gradient element
+ svg.Element.radialGradient = function(node) {
+ this.base = svg.Element.GradientBase;
+ this.base(node);
+
+ this.getGradient = function(ctx, element) {
+ var bb = element.getBoundingBox();
+
+ var cx = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('cx').numValue()
+ : this.attribute('cx').Length.toPixels('x'));
+ var cy = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('cy').numValue()
+ : this.attribute('cy').Length.toPixels('y'));
+
+ var fx = cx;
+ var fy = cy;
+ if (this.attribute('fx').hasValue()) {
+ fx = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('fx').numValue()
+ : this.attribute('fx').Length.toPixels('x'));
+ }
+ if (this.attribute('fy').hasValue()) {
+ fy = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('fy').numValue()
+ : this.attribute('fy').Length.toPixels('y'));
+ }
+
+ var r = (this.gradientUnits == 'objectBoundingBox'
+ ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
+ : this.attribute('r').Length.toPixels());
+
+ var c = new svg.Point(cx, cy);
+ var f = new svg.Point(fx, fy);
+ if (this.attribute('gradientTransform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('gradientTransform').value);
+ transform.applyToPoint(c);
+ transform.applyToPoint(f);
+
+ for (var i=0; i<transform.transforms.length; i++) {
+ // average the scaling part of the transform, apply to radius
+ var scale1 = transform.transforms[i].m[0];
+ var scale2 = transform.transforms[i].m[3];
+ r = r * ((scale1 + scale2) / 2.0);
+ }
+ }
+
+ return ctx.createRadialGradient(f.x, f.y, 0, c.x, c.y, r);
+ }
+ }
+ svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
+
+ // gradient stop element
+ svg.Element.stop = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.offset = this.attribute('offset').numValue();
+
+ var stopColor = this.style('stop-color');
+ if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
+ this.color = stopColor.value;
+ }
+ svg.Element.stop.prototype = new svg.Element.ElementBase;
+
+ // animation base element
+ svg.Element.AnimateBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ svg.Animations.push(this);
+
+ this.duration = 0.0;
+ this.begin = this.attribute('begin').Time.toMilliseconds();
+ this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
+
+ this.getProperty = function() {
+ var attributeType = this.attribute('attributeType').value;
+ var attributeName = this.attribute('attributeName').value;
+
+ if (attributeType == 'CSS') {
+ return this.parent.style(attributeName, true);
+ }
+ return this.parent.attribute(attributeName, true);
+ };
+
+ this.initialValue = null;
+ this.removed = false;
+
+ this.calcValue = function() {
+ // OVERRIDE ME!
+ return '';
+ }
+
+ this.update = function(delta) {
+ // set initial value
+ if (this.initialValue == null) {
+ this.initialValue = this.getProperty().value;
+ }
+
+ // if we're past the end time
+ if (this.duration > this.maxDuration) {
+ // loop for indefinitely repeating animations
+ if (this.attribute('repeatCount').value == 'indefinite') {
+ this.duration = 0.0
+ }
+ else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
+ this.removed = true;
+ this.getProperty().value = this.initialValue;
+ return true;
+ }
+ else {
+ return false; // no updates made
+ }
+ }
+ this.duration = this.duration + delta;
+
+ // if we're past the begin time
+ var updated = false;
+ if (this.begin < this.duration) {
+ var newValue = this.calcValue(); // tween
+
+ if (this.attribute('type').hasValue()) {
+ // for transform, etc.
+ var type = this.attribute('type').value;
+ newValue = type + '(' + newValue + ')';
+ }
+
+ this.getProperty().value = newValue;
+ updated = true;
+ }
+
+ return updated;
+ }
+
+ // fraction of duration we've covered
+ this.progress = function() {
+ return ((this.duration - this.begin) / (this.maxDuration - this.begin));
+ }
+ }
+ svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
+
+ // animate element
+ svg.Element.animate = function(node) {
+ this.base = svg.Element.AnimateBase;
+ this.base(node);
+
+ this.calcValue = function() {
+ var from = this.attribute('from').numValue();
+ var to = this.attribute('to').numValue();
+
+ // tween value linearly
+ return from + (to - from) * this.progress();
+ };
+ }
+ svg.Element.animate.prototype = new svg.Element.AnimateBase;
+
+ // animate color element
+ svg.Element.animateColor = function(node) {
+ this.base = svg.Element.AnimateBase;
+ this.base(node);
+
+ this.calcValue = function() {
+ var from = new RGBColor(this.attribute('from').value);
+ var to = new RGBColor(this.attribute('to').value);
+
+ if (from.ok && to.ok) {
+ // tween color linearly
+ var r = from.r + (to.r - from.r) * this.progress();
+ var g = from.g + (to.g - from.g) * this.progress();
+ var b = from.b + (to.b - from.b) * this.progress();
+ return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
+ }
+ return this.attribute('from').value;
+ };
+ }
+ svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
+
+ // animate transform element
+ svg.Element.animateTransform = function(node) {
+ this.base = svg.Element.animate;
+ this.base(node);
+ }
+ svg.Element.animateTransform.prototype = new svg.Element.animate;
+
+ // text element
+ svg.Element.text = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ if (node != null) {
+ // add children
+ this.children = [];
+ for (var i=0; i<node.childNodes.length; i++) {
+ var childNode = node.childNodes[i];
+ if (childNode.nodeType == 1) { // capture tspan and tref nodes
+ this.addChild(childNode, true);
+ }
+ else if (childNode.nodeType == 3) { // capture text
+ this.addChild(new svg.Element.tspan(childNode), false);
+ }
+ }
+ }
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+ if (this.attribute('text-anchor').hasValue()) {
+ var textAnchor = this.attribute('text-anchor').value;
+ ctx.textAlign = textAnchor == 'middle' ? 'center' : textAnchor;
+ }
+ if (this.attribute('alignment-baseline').hasValue()) ctx.textBaseline = this.attribute('alignment-baseline').value;
+ }
+
+ this.renderChildren = function(ctx) {
+ if(this.attribute('visibility').value=='hidden') return;
+
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+
+ if (child.attribute('x').hasValue()) {
+ child.x = child.attribute('x').Length.toPixels('x');
+ }
+ else {
+ if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
+ child.x = x;
+ x += child.measureText(ctx);
+ }
+
+ if (child.attribute('y').hasValue()) {
+ child.y = child.attribute('y').Length.toPixels('y');
+ }
+ else {
+ if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
+ child.y = y;
+ }
+
+ child.render(ctx);
+ }
+ }
+ }
+ svg.Element.text.prototype = new svg.Element.RenderedElementBase;
+
+ // text base
+ svg.Element.TextElementBase = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.renderChildren = function(ctx) {
+ ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
+ }
+
+ this.getText = function() {
+ // OVERRIDE ME
+ }
+
+ this.measureText = function(ctx) {
+ var textToMeasure = svg.compressSpaces(this.getText());
+ if (!ctx.measureText) return textToMeasure.length * 10;
+ return ctx.measureText(textToMeasure).width;
+ }
+ }
+ svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
+
+ // tspan
+ svg.Element.tspan = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ // TEXT ELEMENT
+ this.text = node.nodeType == 3 ? node.nodeValue : node.childNodes[0].nodeValue;
+ this.getText = function() {
+ return this.text;
+ }
+ }
+ svg.Element.tspan.prototype = new svg.Element.TextElementBase;
+
+ // tref
+ svg.Element.tref = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.getText = function() {
+ var element = this.attribute('xlink:href').Definition.getDefinition();
+ if (element != null) return element.children[0].getText();
+ }
+ }
+ svg.Element.tref.prototype = new svg.Element.TextElementBase;
+
+ // a element
+ svg.Element.a = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.hasText = true;
+ for (var i=0; i<node.childNodes.length; i++) {
+ if (node.childNodes[i].nodeType != 3) this.hasText = false;
+ }
+
+ // this might contain text
+ this.text = this.hasText ? node.childNodes[0].nodeValue : '';
+ this.getText = function() {
+ return this.text;
+ }
+
+ this.baseRenderChildren = this.renderChildren;
+ this.renderChildren = function(ctx) {
+ if (this.hasText) {
+ // render as text element
+ this.baseRenderChildren(ctx);
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+ svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));
+ }
+ else {
+ // render as temporary group
+ var g = new svg.Element.g();
+ g.children = this.children;
+ g.parent = this;
+ g.render(ctx);
+ }
+ }
+
+ this.onclick = function() {
+ window.open(this.attribute('xlink:href').value);
+ }
+
+ this.onmousemove = function() {
+ svg.ctx.canvas.style.cursor = 'pointer';
+ }
+ }
+ svg.Element.a.prototype = new svg.Element.TextElementBase;
+
+ // image element
+ svg.Element.image = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ svg.Images.push(this);
+ this.img = document.createElement('img');
+ this.loaded = false;
+ var that = this;
+ this.img.onload = function() { that.loaded = true; }
+ this.img.src = this.attribute('xlink:href').value;
+
+ this.renderChildren = function(ctx) {
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ if (width == 0 || height == 0) return;
+
+ ctx.save();
+ ctx.translate(x, y);
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ width,
+ this.img.width,
+ height,
+ this.img.height,
+ 0,
+ 0);
+ ctx.drawImage(this.img, 0, 0);
+ ctx.restore();
+ }
+ }
+ svg.Element.image.prototype = new svg.Element.RenderedElementBase;
+
+ // group element
+ svg.Element.g = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.getBoundingBox = function() {
+ var bb = new svg.BoundingBox();
+ for (var i=0; i<this.children.length; i++) {
+ bb.addBoundingBox(this.children[i].getBoundingBox());
+ }
+ return bb;
+ };
+ }
+ svg.Element.g.prototype = new svg.Element.RenderedElementBase;
+
+ // symbol element
+ svg.Element.symbol = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+
+ // viewbox
+ if (this.attribute('viewBox').hasValue()) {
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+ var minX = viewBox[0];
+ var minY = viewBox[1];
+ width = viewBox[2];
+ height = viewBox[3];
+
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ this.attribute('width').Length.toPixels('x'),
+ width,
+ this.attribute('height').Length.toPixels('y'),
+ height,
+ minX,
+ minY);
+
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+ }
+ }
+ }
+ svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;
+
+ // style element
+ svg.Element.style = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ var css = node.childNodes[0].nodeValue;
+ css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gm, ''); // remove comments
+ css = svg.compressSpaces(css); // replace whitespace
+ var cssDefs = css.split('}');
+ for (var i=0; i<cssDefs.length; i++) {
+ if (svg.trim(cssDefs[i]) != '') {
+ var cssDef = cssDefs[i].split('{');
+ var cssClasses = cssDef[0].split(',');
+ var cssProps = cssDef[1].split(';');
+ for (var j=0; j<cssClasses.length; j++) {
+ var cssClass = svg.trim(cssClasses[j]);
+ if (cssClass != '') {
+ var props = {};
+ for (var k=0; k<cssProps.length; k++) {
+ var prop = cssProps[k].split(':');
+ var name = prop[0];
+ var value = prop[1];
+ if (name != null && value != null) {
+ props[svg.trim(prop[0])] = new svg.Property(svg.trim(prop[0]), svg.trim(prop[1]));
+ }
+ }
+ svg.Styles[cssClass] = props;
+ }
+ }
+ }
+ }
+ }
+ svg.Element.style.prototype = new svg.Element.ElementBase;
+
+ // use element
+ svg.Element.use = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+ if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
+ if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
+ }
+
+ this.getDefinition = function() {
+ var element = this.attribute('xlink:href').Definition.getDefinition();
+ if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
+ if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
+ return element;
+ }
+
+ this.path = function(ctx) {
+ var element = this.getDefinition();
+ if (element != null) element.path(ctx);
+ }
+
+ this.renderChildren = function(ctx) {
+ var element = this.getDefinition();
+ if (element != null) element.render(ctx);
+ }
+ }
+ svg.Element.use.prototype = new svg.Element.RenderedElementBase;
+
+ // clip element
+ svg.Element.clipPath = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.apply = function(ctx) {
+ for (var i=0; i<this.children.length; i++) {
+ if (this.children[i].path) {
+ this.children[i].path(ctx);
+ ctx.clip();
+ }
+ }
+ }
+ }
+ svg.Element.clipPath.prototype = new svg.Element.ElementBase;
+
+ // title element, do nothing
+ svg.Element.title = function(node) {
+ }
+ svg.Element.title.prototype = new svg.Element.ElementBase;
+
+ // desc element, do nothing
+ svg.Element.desc = function(node) {
+ }
+ svg.Element.desc.prototype = new svg.Element.ElementBase;
+
+ svg.Element.MISSING = function(node) {
+ console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
+ }
+ svg.Element.MISSING.prototype = new svg.Element.ElementBase;
+
+ // element factory
+ svg.CreateElement = function(node) {
+ var className = node.nodeName.replace(/^[^:]+:/,'');
+ var e = null;
+ if (typeof(svg.Element[className]) != 'undefined') {
+ e = new svg.Element[className](node);
+ }
+ else {
+ e = new svg.Element.MISSING(node);
+ }
+
+ e.type = node.nodeName;
+ return e;
+ }
+
+ // load from url
+ svg.load = function(ctx, url) {
+ svg.loadXml(ctx, svg.ajax(url));
+ }
+
+ // load from xml
+ svg.loadXml = function(ctx, xml) {
+ svg.init(ctx);
+
+ var mapXY = function(p) {
+ var e = ctx.canvas;
+ while (e) {
+ p.x -= e.offsetLeft;
+ p.y -= e.offsetTop;
+ e = e.offsetParent;
+ }
+ if (window.scrollX) p.x += window.scrollX;
+ if (window.scrollY) p.y += window.scrollY;
+ return p;
+ }
+
+ // bind mouse
+ if (svg.opts == null || svg.opts['ignoreMouse'] != true) {
+ ctx.canvas.onclick = function(e) {
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+ svg.Mouse.onclick(p.x, p.y);
+ };
+ ctx.canvas.onmousemove = function(e) {
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+ svg.Mouse.onmousemove(p.x, p.y);
+ };
+ }
+
+ var dom = svg.parseXml(xml);
+ var e = svg.CreateElement(dom.documentElement);
+
+ // render loop
+ var isFirstRender = true;
+ var draw = function() {
+ if (svg.opts == null || svg.opts['ignoreDimensions'] != true) {
+ // set canvas size
+ if (e.style('width').hasValue()) {
+ ctx.canvas.width = e.style('width').Length.toPixels(ctx.canvas.parentNode.clientWidth);
+ }
+ if (e.style('height').hasValue()) {
+ ctx.canvas.height = e.style('height').Length.toPixels(ctx.canvas.parentNode.clientHeight);
+ }
+ }
+ svg.ViewPort.SetCurrent(ctx.canvas.clientWidth, ctx.canvas.clientHeight);
+
+ if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
+ if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
+ if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
+ e.attribute('width', true).value = svg.opts['scaleWidth'];
+ e.attribute('height', true).value = svg.opts['scaleHeight'];
+ e.attribute('viewBox', true).value = '0 0 ' + ctx.canvas.clientWidth + ' ' + ctx.canvas.clientHeight;
+ e.attribute('preserveAspectRatio', true).value = 'none';
+ }
+
+ // clear and render
+ if (svg.opts == null || svg.opts['ignoreClear'] != true) {
+ ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
+ }
+ e.render(ctx);
+ if (isFirstRender) {
+ isFirstRender = false;
+ if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
+ }
+ }
+
+ var waitingForImages = true;
+ if (svg.ImagesLoaded()) {
+ waitingForImages = false;
+ draw();
+ }
+ svg.intervalID = setInterval(function() {
+ var needUpdate = false;
+
+ if (waitingForImages && svg.ImagesLoaded()) {
+ waitingForImages = false;
+ needUpdate = true;
+ }
+
+ // need update from mouse events?
+ if (svg.opts == null || svg.opts['ignoreMouse'] != true) {
+ needUpdate = needUpdate | svg.Mouse.hasEvents();
+ }
+
+ // need update from animations?
+ if (svg.opts == null || svg.opts['ignoreAnimation'] != true) {
+ for (var i=0; i<svg.Animations.length; i++) {
+ needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
+ }
+ }
+
+ // need update from redraw?
+ if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
+ if (svg.opts['forceRedraw']() == true) needUpdate = true;
+ }
+
+ // render if needed
+ if (needUpdate) {
+ draw();
+ svg.Mouse.runEvents(); // run and clear our events
+ }
+ }, 1000 / svg.FRAMERATE);
+ }
+
+ svg.stop = function() {
+ if (svg.intervalID) {
+ clearInterval(svg.intervalID);
+ }
+ }
+
+ svg.Mouse = new (function() {
+ this.events = [];
+ this.hasEvents = function() { return this.events.length != 0; }
+
+ this.onclick = function(x, y) {
+ this.events.push({ type: 'onclick', x: x, y: y,
+ run: function(e) { if (e.onclick) e.onclick(); }
+ });
+ }
+
+ this.onmousemove = function(x, y) {
+ this.events.push({ type: 'onmousemove', x: x, y: y,
+ run: function(e) { if (e.onmousemove) e.onmousemove(); }
+ });
+ }
+
+ this.eventElements = [];
+
+ this.checkPath = function(element, ctx) {
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
+ }
+ }
+
+ this.checkBoundingBox = function(element, bb) {
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
+ }
+ }
+
+ this.runEvents = function() {
+ svg.ctx.canvas.style.cursor = '';
+
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ var element = this.eventElements[i];
+ while (element) {
+ e.run(element);
+ element = element.parent;
+ }
+ }
+
+ // done running, clear
+ this.events = [];
+ this.eventElements = [];
+ }
+ });
+
+ return svg;
+ }
+})();
+
+if (CanvasRenderingContext2D) {
+ CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
+ canvg(this.canvas, s, {
+ ignoreMouse: true,
+ ignoreAnimation: true,
+ ignoreDimensions: true,
+ ignoreClear: true,
+ offsetX: dx,
+ offsetY: dy,
+ scaleWidth: dw,
+ scaleHeight: dh
+ });
+ }
+}
\ No newline at end of file
diff --git a/js/canvg/rgbcolor.js b/js/canvg/rgbcolor.js
new file mode 100644
index 0000000..0338a16
--- /dev/null
+++ b/js/canvg/rgbcolor.js
@@ -0,0 +1,288 @@
+/**
+ * A class to parse color values
+ * @author Stoyan Stefanov <sstoo(a)gmail.com>
+ * @link http://www.phpied.com/rgb-color-parser-in-javascript/
+ * @license Use it if you like it
+ */
+function RGBColor(color_string)
+{
+ this.ok = false;
+
+ // strip any leading #
+ if (color_string.charAt(0) == '#') { // remove # if any
+ color_string = color_string.substr(1,6);
+ }
+
+ color_string = color_string.replace(/ /g,'');
+ color_string = color_string.toLowerCase();
+
+ // before getting into regexps, try simple matches
+ // and overwrite the input
+ var simple_colors = {
+ aliceblue: 'f0f8ff',
+ antiquewhite: 'faebd7',
+ aqua: '00ffff',
+ aquamarine: '7fffd4',
+ azure: 'f0ffff',
+ beige: 'f5f5dc',
+ bisque: 'ffe4c4',
+ black: '000000',
+ blanchedalmond: 'ffebcd',
+ blue: '0000ff',
+ blueviolet: '8a2be2',
+ brown: 'a52a2a',
+ burlywood: 'deb887',
+ cadetblue: '5f9ea0',
+ chartreuse: '7fff00',
+ chocolate: 'd2691e',
+ coral: 'ff7f50',
+ cornflowerblue: '6495ed',
+ cornsilk: 'fff8dc',
+ crimson: 'dc143c',
+ cyan: '00ffff',
+ darkblue: '00008b',
+ darkcyan: '008b8b',
+ darkgoldenrod: 'b8860b',
+ darkgray: 'a9a9a9',
+ darkgreen: '006400',
+ darkkhaki: 'bdb76b',
+ darkmagenta: '8b008b',
+ darkolivegreen: '556b2f',
+ darkorange: 'ff8c00',
+ darkorchid: '9932cc',
+ darkred: '8b0000',
+ darksalmon: 'e9967a',
+ darkseagreen: '8fbc8f',
+ darkslateblue: '483d8b',
+ darkslategray: '2f4f4f',
+ darkturquoise: '00ced1',
+ darkviolet: '9400d3',
+ deeppink: 'ff1493',
+ deepskyblue: '00bfff',
+ dimgray: '696969',
+ dodgerblue: '1e90ff',
+ feldspar: 'd19275',
+ firebrick: 'b22222',
+ floralwhite: 'fffaf0',
+ forestgreen: '228b22',
+ fuchsia: 'ff00ff',
+ gainsboro: 'dcdcdc',
+ ghostwhite: 'f8f8ff',
+ gold: 'ffd700',
+ goldenrod: 'daa520',
+ gray: '808080',
+ green: '008000',
+ greenyellow: 'adff2f',
+ honeydew: 'f0fff0',
+ hotpink: 'ff69b4',
+ indianred : 'cd5c5c',
+ indigo : '4b0082',
+ ivory: 'fffff0',
+ khaki: 'f0e68c',
+ lavender: 'e6e6fa',
+ lavenderblush: 'fff0f5',
+ lawngreen: '7cfc00',
+ lemonchiffon: 'fffacd',
+ lightblue: 'add8e6',
+ lightcoral: 'f08080',
+ lightcyan: 'e0ffff',
+ lightgoldenrodyellow: 'fafad2',
+ lightgrey: 'd3d3d3',
+ lightgreen: '90ee90',
+ lightpink: 'ffb6c1',
+ lightsalmon: 'ffa07a',
+ lightseagreen: '20b2aa',
+ lightskyblue: '87cefa',
+ lightslateblue: '8470ff',
+ lightslategray: '778899',
+ lightsteelblue: 'b0c4de',
+ lightyellow: 'ffffe0',
+ lime: '00ff00',
+ limegreen: '32cd32',
+ linen: 'faf0e6',
+ magenta: 'ff00ff',
+ maroon: '800000',
+ mediumaquamarine: '66cdaa',
+ mediumblue: '0000cd',
+ mediumorchid: 'ba55d3',
+ mediumpurple: '9370d8',
+ mediumseagreen: '3cb371',
+ mediumslateblue: '7b68ee',
+ mediumspringgreen: '00fa9a',
+ mediumturquoise: '48d1cc',
+ mediumvioletred: 'c71585',
+ midnightblue: '191970',
+ mintcream: 'f5fffa',
+ mistyrose: 'ffe4e1',
+ moccasin: 'ffe4b5',
+ navajowhite: 'ffdead',
+ navy: '000080',
+ oldlace: 'fdf5e6',
+ olive: '808000',
+ olivedrab: '6b8e23',
+ orange: 'ffa500',
+ orangered: 'ff4500',
+ orchid: 'da70d6',
+ palegoldenrod: 'eee8aa',
+ palegreen: '98fb98',
+ paleturquoise: 'afeeee',
+ palevioletred: 'd87093',
+ papayawhip: 'ffefd5',
+ peachpuff: 'ffdab9',
+ peru: 'cd853f',
+ pink: 'ffc0cb',
+ plum: 'dda0dd',
+ powderblue: 'b0e0e6',
+ purple: '800080',
+ red: 'ff0000',
+ rosybrown: 'bc8f8f',
+ royalblue: '4169e1',
+ saddlebrown: '8b4513',
+ salmon: 'fa8072',
+ sandybrown: 'f4a460',
+ seagreen: '2e8b57',
+ seashell: 'fff5ee',
+ sienna: 'a0522d',
+ silver: 'c0c0c0',
+ skyblue: '87ceeb',
+ slateblue: '6a5acd',
+ slategray: '708090',
+ snow: 'fffafa',
+ springgreen: '00ff7f',
+ steelblue: '4682b4',
+ tan: 'd2b48c',
+ teal: '008080',
+ thistle: 'd8bfd8',
+ tomato: 'ff6347',
+ turquoise: '40e0d0',
+ violet: 'ee82ee',
+ violetred: 'd02090',
+ wheat: 'f5deb3',
+ white: 'ffffff',
+ whitesmoke: 'f5f5f5',
+ yellow: 'ffff00',
+ yellowgreen: '9acd32'
+ };
+ for (var key in simple_colors) {
+ if (color_string == key) {
+ color_string = simple_colors[key];
+ }
+ }
+ // emd of simple type-in colors
+
+ // array of color definition objects
+ var color_defs = [
+ {
+ re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
+ example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
+ process: function (bits){
+ return [
+ parseInt(bits[1]),
+ parseInt(bits[2]),
+ parseInt(bits[3])
+ ];
+ }
+ },
+ {
+ re: /^(\w{2})(\w{2})(\w{2})$/,
+ example: ['#00ff00', '336699'],
+ process: function (bits){
+ return [
+ parseInt(bits[1], 16),
+ parseInt(bits[2], 16),
+ parseInt(bits[3], 16)
+ ];
+ }
+ },
+ {
+ re: /^(\w{1})(\w{1})(\w{1})$/,
+ example: ['#fb0', 'f0f'],
+ process: function (bits){
+ return [
+ parseInt(bits[1] + bits[1], 16),
+ parseInt(bits[2] + bits[2], 16),
+ parseInt(bits[3] + bits[3], 16)
+ ];
+ }
+ }
+ ];
+
+ // search through the definitions to find a match
+ for (var i = 0; i < color_defs.length; i++) {
+ var re = color_defs[i].re;
+ var processor = color_defs[i].process;
+ var bits = re.exec(color_string);
+ if (bits) {
+ channels = processor(bits);
+ this.r = channels[0];
+ this.g = channels[1];
+ this.b = channels[2];
+ this.ok = true;
+ }
+
+ }
+
+ // validate/cleanup values
+ this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
+ this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
+ this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
+
+ // some getters
+ this.toRGB = function () {
+ return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
+ }
+ this.toHex = function () {
+ var r = this.r.toString(16);
+ var g = this.g.toString(16);
+ var b = this.b.toString(16);
+ if (r.length == 1) r = '0' + r;
+ if (g.length == 1) g = '0' + g;
+ if (b.length == 1) b = '0' + b;
+ return '#' + r + g + b;
+ }
+
+ // help
+ this.getHelpXML = function () {
+
+ var examples = new Array();
+ // add regexps
+ for (var i = 0; i < color_defs.length; i++) {
+ var example = color_defs[i].example;
+ for (var j = 0; j < example.length; j++) {
+ examples[examples.length] = example[j];
+ }
+ }
+ // add type-in colors
+ for (var sc in simple_colors) {
+ examples[examples.length] = sc;
+ }
+
+ var xml = document.createElement('ul');
+ xml.setAttribute('id', 'rgbcolor-examples');
+ for (var i = 0; i < examples.length; i++) {
+ try {
+ var list_item = document.createElement('li');
+ var list_color = new RGBColor(examples[i]);
+ var example_div = document.createElement('div');
+ example_div.style.cssText =
+ 'margin: 3px; '
+ + 'border: 1px solid black; '
+ + 'background:' + list_color.toHex() + '; '
+ + 'color:' + list_color.toHex()
+ ;
+ example_div.appendChild(document.createTextNode('test'));
+ var list_item_value = document.createTextNode(
+ ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
+ );
+ list_item.appendChild(example_div);
+ list_item.appendChild(list_item_value);
+ xml.appendChild(list_item);
+
+ } catch(e){}
+ }
+ return xml;
+
+ }
+
+}
+
diff --git a/js/functions.js b/js/functions.js
index 765f887..6266ba6 100644
--- a/js/functions.js
+++ b/js/functions.js
@@ -26,6 +26,13 @@ var ajax_message_init = false;
var codemirror_editor = false;
/**
+ * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests
+ */
+var chart_activeTimeouts = new Object();
+
+
+
+/**
* Add a hidden field to the form to indicate that this will be an
* Ajax request (only if this hidden field does not exist)
*
@@ -1237,6 +1244,7 @@ $(document).ready(function(){
* optional, defaults to 'Loading...'
* @param var timeout number of milliseconds for the message to be visible
* optional, defaults to 5000
+ * @return jQuery object jQuery Element that holds the message div
*/
function PMA_ajaxShowMessage(message, timeout) {
@@ -1301,7 +1309,7 @@ function PMA_ajaxShowMessage(message, timeout) {
})
}
- return $("#loading");
+ return $("#loading");
}
/**
@@ -1332,7 +1340,7 @@ function PMA_showNoticeForEnum(selectElement) {
/**
* Generates a dialog box to pop up the create_table form
*/
-function PMA_createTableDialog( div, url , target){
+function PMA_createTableDialog( div, url , target) {
/**
* @var button_options Object that stores the options passed to jQueryUI
* dialog
@@ -1377,6 +1385,119 @@ function PMA_createTableDialog( div, url , target){
}
/**
+ * Creates a highcharts chart in the given container
+ *
+ * @param var settings object with highcharts properties that should be applied. (See also http://www.highcharts.com/ref/)
+ * requires at least settings.chart.renderTo and settings.series to be set.
+ * In addition there may be an additional property object 'realtime' that allows for realtime charting:
+ * realtime: {
+ * url: adress to get the data from (will always add token, ajax_request=1 and chart_data=1 to the GET request)
+ * type: the GET request will also add type=[value of the type property] to the request
+ * callback: Callback function that should draw the point, it's called with 4 parameters in this order:
+ * - the chart object
+ * - the current response value of the GET request, JSON parsed
+ * - the previous response value of the GET request, JSON parsed
+ * - the number of added points
+ *
+ * @return object The created highcharts instance
+ */
+function PMA_createChart(passedSettings) {
+ var container = passedSettings.chart.renderTo;
+
+ var settings = {
+ chart: {
+ type: 'spline',
+ marginRight: 10,
+ events: {
+ load: function() {
+ var thisChart = this;
+ var lastValue=null, curValue=null;
+ var numLoadedPoints=0, otherSum=0;
+ var diff;
+ // No realtime updates for graphs that are being exported, and disabled when no callback is set
+ if(thisChart.options.chart.forExport==true || !passedSettings.realtime || !passedSettings.realtime.callback) return;
+
+ thisChart.options.realtime.timeoutCallBack = function() {
+ $.get(passedSettings.realtime.url,{ajax_request:1, chart_data:1, type:passedSettings.realtime.type},function(data) {
+ if(chart_activeTimeouts[container]==null) return;
+
+ curValue = jQuery.parseJSON(data);
+ //if(lastValue==null) lastValue = curValue;
+
+ if(lastValue==null) diff = curValue.x - thisChart.xAxis[0].getExtremes().max;
+ else diff = parseInt(curValue.x - lastValue.x);
+
+ thisChart.xAxis[0].setExtremes(thisChart.xAxis[0].getExtremes().min+diff, thisChart.xAxis[0].getExtremes().max+diff, false);
+
+ passedSettings.realtime.callback(thisChart,curValue,lastValue,numLoadedPoints);
+
+ lastValue = curValue;
+ numLoadedPoints++;
+ chart_activeTimeouts[container] = setTimeout(thisChart.options.realtime.timeoutCallBack, thisChart.options.realtime.refreshRate);
+
+ });
+ }
+
+ chart_activeTimeouts[container] = setTimeout(thisChart.options.realtime.timeoutCallBack, 0);
+ }
+ }
+ },
+ plotOptions: {
+ series: {
+ marker: {
+ radius: 3
+ }
+ }
+ },
+ credits: {
+ enabled:false
+ },
+ xAxis: {
+ type: 'datetime',
+ },
+ yAxis: {
+ min: 0,
+ title: {
+ text: PMA_messages['strTotalCount']
+ },
+ plotLines: [{
+ value: 0,
+ width: 1,
+ color: '#808080'
+ }]
+ },
+ tooltip: {
+ formatter: function() {
+ return '<b>'+ this.series.name +'</b><br/>'+
+ Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) +'<br/>'+
+ Highcharts.numberFormat(this.y, 2);
+ }
+ },
+ exporting: {
+ enabled: true
+ },
+ series: []
+ }
+
+ /* Set/Get realtime chart default values */
+ if(passedSettings.realtime) {
+ if(!passedSettings.realtime.refreshRate)
+ passedSettings.realtime.refreshRate = 5000;
+
+ if(!passedSettings.realtime.numMaxPoints)
+ passedSettings.realtime.numMaxPoints = 32;
+
+ settings.xAxis.min = new Date().getTime() - passedSettings.realtime.numMaxPoints * passedSettings.realtime.refreshRate;
+ settings.xAxis.max = new Date().getTime() + passedSettings.realtime.refreshRate / 2;
+ }
+
+ // Overwrite/Merge default settings with passedsettings
+ $.extend(true,settings,passedSettings);
+
+ return new Highcharts.Chart(settings);
+}
+
+/**
* jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
* return a jQuery object yet and hence cannot be chained
*
@@ -2006,8 +2127,10 @@ $(document).ready(function() {
}
});
+$(document).ready(initTooltips);
+
/* Displays tooltips */
-$(document).ready(function() {
+function initTooltips() {
// Hide the footnotes from the footer (which are displayed for
// JavaScript-disabled browsers) since the tooltip is sufficient
$(".footnotes").hide();
@@ -2033,7 +2156,7 @@ $(document).ready(function() {
style: { background: '#ffffcc' }
});
});
-});
+}
function menuResize()
{
diff --git a/js/highcharts/exporting.js b/js/highcharts/exporting.js
new file mode 100644
index 0000000..4f134d5
--- /dev/null
+++ b/js/highcharts/exporting.js
@@ -0,0 +1,690 @@
+/**
+ * @license Highcharts JS v2.1.4 (2011-03-02)
+ * Exporting module
+ *
+ * (c) 2010 Torstein Hønsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+// JSLint options:
+/*global Highcharts, document, window, Math, setTimeout */
+
+(function() { // encapsulate
+
+// create shortcuts
+var HC = Highcharts,
+ Chart = HC.Chart,
+ addEvent = HC.addEvent,
+ createElement = HC.createElement,
+ discardElement = HC.discardElement,
+ css = HC.css,
+ merge = HC.merge,
+ each = HC.each,
+ extend = HC.extend,
+ math = Math,
+ mathMax = math.max,
+ doc = document,
+ win = window,
+ hasTouch = 'ontouchstart' in doc.documentElement,
+ M = 'M',
+ L = 'L',
+ DIV = 'div',
+ HIDDEN = 'hidden',
+ NONE = 'none',
+ PREFIX = 'highcharts-',
+ ABSOLUTE = 'absolute',
+ PX = 'px',
+
+
+
+ // Add language and get the defaultOptions
+ defaultOptions = HC.setOptions({
+ lang: {
+ downloadPNG: 'Download PNG image',
+ downloadJPEG: 'Download JPEG image',
+ downloadPDF: 'Download PDF document',
+ downloadSVG: 'Download SVG vector image',
+ exportButtonTitle: 'Export to raster or vector image',
+ printButtonTitle: 'Print the chart'
+ }
+ });
+
+// Buttons and menus are collected in a separate config option set called 'navigation'.
+// This can be extended later to add control buttons like zoom and pan right click menus.
+defaultOptions.navigation = {
+ menuStyle: {
+ border: '1px solid #A0A0A0',
+ background: '#FFFFFF'
+ },
+ menuItemStyle: {
+ padding: '0 5px',
+ background: NONE,
+ color: '#303030',
+ fontSize: hasTouch ? '14px' : '11px'
+ },
+ menuItemHoverStyle: {
+ background: '#4572A5',
+ color: '#FFFFFF'
+ },
+
+ buttonOptions: {
+ align: 'right',
+ backgroundColor: {
+ linearGradient: [0, 0, 0, 20],
+ stops: [
+ [0.4, '#F7F7F7'],
+ [0.6, '#E3E3E3']
+ ]
+ },
+ borderColor: '#B0B0B0',
+ borderRadius: 3,
+ borderWidth: 1,
+ //enabled: true,
+ height: 20,
+ hoverBorderColor: '#909090',
+ hoverSymbolFill: '#81A7CF',
+ hoverSymbolStroke: '#4572A5',
+ symbolFill: '#E0E0E0',
+ //symbolSize: 12,
+ symbolStroke: '#A0A0A0',
+ //symbolStrokeWidth: 1,
+ symbolX: 11.5,
+ symbolY: 10.5,
+ verticalAlign: 'top',
+ width: 24,
+ y: 10
+ }
+};
+
+
+
+// Add the export related options
+defaultOptions.exporting = {
+ //enabled: true,
+ //filename: 'chart',
+ type: 'image/png',
+ url: 'chart_export.php',
+ width: 800,
+ buttons: {
+ exportButton: {
+ //enabled: true,
+ symbol: 'exportIcon',
+ x: -10,
+ symbolFill: '#A8BF77',
+ hoverSymbolFill: '#768F3E',
+ _titleKey: 'exportButtonTitle',
+ menuItems: [{
+ textKey: 'downloadPNG',
+ onclick: function() {
+ this.exportChart();
+ }
+ },/* {
+ textKey: 'downloadJPEG',
+ onclick: function() {
+ this.exportChart({
+ type: 'image/jpeg'
+ });
+ }
+ }, {
+ textKey: 'downloadPDF',
+ onclick: function() {
+ this.exportChart({
+ type: 'application/pdf'
+ });
+ }
+ }, */{
+ textKey: 'downloadSVG',
+ onclick: function() {
+ this.exportChart({
+ type: 'image/svg+xml'
+ });
+ }
+ }/*, {
+ text: 'View SVG',
+ onclick: function() {
+ var svg = this.getSVG()
+ .replace(/</g, '\n<')
+ .replace(/>/g, '>');
+
+ doc.body.innerHTML = '<pre>'+ svg +'</pre>';
+ }
+ }*/]
+
+ },
+ printButton: {
+ //enabled: true,
+ symbol: 'printIcon',
+ x: -36,
+ symbolFill: '#B5C9DF',
+ hoverSymbolFill: '#779ABF',
+ _titleKey: 'printButtonTitle',
+ onclick: function() {
+ this.print();
+ }
+ }
+ }
+};
+
+
+
+extend(Chart.prototype, {
+ /**
+ * Return an SVG representation of the chart
+ *
+ * @param additionalOptions {Object} Additional chart options for the generated SVG representation
+ */
+ getSVG: function(additionalOptions) {
+ var chart = this,
+ chartCopy,
+ sandbox,
+ svg,
+ seriesOptions,
+ config,
+ pointOptions,
+ pointMarker,
+ options = merge(chart.options, additionalOptions); // copy the options and add extra options
+
+ // IE compatibility hack for generating SVG content that it doesn't really understand
+ if (!doc.createElementNS) {
+ doc.createElementNS = function(ns, tagName) {
+ var elem = doc.createElement(tagName);
+ elem.getBBox = function() {
+ return chart.renderer.Element.prototype.getBBox.apply({ element: elem });
+ };
+ return elem;
+ };
+ }
+
+ // create a sandbox where a new chart will be generated
+ sandbox = createElement(DIV, null, {
+ position: ABSOLUTE,
+ top: '-9999em',
+ width: chart.chartWidth + PX,
+ height: chart.chartHeight + PX
+ }, doc.body);
+
+ // override some options
+ extend(options.chart, {
+ renderTo: sandbox,
+ forExport: true
+ });
+ options.exporting.enabled = false; // hide buttons in print
+ options.chart.plotBackgroundImage = null; // the converter doesn't handle images
+ // prepare for replicating the chart
+ options.series = [];
+ each(chart.series, function(serie) {
+ seriesOptions = serie.options;
+
+ seriesOptions.animation = false; // turn off animation
+ seriesOptions.showCheckbox = false;
+
+ // remove image markers
+ if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
+ seriesOptions.marker.symbol = 'circle';
+ }
+
+ seriesOptions.data = [];
+
+ each(serie.data, function(point) {
+
+ // extend the options by those values that can be expressed in a number or array config
+ config = point.config;
+ pointOptions = {
+ x: point.x,
+ y: point.y,
+ name: point.name
+ };
+
+ if (typeof config == 'object' && point.config && config.constructor != Array) {
+ extend(pointOptions, config);
+ }
+
+ seriesOptions.data.push(pointOptions); // copy fresh updated data
+
+ // remove image markers
+ pointMarker = point.config && point.config.marker;
+ if (pointMarker && /^url\(/.test(pointMarker.symbol)) {
+ delete pointMarker.symbol;
+ }
+ });
+
+ options.series.push(seriesOptions);
+ });
+
+ // generate the chart copy
+ chartCopy = new Highcharts.Chart(options);
+
+ // get the SVG from the container's innerHTML
+ svg = chartCopy.container.innerHTML;
+
+ // free up memory
+ options = null;
+ chartCopy.destroy();
+ discardElement(sandbox);
+
+ // sanitize
+ svg = svg
+ .replace(/zIndex="[^"]+"/g, '')
+ .replace(/isShadow="[^"]+"/g, '')
+ .replace(/symbolName="[^"]+"/g, '')
+ .replace(/jQuery[0-9]+="[^"]+"/g, '')
+ .replace(/isTracker="[^"]+"/g, '')
+ .replace(/url\([^#]+#/g, 'url(#')
+ /*.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
+ .replace(/ href=/, ' xlink:href=')
+ .replace(/preserveAspectRatio="none">/g, 'preserveAspectRatio="none"/>')*/
+ /* This fails in IE < 8
+ .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
+ return s2 +'.'+ s3[0];
+ })*/
+
+ // IE specific
+ .replace(/id=([^" >]+)/g, 'id="$1"')
+ .replace(/class=([^" ]+)/g, 'class="$1"')
+ .replace(/ transform /g, ' ')
+ .replace(/:(path|rect)/g, '$1')
+ .replace(/style="([^"]+)"/g, function(s) {
+ return s.toLowerCase();
+ });
+
+ // IE9 beta bugs with innerHTML. Test again with final IE9.
+ svg = svg.replace(/(url\(#highcharts-[0-9]+)"/g, '$1')
+ .replace(/"/g, "'");
+ if (svg.match(/ xmlns="/g).length == 2) {
+ svg = svg.replace(/xmlns="[^"]+"/, '');
+ }
+
+ return svg;
+ },
+
+ /**
+ * Submit the SVG representation of the chart to the server
+ * @param {Object} options Exporting options. Possible members are url, type and width.
+ * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
+ */
+ exportChart: function(options, chartOptions) {
+ var form,
+ chart = this,
+ canvas=createElement('canvas');
+
+ $('body').append(canvas);
+ $(canvas).hide();
+
+ var submitData = function(chartData) {
+ // merge the options
+ options = merge(chart.options.exporting, options);
+
+ // create the form
+ form = createElement('form', {
+ method: 'post',
+ action: options.url
+ }, {
+ display: NONE
+ }, doc.body);
+
+ // add the values
+ each(['filename', 'type', 'width', 'image','token'], function(name) {
+ createElement('input', {
+ type: HIDDEN,
+ name: name,
+ value: {
+ filename: options.filename || 'chart',
+ type: options.type,
+ width: options.width,
+ image: chartData,
+ token: pma_token
+ }[name]
+ }, null, form);
+ });
+
+ // submit
+ form.submit();
+
+ // clean up
+ discardElement(form);
+ }
+
+ if(options && options.type=='image/svg+xml') {
+ submitData(chart.getSVG(chartOptions));
+ } else {
+ // Generate data uri and submit once done
+ canvg(canvas, chart.getSVG(chartOptions),{
+ ignoreAnimation:true,
+ ignoreMouse:true,
+ renderCallback:function() { submitData(canvas.toDataURL()); }
+ });
+ }
+ },
+
+ /**
+ * Print the chart
+ */
+ print: function() {
+
+ var chart = this,
+ container = chart.container,
+ origDisplay = [],
+ origParent = container.parentNode,
+ body = doc.body,
+ childNodes = body.childNodes;
+
+ if (chart.isPrinting) { // block the button while in printing mode
+ return;
+ }
+
+ chart.isPrinting = true;
+
+ // hide all body content
+ each(childNodes, function(node, i) {
+ if (node.nodeType == 1) {
+ origDisplay[i] = node.style.display;
+ node.style.display = NONE;
+ }
+ });
+
+ // pull out the chart
+ body.appendChild(container);
+
+ // print
+ win.print();
+
+ // allow the browser to prepare before reverting
+ setTimeout(function() {
+
+ // put the chart back in
+ origParent.appendChild(container);
+
+ // restore all body content
+ each(childNodes, function(node, i) {
+ if (node.nodeType == 1) {
+ node.style.display = origDisplay[i];
+ }
+ });
+
+ chart.isPrinting = false;
+
+ }, 1000);
+
+ },
+
+ /**
+ * Display a popup menu for choosing the export type
+ *
+ * @param {String} name An identifier for the menu
+ * @param {Array} items A collection with text and onclicks for the items
+ * @param {Number} x The x position of the opener button
+ * @param {Number} y The y position of the opener button
+ * @param {Number} width The width of the opener button
+ * @param {Number} height The height of the opener button
+ */
+ contextMenu: function(name, items, x, y, width, height) {
+ var chart = this,
+ navOptions = chart.options.navigation,
+ menuItemStyle = navOptions.menuItemStyle,
+ chartWidth = chart.chartWidth,
+ chartHeight = chart.chartHeight,
+ cacheName = 'cache-'+ name,
+ menu = chart[cacheName],
+ menuPadding = mathMax(width, height), // for mouse leave detection
+ boxShadow = '3px 3px 10px #888',
+ innerMenu,
+ hide,
+ menuStyle;
+
+ // create the menu only the first time
+ if (!menu) {
+
+ // create a HTML element above the SVG
+ chart[cacheName] = menu = createElement(DIV, {
+ className: PREFIX + name
+ }, {
+ position: ABSOLUTE,
+ zIndex: 1000,
+ padding: menuPadding + PX
+ }, chart.container);
+
+ innerMenu = createElement(DIV, null,
+ extend({
+ MozBoxShadow: boxShadow,
+ WebkitBoxShadow: boxShadow,
+ boxShadow: boxShadow
+ }, navOptions.menuStyle) , menu);
+
+ // hide on mouse out
+ hide = function() {
+ css(menu, { display: NONE });
+ };
+
+ addEvent(menu, 'mouseleave', hide);
+
+
+ // create the items
+ each(items, function(item) {
+ if (item) {
+ var div = createElement(DIV, {
+ onmouseover: function() {
+ css(this, navOptions.menuItemHoverStyle);
+ },
+ onmouseout: function() {
+ css(this, menuItemStyle);
+ },
+ innerHTML: item.text || HC.getOptions().lang[item.textKey]
+ }, extend({
+ cursor: 'pointer'
+ }, menuItemStyle), innerMenu);
+
+ div[hasTouch ? 'ontouchstart' : 'onclick'] = function() {
+ hide();
+ item.onclick.apply(chart, arguments);
+ };
+
+ }
+ });
+
+ chart.exportMenuWidth = menu.offsetWidth;
+ chart.exportMenuHeight = menu.offsetHeight;
+ }
+
+ menuStyle = { display: 'block' };
+
+ // if outside right, right align it
+ if (x + chart.exportMenuWidth > chartWidth) {
+ menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
+ } else {
+ menuStyle.left = (x - menuPadding) + PX;
+ }
+ // if outside bottom, bottom align it
+ if (y + height + chart.exportMenuHeight > chartHeight) {
+ menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
+ } else {
+ menuStyle.top = (y + height - menuPadding) + PX;
+ }
+
+ css(menu, menuStyle);
+ },
+
+ /**
+ * Add the export button to the chart
+ */
+ addButton: function(options) {
+ var chart = this,
+ renderer = chart.renderer,
+ btnOptions = merge(chart.options.navigation.buttonOptions, options),
+ onclick = btnOptions.onclick,
+ menuItems = btnOptions.menuItems,
+ /*position = chart.getAlignment(btnOptions),
+ buttonLeft = position.x,
+ buttonTop = position.y,*/
+ buttonWidth = btnOptions.width,
+ buttonHeight = btnOptions.height,
+ box,
+ symbol,
+ button,
+ borderWidth = btnOptions.borderWidth,
+ boxAttr = {
+ stroke: btnOptions.borderColor
+
+ },
+ symbolAttr = {
+ stroke: btnOptions.symbolStroke,
+ fill: btnOptions.symbolFill
+ };
+
+ if (btnOptions.enabled === false) {
+ return;
+ }
+
+ // element to capture the click
+ function revert() {
+ symbol.attr(symbolAttr);
+ box.attr(boxAttr);
+ }
+
+ // the box border
+ box = renderer.rect(
+ 0,
+ 0,
+ buttonWidth,
+ buttonHeight,
+ btnOptions.borderRadius,
+ borderWidth
+ )
+ //.translate(buttonLeft, buttonTop) // to allow gradients
+ .align(btnOptions, true)
+ .attr(extend({
+ fill: btnOptions.backgroundColor,
+ 'stroke-width': borderWidth,
+ zIndex: 19
+ }, boxAttr)).add();
+
+ // the invisible element to track the clicks
+ button = renderer.rect(
+ 0,
+ 0,
+ buttonWidth,
+ buttonHeight,
+ 0
+ )
+ .align(btnOptions)
+ .attr({
+ fill: 'rgba(255, 255, 255, 0.001)',
+ title: HC.getOptions().lang[btnOptions._titleKey],
+ zIndex: 21
+ }).css({
+ cursor: 'pointer'
+ })
+ .on('mouseover', function() {
+ symbol.attr({
+ stroke: btnOptions.hoverSymbolStroke,
+ fill: btnOptions.hoverSymbolFill
+ });
+ box.attr({
+ stroke: btnOptions.hoverBorderColor
+ });
+ })
+ .on('mouseout', revert)
+ .on('click', revert)
+ .add();
+
+ //addEvent(button.element, 'click', revert);
+
+ // add the click event
+ if (menuItems) {
+ onclick = function(e) {
+ revert();
+ var bBox = button.getBBox();
+ chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
+ };
+ }
+ /*addEvent(button.element, 'click', function() {
+ onclick.apply(chart, arguments);
+ });*/
+ button.on('click', function() {
+ onclick.apply(chart, arguments);
+ });
+
+ // the icon
+ symbol = renderer.symbol(
+ btnOptions.symbol,
+ btnOptions.symbolX,
+ btnOptions.symbolY,
+ (btnOptions.symbolSize || 12) / 2
+ )
+ .align(btnOptions, true)
+ .attr(extend(symbolAttr, {
+ 'stroke-width': btnOptions.symbolStrokeWidth || 1,
+ zIndex: 20
+ })).add();
+
+
+
+ }
+});
+
+// Create the export icon
+HC.Renderer.prototype.symbols.exportIcon = function(x, y, radius) {
+ return [
+ M, // the disk
+ x - radius, y + radius,
+ L,
+ x + radius, y + radius,
+ x + radius, y + radius * 0.5,
+ x - radius, y + radius * 0.5,
+ 'Z',
+ M, // the arrow
+ x, y + radius * 0.5,
+ L,
+ x - radius * 0.5, y - radius / 3,
+ x - radius / 6, y - radius / 3,
+ x - radius / 6, y - radius,
+ x + radius / 6, y - radius,
+ x + radius / 6, y - radius / 3,
+ x + radius * 0.5, y - radius / 3,
+ 'Z'
+ ];
+};
+// Create the print icon
+HC.Renderer.prototype.symbols.printIcon = function(x, y, radius) {
+ return [
+ M, // the printer
+ x - radius, y + radius * 0.5,
+ L,
+ x + radius, y + radius * 0.5,
+ x + radius, y - radius / 3,
+ x - radius, y - radius / 3,
+ 'Z',
+ M, // the upper sheet
+ x - radius * 0.5, y - radius / 3,
+ L,
+ x - radius * 0.5, y - radius,
+ x + radius * 0.5, y - radius,
+ x + radius * 0.5, y - radius / 3,
+ 'Z',
+ M, // the lower sheet
+ x - radius * 0.5, y + radius * 0.5,
+ L,
+ x - radius * 0.75, y + radius,
+ x + radius * 0.75, y + radius,
+ x + radius * 0.5, y + radius * 0.5,
+ 'Z'
+ ];
+};
+
+
+// Add the buttons on chart load
+Chart.prototype.callbacks.push(function(chart) {
+ var n,
+ exportingOptions = chart.options.exporting,
+ buttons = exportingOptions.buttons;
+
+ if (exportingOptions.enabled !== false) {
+
+ for (n in buttons) {
+ chart.addButton(buttons[n]);
+ }
+ }
+});
+
+
+})();
\ No newline at end of file
diff --git a/js/highcharts/highcharts.js b/js/highcharts/highcharts.js
new file mode 100644
index 0000000..c15862b
--- /dev/null
+++ b/js/highcharts/highcharts.js
@@ -0,0 +1,10671 @@
+// ==ClosureCompiler==
+// @compilation_level SIMPLE_OPTIMIZATIONS
+
+/**
+ * @license Highcharts JS v2.1.4 (2011-03-02)
+ *
+ * (c) 2009-2010 Torstein Hønsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+// JSLint options:
+/*jslint forin: true */
+/*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */
+
+(function() {
+// encapsulated variables
+var doc = document,
+ win = window,
+ math = Math,
+ mathRound = math.round,
+ mathFloor = math.floor,
+ mathCeil = math.ceil,
+ mathMax = math.max,
+ mathMin = math.min,
+ mathAbs = math.abs,
+ mathCos = math.cos,
+ mathSin = math.sin,
+ mathPI = math.PI,
+ deg2rad = mathPI * 2 / 360,
+
+
+ // some variables
+ userAgent = navigator.userAgent,
+ isIE = /msie/i.test(userAgent) && !win.opera,
+ docMode8 = doc.documentMode == 8,
+ isWebKit = /AppleWebKit/.test(userAgent),
+ isFirefox = /Firefox/.test(userAgent),
+ //hasSVG = win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
+ hasSVG = !!doc.createElementNS && !!doc.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGRect,
+ SVG_NS = 'http://www.w3.org/2000/svg',
+ hasTouch = 'ontouchstart' in doc.documentElement,
+ colorCounter,
+ symbolCounter,
+ symbolSizes = {},
+ idCounter = 0,
+ timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
+ garbageBin,
+ defaultOptions,
+ dateFormat, // function
+ globalAnimation,
+ pathAnim,
+
+
+ // some constants for frequently used strings
+ UNDEFINED,
+ DIV = 'div',
+ ABSOLUTE = 'absolute',
+ RELATIVE = 'relative',
+ HIDDEN = 'hidden',
+ PREFIX = 'highcharts-',
+ VISIBLE = 'visible',
+ PX = 'px',
+ NONE = 'none',
+ M = 'M',
+ L = 'L',
+ /*
+ * Empirical lowest possible opacities for TRACKER_FILL
+ * IE6: 0.002
+ * IE7: 0.002
+ * IE8: 0.002
+ * IE9: 0.00000000001 (unlimited)
+ * FF: 0.00000000001 (unlimited)
+ * Chrome: 0.000001
+ * Safari: 0.000001
+ * Opera: 0.00000000001 (unlimited)
+ */
+ TRACKER_FILL = 'rgba(192,192,192,'+ (hasSVG ? 0.000001 : 0.002) +')', // invisible but clickable
+ NORMAL_STATE = '',
+ HOVER_STATE = 'hover',
+ SELECT_STATE = 'select',
+
+ // time methods, changed based on whether or not UTC is used
+ makeTime,
+ getMinutes,
+ getHours,
+ getDay,
+ getDate,
+ getMonth,
+ getFullYear,
+ setMinutes,
+ setHours,
+ setDate,
+ setMonth,
+ setFullYear,
+
+ // check for a custom HighchartsAdapter defined prior to this file
+ globalAdapter = win.HighchartsAdapter,
+ adapter = globalAdapter || {},
+
+ // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
+ // and all the utility functions will be null. In that case they are populated by the
+ // default adapters below.
+ each = adapter.each,
+ grep = adapter.grep,
+ map = adapter.map,
+ merge = adapter.merge,
+ hyphenate = adapter.hyphenate,
+ addEvent = adapter.addEvent,
+ removeEvent = adapter.removeEvent,
+ fireEvent = adapter.fireEvent,
+ animate = adapter.animate,
+ stop = adapter.stop,
+
+ // lookup over the types and the associated classes
+ seriesTypes = {},
+ hoverChart;
+
+/**
+ * Extend an object with the members of another
+ * @param {Object} a The object to be extended
+ * @param {Object} b The object to add to the first one
+ */
+function extend(a, b) {
+ if (!a) {
+ a = {};
+ }
+ for (var n in b) {
+ a[n] = b[n];
+ }
+ return a;
+}
+
+/**
+ * Shortcut for parseInt
+ * @param {Object} s
+ */
+function pInt(s, mag) {
+ return parseInt(s, mag || 10);
+}
+
+/**
+ * Check for string
+ * @param {Object} s
+ */
+function isString(s) {
+ return typeof s == 'string';
+}
+
+/**
+ * Check for object
+ * @param {Object} obj
+ */
+function isObject(obj) {
+ return typeof obj == 'object';
+}
+
+/**
+ * Check for number
+ * @param {Object} n
+ */
+function isNumber(n) {
+ return typeof n == 'number';
+}
+
+/**
+ * Remove last occurence of an item from an array
+ * @param {Array} arr
+ * @param {Mixed} item
+ */
+function erase(arr, item) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] == item) {
+ arr.splice(i, 1);
+ break;
+ }
+ }
+ //return arr;
+}
+
+/**
+ * Returns true if the object is not null or undefined. Like MooTools' $.defined.
+ * @param {Object} obj
+ */
+function defined (obj) {
+ return obj !== UNDEFINED && obj !== null;
+}
+
+/**
+ * Set or get an attribute or an object of attributes. Can't use jQuery attr because
+ * it attempts to set expando properties on the SVG element, which is not allowed.
+ *
+ * @param {Object} elem The DOM element to receive the attribute(s)
+ * @param {String|Object} prop The property or an abject of key-value pairs
+ * @param {String} value The value if a single property is set
+ */
+function attr(elem, prop, value) {
+ var key,
+ setAttribute = 'setAttribute',
+ ret;
+
+ // if the prop is a string
+ if (isString(prop)) {
+ // set the value
+ if (defined(value)) {
+
+ elem[setAttribute](prop, value);
+
+ // get the value
+ } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
+ ret = elem.getAttribute(prop);
+ }
+
+ // else if prop is defined, it is a hash of key/value pairs
+ } else if (defined(prop) && isObject(prop)) {
+ for (key in prop) {
+ elem[setAttribute](key, prop[key]);
+ }
+ }
+ return ret;
+}
+/**
+ * Check if an element is an array, and if not, make it into an array. Like
+ * MooTools' $.splat.
+ */
+function splat(obj) {
+ if (!obj || obj.constructor != Array) {
+ obj = [obj];
+ }
+ return obj;
+}
+
+
+
+/**
+ * Return the first value that is defined. Like MooTools' $.pick.
+ */
+function pick() {
+ var args = arguments,
+ i,
+ arg,
+ length = args.length;
+ for (i = 0; i < length; i++) {
+ arg = args[i];
+ if (typeof arg !== 'undefined' && arg !== null) {
+ return arg;
+ }
+ }
+}
+/**
+ * Make a style string from a JS object
+ * @param {Object} style
+ */
+function serializeCSS(style) {
+ var s = '',
+ key;
+ // serialize the declaration
+ for (key in style) {
+ s += hyphenate(key) +':'+ style[key] + ';';
+ }
+ return s;
+
+}
+/**
+ * Set CSS on a give element
+ * @param {Object} el
+ * @param {Object} styles
+ */
+function css (el, styles) {
+ if (isIE) {
+ if (styles && styles.opacity !== UNDEFINED) {
+ styles.filter = 'alpha(opacity='+ (styles.opacity * 100) +')';
+ }
+ }
+ extend(el.style, styles);
+}
+
+/**
+ * Utility function to create element with attributes and styles
+ * @param {Object} tag
+ * @param {Object} attribs
+ * @param {Object} styles
+ * @param {Object} parent
+ * @param {Object} nopad
+ */
+function createElement (tag, attribs, styles, parent, nopad) {
+ var el = doc.createElement(tag);
+ if (attribs) {
+ extend(el, attribs);
+ }
+ if (nopad) {
+ css(el, {padding: 0, border: NONE, margin: 0});
+ }
+ if (styles) {
+ css(el, styles);
+ }
+ if (parent) {
+ parent.appendChild(el);
+ }
+ return el;
+}
+
+/**
+ * Set the global animation to either a given value, or fall back to the
+ * given chart's animation option
+ * @param {Object} animation
+ * @param {Object} chart
+ */
+function setAnimation(animation, chart) {
+ globalAnimation = pick(animation, chart.animation);
+}
+
+/*
+ * Define the adapter for frameworks. If an external adapter is not defined,
+ * Highcharts reverts to the built-in jQuery adapter.
+ */
+if (globalAdapter && globalAdapter.init) {
+ globalAdapter.init();
+}
+if (!globalAdapter && win.jQuery) {
+ var jQ = jQuery;
+
+ /**
+ * Utility for iterating over an array. Parameters are reversed compared to jQuery.
+ * @param {Array} arr
+ * @param {Function} fn
+ */
+ each = function(arr, fn) {
+ for (var i = 0, len = arr.length; i < len; i++) {
+ if (fn.call(arr[i], arr[i], i, arr) === false) {
+ return i;
+ }
+ }
+ };
+
+ /**
+ * Filter an array
+ */
+ grep = jQ.grep;
+
+ /**
+ * Map an array
+ * @param {Array} arr
+ * @param {Function} fn
+ */
+ map = function(arr, fn){
+ //return jQuery.map(arr, fn);
+ var results = [];
+ for (var i = 0, len = arr.length; i < len; i++) {
+ results[i] = fn.call(arr[i], arr[i], i, arr);
+ }
+ return results;
+
+ };
+
+ /**
+ * Deep merge two objects and return a third object
+ */
+ merge = function(){
+ var args = arguments;
+ return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
+ };
+
+ /**
+ * Convert a camelCase string to a hyphenated string
+ * @param {String} str
+ */
+ hyphenate = function (str) {
+ return str.replace(/([A-Z])/g, function(a, b){ return '-'+ b.toLowerCase(); });
+ };
+
+ /**
+ * Add an event listener
+ * @param {Object} el A HTML element or custom object
+ * @param {String} event The event type
+ * @param {Function} fn The event handler
+ */
+ addEvent = function (el, event, fn){
+ jQ(el).bind(event, fn);
+ };
+
+ /**
+ * Remove event added with addEvent
+ * @param {Object} el The object
+ * @param {String} eventType The event type. Leave blank to remove all events.
+ * @param {Function} handler The function to remove
+ */
+ removeEvent = function(el, eventType, handler) {
+ // workaround for jQuery issue with unbinding custom events:
+ // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-even…
+ var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
+ if (doc[func] && !el[func]) {
+ el[func] = function() {};
+ }
+
+ jQ(el).unbind(eventType, handler);
+ };
+
+ /**
+ * Fire an event on a custom object
+ * @param {Object} el
+ * @param {String} type
+ * @param {Object} eventArguments
+ * @param {Function} defaultFunction
+ */
+ fireEvent = function(el, type, eventArguments, defaultFunction) {
+ var event = jQ.Event(type),
+ detachedType = 'detached'+ type;
+ extend(event, eventArguments);
+
+ // Prevent jQuery from triggering the object method that is named the
+ // same as the event. For example, if the event is 'select', jQuery
+ // attempts calling el.select and it goes into a loop.
+ if (el[type]) {
+ el[detachedType] = el[type];
+ el[type] = null;
+ }
+
+ // trigger it
+ jQ(el).trigger(event);
+
+ // attach the method
+ if (el[detachedType]) {
+ el[type] = el[detachedType];
+ el[detachedType] = null;
+ }
+
+ if (defaultFunction && !event.isDefaultPrevented()) {
+ defaultFunction(event);
+ }
+ };
+
+ /**
+ * Animate a HTML element or SVG element wrapper
+ * @param {Object} el
+ * @param {Object} params
+ * @param {Object} options jQuery-like animation options: duration, easing, callback
+ */
+ animate = function (el, params, options) {
+ var $el = jQ(el);
+ if (params.d) {
+ el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
+ params.d = 1; // because in jQuery, animating to an array has a different meaning
+ }
+
+ $el.stop();
+ $el.animate(params, options);
+
+ };
+ /**
+ * Stop running animation
+ */
+ stop = function (el) {
+ jQ(el).stop();
+ };
+
+
+ // extend jQuery
+ jQ.extend( jQ.easing, {
+ easeOutQuad: function (x, t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ }
+ });
+
+ // extend the animate function to allow SVG animations
+ var oldStepDefault = jQuery.fx.step._default,
+ oldCur = jQuery.fx.prototype.cur;
+
+ // do the step
+ jQ.fx.step._default = function(fx){
+ var elem = fx.elem;
+ if (elem.attr) { // is SVG element wrapper
+ elem.attr(fx.prop, fx.now);
+ } else {
+ oldStepDefault.apply(this, arguments);
+ }
+ };
+ // animate paths
+ jQ.fx.step.d = function(fx) {
+ var elem = fx.elem;
+
+
+ // Normally start and end should be set in state == 0, but sometimes,
+ // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
+ // in these cases
+ if (!fx.started) {
+ var ends = pathAnim.init(elem, elem.d, elem.toD);
+ fx.start = ends[0];
+ fx.end = ends[1];
+ fx.started = true;
+ }
+
+
+ // interpolate each value of the path
+ elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
+
+ };
+ // get the current value
+ jQ.fx.prototype.cur = function() {
+ var elem = this.elem,
+ r;
+ if (elem.attr) { // is SVG element wrapper
+ r = elem.attr(this.prop);
+ } else {
+ r = oldCur.apply(this, arguments);
+ }
+ return r;
+ };
+}
+
+
+/**
+ * Add a global listener for mousemove events
+ */
+/*addEvent(doc, 'mousemove', function(e) {
+ if (globalMouseMove) {
+ globalMouseMove(e);
+ }
+});*/
+
+/**
+ * Path interpolation algorithm used across adapters
+ */
+pathAnim = {
+ /**
+ * Prepare start and end values so that the path can be animated one to one
+ */
+ init: function(elem, fromD, toD) {
+ fromD = fromD || '';
+ var shift = elem.shift,
+ bezier = fromD.indexOf('C') > -1,
+ numParams = bezier ? 7 : 3,
+ endLength,
+ slice,
+ i,
+ start = fromD.split(' '),
+ end = [].concat(toD), // copy
+ startBaseLine,
+ endBaseLine,
+ sixify = function(arr) { // in splines make move points have six parameters like bezier curves
+ i = arr.length;
+ while (i--) {
+ if (arr[i] == M) {
+ arr.splice(i + 1, 0, arr[i+1], arr[i+2], arr[i+1], arr[i+2]);
+ }
+ }
+ };
+
+ if (bezier) {
+ sixify(start);
+ sixify(end);
+ }
+
+ // pull out the base lines before padding
+ if (elem.isArea) {
+ startBaseLine = start.splice(start.length - 6, 6);
+ endBaseLine = end.splice(end.length - 6, 6);
+ }
+
+ // if shifting points, prepend a dummy point to the end path
+ if (shift) {
+
+ end = [].concat(end).splice(0, numParams).concat(end);
+ elem.shift = false; // reset for following animations
+ }
+
+ // copy and append last point until the length matches the end length
+ if (start.length) {
+ endLength = end.length;
+ while (start.length < endLength) {
+
+ //bezier && sixify(start);
+ slice = [].concat(start).splice(start.length - numParams, numParams);
+ if (bezier) { // disable first control point
+ slice[numParams - 6] = slice[numParams - 2];
+ slice[numParams - 5] = slice[numParams - 1];
+ }
+ start = start.concat(slice);
+ }
+ }
+
+ if (startBaseLine) { // append the base lines for areas
+ start = start.concat(startBaseLine);
+ end = end.concat(endBaseLine);
+ }
+ return [start, end];
+ },
+
+ /**
+ * Interpolate each value of the path and return the array
+ */
+ step: function(start, end, pos, complete) {
+ var ret = [],
+ i = start.length,
+ startVal;
+
+ if (pos == 1) { // land on the final path without adjustment points appended in the ends
+ ret = complete;
+
+ } else if (i == end.length && pos < 1) {
+ while (i--) {
+ startVal = parseFloat(start[i]);
+ ret[i] =
+ isNaN(startVal) ? // a letter instruction like M or L
+ start[i] :
+ pos * (parseFloat(end[i] - startVal)) + startVal;
+
+ }
+ } else { // if animation is finished or length not matching, land on right value
+ ret = end;
+ }
+ return ret;
+ }
+};
+
+/**
+ * Set the time methods globally based on the useUTC option. Time method can be either
+ * local time or UTC (default).
+ */
+function setTimeMethods() {
+ var useUTC = defaultOptions.global.useUTC;
+
+ makeTime = useUTC ? Date.UTC : function(year, month, date, hours, minutes, seconds) {
+ return new Date(
+ year,
+ month,
+ pick(date, 1),
+ pick(hours, 0),
+ pick(minutes, 0),
+ pick(seconds, 0)
+ ).getTime();
+ };
+ getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes';
+ getHours = useUTC ? 'getUTCHours' : 'getHours';
+ getDay = useUTC ? 'getUTCDay' : 'getDay';
+ getDate = useUTC ? 'getUTCDate' : 'getDate';
+ getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
+ getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
+ setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
+ setHours = useUTC ? 'setUTCHours' : 'setHours';
+ setDate = useUTC ? 'setUTCDate' : 'setDate';
+ setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
+ setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';
+
+}
+
+/**
+ * Merge the default options with custom options and return the new options structure
+ * @param {Object} options The new custom options
+ */
+function setOptions(options) {
+ defaultOptions = merge(defaultOptions, options);
+
+ // apply UTC
+ setTimeMethods();
+
+ return defaultOptions;
+}
+
+/**
+ * Get the updated default options. Merely exposing defaultOptions for outside modules
+ * isn't enough because the setOptions method creates a new object.
+ */
+function getOptions() {
+ return defaultOptions;
+}
+
+/**
+ * Discard an element by moving it to the bin and delete
+ * @param {Object} The HTML node to discard
+ */
+function discardElement(element) {
+ // create a garbage bin element, not part of the DOM
+ if (!garbageBin) {
+ garbageBin = createElement(DIV);
+ }
+
+ // move the node and empty bin
+ if (element) {
+ garbageBin.appendChild(element);
+ }
+ garbageBin.innerHTML = '';
+}
+
+/* ****************************************************************************
+ * Handle the options *
+ *****************************************************************************/
+var
+
+defaultLabelOptions = {
+ enabled: true,
+ // rotation: 0,
+ align: 'center',
+ x: 0,
+ y: 15,
+ /*formatter: function() {
+ return this.value;
+ },*/
+ style: {
+ color: '#666',
+ fontSize: '11px',
+ lineHeight: '14px'
+ }
+};
+
+defaultOptions = {
+ colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
+ '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
+ symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
+ lang: {
+ loading: 'Loading...',
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
+ 'August', 'September', 'October', 'November', 'December'],
+ weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ decimalPoint: '.',
+ resetZoom: 'Reset zoom',
+ resetZoomTitle: 'Reset zoom level 1:1',
+ thousandsSep: ','
+ },
+ global: {
+ useUTC: true
+ },
+ chart: {
+ //animation: true,
+ //alignTicks: false,
+ //reflow: true,
+ //className: null,
+ //events: { load, selection },
+ //margin: [null],
+ //marginTop: null,
+ //marginRight: null,
+ //marginBottom: null,
+ //marginLeft: null,
+ borderColor: '#4572A7',
+ //borderWidth: 0,
+ borderRadius: 5,
+ defaultSeriesType: 'line',
+ ignoreHiddenSeries: true,
+ //inverted: false,
+ //shadow: false,
+ spacingTop: 10,
+ spacingRight: 10,
+ spacingBottom: 15,
+ spacingLeft: 10,
+ style: {
+ fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
+ fontSize: '12px'
+ },
+ backgroundColor: '#FFFFFF',
+ //plotBackgroundColor: null,
+ plotBorderColor: '#C0C0C0'
+ //plotBorderWidth: 0,
+ //plotShadow: false,
+ //zoomType: ''
+ },
+ title: {
+ text: 'Chart title',
+ align: 'center',
+ // floating: false,
+ // margin: 15,
+ // x: 0,
+ // verticalAlign: 'top',
+ y: 15, // docs
+ style: {
+ color: '#3E576F',
+ fontSize: '16px'
+ }
+
+ },
+ subtitle: {
+ text: '',
+ align: 'center',
+ // floating: false
+ // x: 0,
+ // verticalAlign: 'top',
+ y: 30, // docs
+ style: {
+ color: '#6D869F'
+ }
+ },
+
+ plotOptions: {
+ line: { // base series options
+ allowPointSelect: false,
+ showCheckbox: false,
+ animation: {
+ duration: 1000
+ },
+ //cursor: 'default',
+ //dashStyle: null,
+ //enableMouseTracking: true,
+ events: {},
+ lineWidth: 2,
+ shadow: true,
+ // stacking: null,
+ marker: {
+ enabled: true,
+ //symbol: null,
+ lineWidth: 0,
+ radius: 4,
+ lineColor: '#FFFFFF',
+ //fillColor: null,
+ states: { // states for a single point
+ hover: {
+ //radius: base + 2
+ },
+ select: {
+ fillColor: '#FFFFFF',
+ lineColor: '#000000',
+ lineWidth: 2
+ }
+ }
+ },
+ point: {
+ events: {}
+ },
+ dataLabels: merge(defaultLabelOptions, {
+ enabled: false,
+ y: -6,
+ formatter: function() {
+ return this.y;
+ }
+ }),
+
+ //pointStart: 0,
+ //pointInterval: 1,
+ showInLegend: true,
+ states: { // states for the entire series
+ hover: {
+ //enabled: false,
+ //lineWidth: base + 1,
+ marker: {
+ // lineWidth: base + 1,
+ // radius: base + 1
+ }
+ },
+ select: {
+ marker: {}
+ }
+ },
+ stickyTracking: true
+ //zIndex: null
+ }
+ },
+ labels: {
+ //items: [],
+ style: {
+ //font: defaultFont,
+ position: ABSOLUTE,
+ color: '#3E576F'
+ }
+ },
+ legend: {
+ enabled: true,
+ align: 'center',
+ //floating: false,
+ layout: 'horizontal',
+ labelFormatter: function() {
+ return this.name;
+ },
+ // lineHeight: 16, // docs: deprecated
+ borderWidth: 1,
+ borderColor: '#909090',
+ borderRadius: 5,
+ // margin: 10,
+ // reversed: false,
+ shadow: false,
+ // backgroundColor: null,
+ style: {
+ padding: '5px'
+ },
+ itemStyle: {
+ cursor: 'pointer',
+ color: '#3E576F'
+ },
+ itemHoverStyle: {
+ cursor: 'pointer',
+ color: '#000000'
+ },
+ itemHiddenStyle: {
+ color: '#C0C0C0'
+ },
+ itemCheckboxStyle: {
+ position: ABSOLUTE,
+ width: '13px', // for IE precision
+ height: '13px'
+ },
+ // itemWidth: undefined,
+ symbolWidth: 16,
+ symbolPadding: 5,
+ verticalAlign: 'bottom',
+ // width: undefined,
+ x: 0, // docs
+ y: 0 // docs
+ },
+
+ loading: {
+ hideDuration: 100,
+ labelStyle: {
+ fontWeight: 'bold',
+ position: RELATIVE,
+ top: '1em'
+ },
+ showDuration: 100,
+ style: {
+ position: ABSOLUTE,
+ backgroundColor: 'white',
+ opacity: 0.5,
+ textAlign: 'center'
+ }
+ },
+
+ tooltip: {
+ enabled: true,
+ //crosshairs: null,
+ backgroundColor: 'rgba(255, 255, 255, .85)',
+ borderWidth: 2,
+ borderRadius: 5,
+ //formatter: defaultFormatter,
+ shadow: true,
+ //shared: false,
+ snap: hasTouch ? 25 : 10,
+ style: {
+ color: '#333333',
+ fontSize: '12px',
+ padding: '5px',
+ whiteSpace: 'nowrap'
+ }
+ },
+
+ toolbar: {
+ itemStyle: {
+ color: '#4572A7',
+ cursor: 'pointer'
+ }
+ },
+
+ credits: {
+ enabled: true,
+ text: 'Highcharts.com',
+ href: 'http://www.highcharts.com',
+ position: {
+ align: 'right',
+ x: -10,
+ verticalAlign: 'bottom',
+ y: -5
+ },
+ style: {
+ cursor: 'pointer',
+ color: '#909090',
+ fontSize: '10px'
+ }
+ }
+};
+
+// Axis defaults
+var defaultXAxisOptions = {
+ // allowDecimals: null,
+ // alternateGridColor: null,
+ // categories: [],
+ dateTimeLabelFormats: {
+ second: '%H:%M:%S',
+ minute: '%H:%M',
+ hour: '%H:%M',
+ day: '%e. %b',
+ week: '%e. %b',
+ month: '%b \'%y',
+ year: '%Y'
+ },
+ endOnTick: false,
+ gridLineColor: '#C0C0C0',
+ // gridLineDashStyle: 'solid', // docs
+ // gridLineWidth: 0,
+ // reversed: false,
+
+ labels: defaultLabelOptions,
+ // { step: null },
+ lineColor: '#C0D0E0',
+ lineWidth: 1,
+ //linkedTo: null,
+ max: null,
+ min: null,
+ minPadding: 0.01,
+ maxPadding: 0.01,
+ //maxZoom: null,
+ minorGridLineColor: '#E0E0E0',
+ // minorGridLineDashStyle: null,
+ minorGridLineWidth: 1,
+ minorTickColor: '#A0A0A0',
+ //minorTickInterval: null,
+ minorTickLength: 2,
+ minorTickPosition: 'outside', // inside or outside
+ //minorTickWidth: 0,
+ //opposite: false,
+ //offset: 0,
+ //plotBands: [{
+ // events: {},
+ // zIndex: 1,
+ // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
+ //}],
+ //plotLines: [{
+ // events: {}
+ // dashStyle: {}
+ // zIndex:
+ // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
+ //}],
+ //reversed: false,
+ // showFirstLabel: true,
+ // showLastLabel: false,
+ startOfWeek: 1,
+ startOnTick: false,
+ tickColor: '#C0D0E0',
+ //tickInterval: null,
+ tickLength: 5,
+ tickmarkPlacement: 'between', // on or between
+ tickPixelInterval: 100,
+ tickPosition: 'outside',
+ tickWidth: 1,
+ title: {
+ //text: null,
+ align: 'middle', // low, middle or high
+ //margin: 0 for horizontal, 10 for vertical axes,
+ //rotation: 0,
+ //side: 'outside',
+ style: {
+ color: '#6D869F',
+ //font: defaultFont.replace('normal', 'bold')
+ fontWeight: 'bold'
+ }
+ //x: 0,
+ //y: 0
+ },
+ type: 'linear' // linear or datetime
+},
+
+defaultYAxisOptions = merge(defaultXAxisOptions, {
+ endOnTick: true,
+ gridLineWidth: 1,
+ tickPixelInterval: 72,
+ showLastLabel: true,
+ labels: {
+ align: 'right',
+ x: -8,
+ y: 3
+ },
+ lineWidth: 0,
+ maxPadding: 0.05,
+ minPadding: 0.05,
+ startOnTick: true,
+ tickWidth: 0,
+ title: {
+ rotation: 270,
+ text: 'Y-values'
+ }
+}),
+
+defaultLeftAxisOptions = {
+ labels: {
+ align: 'right',
+ x: -8,
+ y: null // docs
+ },
+ title: {
+ rotation: 270
+ }
+},
+defaultRightAxisOptions = {
+ labels: {
+ align: 'left',
+ x: 8,
+ y: null // docs
+ },
+ title: {
+ rotation: 90
+ }
+},
+defaultBottomAxisOptions = { // horizontal axis
+ labels: {
+ align: 'center',
+ x: 0,
+ y: 14
+ // staggerLines: null
+ },
+ title: {
+ rotation: 0
+ }
+},
+defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
+ labels: {
+ y: -5
+ // staggerLines: null
+ }
+});
+
+
+
+
+// Series defaults
+var defaultPlotOptions = defaultOptions.plotOptions,
+ defaultSeriesOptions = defaultPlotOptions.line;
+//defaultPlotOptions.line = merge(defaultSeriesOptions);
+defaultPlotOptions.spline = merge(defaultSeriesOptions);
+defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
+ lineWidth: 0,
+ states: {
+ hover: {
+ lineWidth: 0
+ }
+ }
+});
+defaultPlotOptions.area = merge(defaultSeriesOptions, {
+ // threshold: 0,
+ // lineColor: null, // overrides color, but lets fillColor be unaltered
+ // fillOpacity: 0.75,
+ // fillColor: null
+
+});
+defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
+defaultPlotOptions.column = merge(defaultSeriesOptions, {
+ borderColor: '#FFFFFF',
+ borderWidth: 1,
+ borderRadius: 0,
+ //colorByPoint: undefined,
+ groupPadding: 0.2,
+ marker: null, // point options are specified in the base options
+ pointPadding: 0.1,
+ //pointWidth: null,
+ minPointLength: 0,
+ states: {
+ hover: {
+ brightness: 0.1,
+ shadow: false
+ },
+ select: {
+ color: '#C0C0C0',
+ borderColor: '#000000',
+ shadow: false
+ }
+ }
+});
+defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
+ dataLabels: {
+ align: 'left',
+ x: 5,
+ y: 0
+ }
+});
+defaultPlotOptions.pie = merge(defaultSeriesOptions, {
+ //dragType: '', // n/a
+ borderColor: '#FFFFFF',
+ borderWidth: 1,
+ center: ['50%', '50%'],
+ colorByPoint: true, // always true for pies
+ dataLabels: {
+ // align: null,
+ // connectorWidth: 1,
+ // connectorColor: '#606060',
+ // connectorPadding: 5,
+ distance: 30,
+ enabled: true,
+ formatter: function() {
+ return this.point.name;
+ },
+ y: 5
+ },
+ //innerSize: 0,
+ legendType: 'point',
+ marker: null, // point options are specified in the base options
+ size: '75%',
+ showInLegend: false,
+ slicedOffset: 10,
+ states: {
+ hover: {
+ brightness: 0.1,
+ shadow: false
+ }
+ }
+
+});
+
+// set the default time methods
+setTimeMethods();
+
+
+/**
+ * Extend a prototyped class by new members
+ * @param {Object} parent
+ * @param {Object} members
+ */
+function extendClass(parent, members) {
+ var object = function(){};
+ object.prototype = new parent();
+ extend(object.prototype, members);
+ return object;
+}
+
+
+/**
+ * Handle color operations. The object methods are chainable.
+ * @param {String} input The input color in either rbga or hex format
+ */
+var Color = function(input) {
+ // declare variables
+ var rgba = [], result;
+
+ /**
+ * Parse the input color to rgba array
+ * @param {String} input
+ */
+ function init(input) {
+
+ // rgba
+ if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input))) {
+ rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
+ }
+
+ // hex
+ else if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input))) {
+ rgba = [pInt(result[1],16), pInt(result[2],16), pInt(result[3],16), 1];
+ }
+
+ }
+ /**
+ * Return the color a specified format
+ * @param {String} format
+ */
+ function get(format) {
+ var ret;
+
+ // it's NaN if gradient colors on a column chart
+ if (rgba && !isNaN(rgba[0])) {
+ if (format == 'rgb') {
+ ret = 'rgb('+ rgba[0] +','+ rgba[1] +','+ rgba[2] +')';
+ } else if (format == 'a') {
+ ret = rgba[3];
+ } else {
+ ret = 'rgba('+ rgba.join(',') +')';
+ }
+ } else {
+ ret = input;
+ }
+ return ret;
+ }
+
+ /**
+ * Brighten the color
+ * @param {Number} alpha
+ */
+ function brighten(alpha) {
+ if (isNumber(alpha) && alpha !== 0) {
+ var i;
+ for (i = 0; i < 3; i++) {
+ rgba[i] += pInt(alpha * 255);
+
+ if (rgba[i] < 0) {
+ rgba[i] = 0;
+ }
+ if (rgba[i] > 255) {
+ rgba[i] = 255;
+ }
+ }
+ }
+ return this;
+ }
+ /**
+ * Set the color's opacity to a given alpha value
+ * @param {Number} alpha
+ */
+ function setOpacity(alpha) {
+ rgba[3] = alpha;
+ return this;
+ }
+
+ // initialize: parse the input
+ init(input);
+
+ // public methods
+ return {
+ get: get,
+ brighten: brighten,
+ setOpacity: setOpacity
+ };
+};
+
+
+
+/**
+ * Format a number and return a string based on input settings
+ * @param {Number} number The input number to format
+ * @param {Number} decimals The amount of decimals
+ * @param {String} decPoint The decimal point, defaults to the one given in the lang options
+ * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
+ */
+function numberFormat (number, decimals, decPoint, thousandsSep) {
+ var lang = defaultOptions.lang,
+ // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_ph…
+ n = number, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
+ d = decPoint === undefined ? lang.decimalPoint : decPoint,
+ t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
+ i = pInt(n = mathAbs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
+
+ return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
+ (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
+}
+
+/**
+ * Based on http://www.php.net/manual/en/function.strftime.php
+ * @param {String} format
+ * @param {Number} timestamp
+ * @param {Boolean} capitalize
+ */
+dateFormat = function (format, timestamp, capitalize) {
+ function pad (number) {
+ return number.toString().replace(/^([0-9])$/, '0$1');
+ }
+
+ if (!defined(timestamp) || isNaN(timestamp)) {
+ return 'Invalid date';
+ }
+ format = pick(format, '%Y-%m-%d %H:%M:%S');
+
+ var date = new Date(timestamp * timeFactor),
+
+ // get the basic time values
+ hours = date[getHours](),
+ day = date[getDay](),
+ dayOfMonth = date[getDate](),
+ month = date[getMonth](),
+ fullYear = date[getFullYear](),
+ lang = defaultOptions.lang,
+ langWeekdays = lang.weekdays,
+ langMonths = lang.months,
+
+ // list all format keys
+ replacements = {
+
+ // Day
+ 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
+ 'A': langWeekdays[day], // Long weekday, like 'Monday'
+ 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
+ 'e': dayOfMonth, // Day of the month, 1 through 31
+
+ // Week (none implemented)
+
+ // Month
+ 'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
+ 'B': langMonths[month], // Long month, like 'January'
+ 'm': pad(month + 1), // Two digit month number, 01 through 12
+
+ // Year
+ 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
+ 'Y': fullYear, // Four digits year, like 2009
+
+ // Time
+ 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
+ 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
+ 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
+ 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
+ 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
+ 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
+ 'S': pad(date.getSeconds()) // Two digits seconds, 00 through 59
+
+ };
+
+
+ // do the replaces
+ for (var key in replacements) {
+ format = format.replace('%'+ key, replacements[key]);
+ }
+
+ // Optionally capitalize the string and return
+ return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
+};
+
+
+
+/**
+ * Loop up the node tree and add offsetWidth and offsetHeight to get the
+ * total page offset for a given element. Used by Opera and iOS on hover and
+ * all browsers on point click.
+ *
+ * @param {Object} el
+ *
+ */
+function getPosition (el) {
+ var p = { left: el.offsetLeft, top: el.offsetTop };
+ while ((el = el.offsetParent)) {
+ p.left += el.offsetLeft;
+ p.top += el.offsetTop;
+ if (el != doc.body && el != doc.documentElement) {
+ p.left -= el.scrollLeft;
+ p.top -= el.scrollTop;
+ }
+ }
+ return p;
+}
+
+
+/**
+ * A wrapper object for SVG elements
+ */
+function SVGElement () {}
+
+SVGElement.prototype = {
+ /**
+ * Initialize the SVG renderer
+ * @param {Object} renderer
+ * @param {String} nodeName
+ */
+ init: function(renderer, nodeName) {
+ this.element = doc.createElementNS(SVG_NS, nodeName);
+ this.renderer = renderer;
+ },
+ /**
+ * Animate a given attribute
+ * @param {Object} params
+ * @param {Number} options The same options as in jQuery animation
+ * @param {Function} complete Function to perform at the end of animation
+ */
+ animate: function(params, options, complete) {
+ var animOptions = pick(options, globalAnimation, true);
+ if (animOptions) {
+ animOptions = merge(animOptions);
+ if (complete) { // allows using a callback with the global animation without overwriting it
+ animOptions.complete = complete;
+ }
+ animate(this, params, animOptions);
+ } else {
+ this.attr(params);
+ if (complete) {
+ complete();
+ }
+ }
+ },
+ /**
+ * Set or get a given attribute
+ * @param {Object|String} hash
+ * @param {Mixed|Undefined} val
+ */
+ attr: function(hash, val) {
+ var key,
+ value,
+ i,
+ child,
+ element = this.element,
+ nodeName = element.nodeName,
+ renderer = this.renderer,
+ skipAttr,
+ shadows = this.shadows,
+ hasSetSymbolSize,
+ ret = this;
+
+ // single key-value pair
+ if (isString(hash) && defined(val)) {
+ key = hash;
+ hash = {};
+ hash[key] = val;
+ }
+
+ // used as a getter: first argument is a string, second is undefined
+ if (isString(hash)) {
+ key = hash;
+ if (nodeName == 'circle') {
+ key = { x: 'cx', y: 'cy' }[key] || key;
+ } else if (key == 'strokeWidth') {
+ key = 'stroke-width';
+ }
+ ret = attr(element, key) || this[key] || 0;
+
+ if (key != 'd' && key != 'visibility') { // 'd' is string in animation step
+ ret = parseFloat(ret);
+ }
+
+ // setter
+ } else {
+
+ for (key in hash) {
+ skipAttr = false; // reset
+ value = hash[key];
+
+ // paths
+ if (key == 'd') {
+ if (value && value.join) { // join path
+ value = value.join(' ');
+ }
+ if (/(NaN| {2}|^$)/.test(value)) {
+ value = 'M 0 0';
+ }
+ this.d = value; // shortcut for animations
+
+ // update child tspans x values
+ } else if (key == 'x' && nodeName == 'text') {
+ for (i = 0; i < element.childNodes.length; i++ ) {
+ child = element.childNodes[i];
+ // if the x values are equal, the tspan represents a linebreak
+ if (attr(child, 'x') == attr(element, 'x')) {
+ //child.setAttribute('x', value);
+ attr(child, 'x', value);
+ }
+ }
+
+ if (this.rotation) {
+ attr(element, 'transform', 'rotate('+ this.rotation +' '+ value +' '+
+ pInt(hash.y || attr(element, 'y')) +')');
+ }
+
+ // apply gradients
+ } else if (key == 'fill') {
+ value = renderer.color(value, element, key);
+
+ // circle x and y
+ } else if (nodeName == 'circle' && (key == 'x' || key == 'y')) {
+ key = { x: 'cx', y: 'cy' }[key] || key;
+
+ // translation and text rotation
+ } else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'verticalAlign') {
+ this[key] = value;
+ this.updateTransform();
+ skipAttr = true;
+
+ // apply opacity as subnode (required by legacy WebKit and Batik)
+ } else if (key == 'stroke') {
+ value = renderer.color(value, element, key);
+
+ // emulate VML's dashstyle implementation
+ } else if (key == 'dashstyle') {
+ key = 'stroke-dasharray';
+ if (value) {
+ value = value.toLowerCase()
+ .replace('shortdashdotdot', '3,1,1,1,1,1,')
+ .replace('shortdashdot', '3,1,1,1')
+ .replace('shortdot', '1,1,')
+ .replace('shortdash', '3,1,')
+ .replace('longdash', '8,3,')
+ .replace(/dot/g, '1,3,')
+ .replace('dash', '4,3,')
+ .replace(/,$/, '')
+ .split(','); // ending comma
+
+ i = value.length;
+ while (i--) {
+ value[i] = pInt(value[i]) * hash['stroke-width'];
+ }
+ value = value.join(',');
+ }
+
+ // special
+ } else if (key == 'isTracker') {
+ this[key] = value;
+
+ // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
+ // is unable to cast them. Test again with final IE9.
+ } else if (key == 'width') {
+ value = pInt(value);
+
+ // Text alignment
+ } else if (key == 'align') {
+ key = 'text-anchor';
+ value = { left: 'start', center: 'middle', right: 'end' }[value];
+ }
+
+
+
+ // jQuery animate changes case
+ if (key == 'strokeWidth') {
+ key = 'stroke-width';
+ }
+
+ // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
+ if (isWebKit && key == 'stroke-width' && value === 0) {
+ value = 0.000001;
+ }
+
+ // symbols
+ if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {
+
+
+ if (!hasSetSymbolSize) {
+ this.symbolAttr(hash);
+ hasSetSymbolSize = true;
+ }
+ skipAttr = true;
+ }
+
+ // let the shadow follow the main element
+ if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
+ i = shadows.length;
+ while (i--) {
+ attr(shadows[i], key, value);
+ }
+ }
+
+ /* trows errors in Chrome
+ if ((key == 'width' || key == 'height') && nodeName == 'rect' && value < 0) {
+ console.log(element);
+ }
+ */
+
+
+
+ if (key == 'text') {
+ // only one node allowed
+ this.textStr = value;
+ if (this.added) {
+ renderer.buildText(this);
+ }
+ } else if (!skipAttr) {
+ //element.setAttribute(key, value);
+ attr(element, key, value);
+ }
+
+ }
+
+ }
+ return ret;
+ },
+
+ /**
+ * If one of the symbol size affecting parameters are changed,
+ * check all the others only once for each call to an element's
+ * .attr() method
+ * @param {Object} hash
+ */
+ symbolAttr: function(hash) {
+ var wrapper = this;
+
+ each (['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function(key) {
+ wrapper[key] = pick(hash[key], wrapper[key]);
+ });
+
+ wrapper.attr({
+ d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.r, {
+ start: wrapper.start,
+ end: wrapper.end,
+ width: wrapper.width,
+ height: wrapper.height,
+ innerR: wrapper.innerR
+ })
+ });
+ },
+
+ /**
+ * Apply a clipping path to this object
+ * @param {String} id
+ */
+ clip: function(clipRect) {
+ return this.attr('clip-path', 'url('+ this.renderer.url +'#'+ clipRect.id +')');
+ },
+
+ /**
+ * Calculate the coordinates needed for drawing a rectangle crisply and return the
+ * calculated attributes
+ * @param {Number} strokeWidth
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} width
+ * @param {Number} height
+ */
+ crisp: function(strokeWidth, x, y, width, height) {
+
+ var wrapper = this,
+ key,
+ attr = {},
+ values = {},
+ normalizer;
+
+ strokeWidth = strokeWidth || wrapper.strokeWidth || 0;
+ normalizer = strokeWidth % 2 / 2;
+
+ // normalize for crisp edges
+ values.x = mathFloor(x || wrapper.x || 0) + normalizer;
+ values.y = mathFloor(y || wrapper.y || 0) + normalizer;
+ values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
+ values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
+ values.strokeWidth = strokeWidth;
+
+ for (key in values) {
+ if (wrapper[key] != values[key]) { // only set attribute if changed
+ wrapper[key] = attr[key] = values[key];
+ }
+ }
+
+ return attr;
+ },
+
+ /**
+ * Set styles for the element
+ * @param {Object} styles
+ */
+ css: function(styles) {
+ var elemWrapper = this,
+ elem = elemWrapper.element,
+ textWidth = styles && styles.width && elem.nodeName == 'text';
+
+ // convert legacy
+ if (styles && styles.color) {
+ styles.fill = styles.color;
+ }
+
+ // save the styles in an object
+ styles = extend(
+ elemWrapper.styles,
+ styles
+ );
+
+
+ // store object
+ elemWrapper.styles = styles;
+
+ // serialize and set style attribute
+ if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
+ if (textWidth) {
+ delete styles.width;
+ }
+ css(elemWrapper.element, styles);
+ } else {
+ elemWrapper.attr({
+ style: serializeCSS(styles)
+ });
+ }
+
+
+ // re-build text
+ if (textWidth && elemWrapper.added) {
+ elemWrapper.renderer.buildText(elemWrapper);
+ }
+
+ return elemWrapper;
+ },
+
+ /**
+ * Add an event listener
+ * @param {String} eventType
+ * @param {Function} handler
+ */
+ on: function(eventType, handler) {
+ var fn = handler;
+ // touch
+ if (hasTouch && eventType == 'click') {
+ eventType = 'touchstart';
+ fn = function(e) {
+ e.preventDefault();
+ handler();
+ }
+ }
+ // simplest possible event model for internal use
+ this.element['on'+ eventType] = fn;
+ return this;
+ },
+
+
+ /**
+ * Move an object and its children by x and y values
+ * @param {Number} x
+ * @param {Number} y
+ */
+ translate: function(x, y) {
+ return this.attr({
+ translateX: x,
+ translateY: y
+ });
+ },
+
+ /**
+ * Invert a group, rotate and flip
+ */
+ invert: function() {
+ var wrapper = this;
+ wrapper.inverted = true;
+ wrapper.updateTransform();
+ return wrapper;
+ },
+
+ /**
+ * Private method to update the transform attribute based on internal
+ * properties
+ */
+ updateTransform: function() {
+ var wrapper = this,
+ translateX = wrapper.translateX || 0,
+ translateY = wrapper.translateY || 0,
+ inverted = wrapper.inverted,
+ rotation = wrapper.rotation,
+ transform = [];
+
+ // flipping affects translate as adjustment for flipping around the group's axis
+ if (inverted) {
+ translateX += wrapper.attr('width');
+ translateY += wrapper.attr('height');
+ }
+
+ // apply translate
+ if (translateX || translateY) {
+ transform.push('translate('+ translateX +','+ translateY +')');
+ }
+
+ // apply rotation
+ if (inverted) {
+ transform.push('rotate(90) scale(-1,1)');
+ } else if (rotation) { // text rotation
+ transform.push('rotate('+ rotation +' '+ wrapper.x +' '+ wrapper.y +')');
+ }
+
+ if (transform.length) {
+ attr(wrapper.element, 'transform', transform.join(' '));
+ }
+ },
+ /**
+ * Bring the element to the front
+ */
+ toFront: function() {
+ var element = this.element;
+ element.parentNode.appendChild(element);
+ return this;
+ },
+
+
+ /**
+ * Break down alignment options like align, verticalAlign, x and y
+ * to x and y relative to the chart.
+ *
+ * @param {Object} alignOptions
+ * @param {Boolean} alignByTranslate
+ * @param {Object} box The box to align to, needs a width and height
+ *
+ */
+ align: function(alignOptions, alignByTranslate, box) {
+
+ if (!alignOptions) { // called on resize
+ alignOptions = this.alignOptions;
+ alignByTranslate = this.alignByTranslate;
+ } else { // first call on instanciate
+ this.alignOptions = alignOptions;
+ this.alignByTranslate = alignByTranslate;
+ if (!box) { // boxes other than renderer handle this internally
+ this.renderer.alignedObjects.push(this);
+ }
+ }
+
+ box = pick(box, this.renderer);
+
+ var align = alignOptions.align,
+ vAlign = alignOptions.verticalAlign,
+ x = (box.x || 0) + (alignOptions.x || 0), // default: left align
+ y = (box.y || 0) + (alignOptions.y || 0), // default: top align
+ attribs = {};
+
+
+ // align
+ if (/^(right|center)$/.test(align)) {
+ x += (box.width - (alignOptions.width || 0) ) /
+ { right: 1, center: 2 }[align];
+ }
+ attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
+
+
+ // vertical align
+ if (/^(bottom|middle)$/.test(vAlign)) {
+ y += (box.height - (alignOptions.height || 0)) /
+ ({ bottom: 1, middle: 2 }[vAlign] || 1);
+
+ }
+ attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
+
+ // animate only if already placed
+ this[this.placed ? 'animate' : 'attr'](attribs);
+ this.placed = true;
+
+ return this;
+ },
+
+ /**
+ * Get the bounding box (width, height, x and y) for the element
+ */
+ getBBox: function() {
+ var bBox,
+ width,
+ height,
+ rotation = this.rotation,
+ rad = rotation * deg2rad;
+
+ try { // fails in Firefox if the container has display: none
+ // use extend because IE9 is not allowed to change width and height in case
+ // of rotation (below)
+ bBox = extend({}, this.element.getBBox());
+ } catch(e) {
+ bBox = { width: 0, height: 0 };
+ }
+ width = bBox.width;
+ height = bBox.height;
+
+ // adjust for rotated text
+ if (rotation) {
+ bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
+ bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
+ }
+
+ return bBox;
+ },
+
+ /* *
+ * Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
+ * @param {Object} bBox
+ * @param {number} rotation
+ * /
+ rotateBBox: function(bBox, rotation) {
+ var rad = rotation * math.PI * 2 / 360, // radians
+ width = bBox.width,
+ height = bBox.height;
+
+
+ },*/
+
+ /**
+ * Show the element
+ */
+ show: function() {
+ return this.attr({ visibility: VISIBLE });
+ },
+
+ /**
+ * Hide the element
+ */
+ hide: function() {
+ return this.attr({ visibility: HIDDEN });
+ },
+
+ /**
+ * Add the element
+ * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
+ * to append the element to the renderer.box.
+ */
+ add: function(parent) {
+
+ var renderer = this.renderer,
+ parentWrapper = parent || renderer,
+ parentNode = parentWrapper.element || renderer.box,
+ childNodes = parentNode.childNodes,
+ element = this.element,
+ zIndex = attr(element, 'zIndex'),
+ otherElement,
+ otherZIndex,
+ i;
+
+ // mark as inverted
+ this.parentInverted = parent && parent.inverted;
+
+ // build formatted text
+ if (this.textStr !== undefined) {
+ renderer.buildText(this);
+ }
+
+ // mark the container as having z indexed children
+ if (zIndex) {
+ parentWrapper.handleZ = true;
+ zIndex = pInt(zIndex);
+ }
+
+ // insert according to this and other elements' zIndex
+ if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
+ for (i = 0; i < childNodes.length; i++) {
+ otherElement = childNodes[i];
+ otherZIndex = attr(otherElement, 'zIndex');
+ if (otherElement != element && (
+ // insert before the first element with a higher zIndex
+ pInt(otherZIndex) > zIndex ||
+ // if no zIndex given, insert before the first element with a zIndex
+ (!defined(zIndex) && defined(otherZIndex))
+
+ )) {
+ parentNode.insertBefore(element, otherElement);
+ return this;
+ }
+ }
+ }
+
+ // default: append at the end
+ parentNode.appendChild(element);
+
+ this.added = true;
+
+ return this;
+ },
+
+ /**
+ * Destroy the element and element wrapper
+ */
+ destroy: function() {
+ var wrapper = this,
+ element = wrapper.element || {},
+ shadows = wrapper.shadows,
+ parentNode = element.parentNode,
+ key;
+
+ // remove events
+ element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
+ stop(wrapper); // stop running animations
+
+ // remove element
+ if (parentNode) {
+ parentNode.removeChild(element);
+ }
+
+ // destroy shadows
+ if (shadows) {
+ each(shadows, function(shadow) {
+ parentNode = shadow.parentNode;
+ if (parentNode) { // the entire chart HTML can be overwritten
+ parentNode.removeChild(shadow);
+ }
+ });
+ }
+
+ // remove from alignObjects
+ erase(wrapper.renderer.alignedObjects, wrapper);
+
+ for (key in wrapper) {
+ delete wrapper[key];
+ }
+
+ return null;
+ },
+
+ /**
+ * Empty a group element
+ */
+ empty: function() {
+ var element = this.element,
+ childNodes = element.childNodes,
+ i = childNodes.length;
+
+ while (i--) {
+ element.removeChild(childNodes[i]);
+ }
+ },
+
+ /**
+ * Add a shadow to the element. Must be done after the element is added to the DOM
+ * @param {Boolean} apply
+ */
+ shadow: function(apply) {
+ var shadows = [],
+ i,
+ shadow,
+ element = this.element,
+
+ // compensate for inverted plot area
+ transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
+
+
+ if (apply) {
+ for (i = 1; i <= 3; i++) {
+ shadow = element.cloneNode(0);
+ attr(shadow, {
+ 'isShadow': 'true',
+ 'stroke': 'rgb(0, 0, 0)',
+ 'stroke-opacity': 0.05 * i,
+ 'stroke-width': 7 - 2 * i,
+ 'transform': 'translate'+ transform,
+ 'fill': NONE
+ });
+
+
+ element.parentNode.insertBefore(shadow, element);
+
+ shadows.push(shadow);
+ }
+
+ this.shadows = shadows;
+ }
+ return this;
+
+ }
+};
+
+
+
+/**
+ * The default SVG renderer
+ */
+var SVGRenderer = function() {
+ this.init.apply(this, arguments);
+};
+SVGRenderer.prototype = {
+ /**
+ * Initialize the SVGRenderer
+ * @param {Object} container
+ * @param {Number} width
+ * @param {Number} height
+ * @param {Boolean} forExport
+ */
+ init: function(container, width, height, forExport) {
+ var renderer = this,
+ loc = location,
+ boxWrapper;
+
+ renderer.Element = SVGElement;
+ boxWrapper = renderer.createElement('svg')
+ .attr({
+ xmlns: SVG_NS,
+ version: '1.1'
+ });
+ container.appendChild(boxWrapper.element);
+
+ // object properties
+ renderer.box = boxWrapper.element;
+ renderer.boxWrapper = boxWrapper;
+ renderer.alignedObjects = [];
+ renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
+ renderer.defs = this.createElement('defs').add();
+ renderer.forExport = forExport;
+
+ renderer.setSize(width, height, false);
+
+ },
+
+
+ /**
+ * Create a wrapper for an SVG element
+ * @param {Object} nodeName
+ */
+ createElement: function(nodeName) {
+ var wrapper = new this.Element();
+ wrapper.init(this, nodeName);
+ return wrapper;
+ },
+
+
+ /**
+ * Parse a simple HTML string into SVG tspans
+ *
+ * @param {Object} textNode The parent text SVG node
+ */
+ buildText: function(wrapper) {
+ var textNode = wrapper.element,
+ lines = pick(wrapper.textStr, '').toString()
+ .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
+ .replace(/<(i|em)>/g, '<span style="font-style:italic">')
+ .replace(/<a/g, '<span')
+ .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
+ .split(/<br[^>]?>/g),
+ childNodes = textNode.childNodes,
+ styleRegex = /style="([^"]+)"/,
+ hrefRegex = /href="([^"]+)"/,
+ parentX = attr(textNode, 'x'),
+ textStyles = wrapper.styles,
+ reverse = isFirefox && textStyles && textStyles.HcDirection == 'rtl' && !this.forExport, // issue #38
+ arr,
+ width = textStyles && pInt(textStyles.width),
+ textLineHeight = textStyles && textStyles.lineHeight,
+ lastLine,
+ i = childNodes.length;
+
+ // remove old text
+ while (i--) {
+ textNode.removeChild(childNodes[i]);
+ }
+
+ if (width && !wrapper.added) {
+ this.box.appendChild(textNode); // attach it to the DOM to read offset width
+ }
+
+ each(lines, function(line, lineNo) {
+ var spans, spanNo = 0, lineHeight;
+
+ line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
+ spans = line.split('|||');
+
+ each(spans, function (span) {
+ if (span !== '' || spans.length == 1) {
+ var attributes = {},
+ tspan = doc.createElementNS(SVG_NS, 'tspan');
+ if (styleRegex.test(span)) {
+ attr(
+ tspan,
+ 'style',
+ span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
+ );
+ }
+ if (hrefRegex.test(span)) {
+ attr(tspan, 'onclick', 'location.href=\"'+ span.match(hrefRegex)[1] +'\"');
+ css(tspan, { cursor: 'pointer' });
+ }
+
+ span = span.replace(/<(.|\n)*?>/g, '') || ' ';
+
+ // issue #38 workaround.
+ if (reverse) {
+ arr = [];
+ i = span.length;
+ while (i--) {
+ arr.push(span.charAt(i))
+ }
+ span = arr.join('');
+ }
+
+ // add the text node
+ tspan.appendChild(doc.createTextNode(span));
+
+ if (!spanNo) { // first span in a line, align it to the left
+ attributes.x = parentX;
+ } else {
+ // Firefox ignores spaces at the front or end of the tspan
+ attributes.dx = 3; // space
+ }
+
+ // first span on subsequent line, add the line height
+ if (!spanNo) {
+ if (lineNo) {
+ // Webkit and opera sometimes return 'normal' as the line height. In that
+ // case, webkit uses offsetHeight, while Opera falls back to 18
+ lineHeight = pInt(window.getComputedStyle(lastLine, null).getPropertyValue('line-height'));
+ if (isNaN(lineHeight)) {
+ lineHeight = textLineHeight || lastLine.offsetHeight || 18;
+ }
+ attr(tspan, 'dy', lineHeight);
+ }
+ lastLine = tspan; // record for use in next line
+ }
+
+ // add attributes
+ attr(tspan, attributes);
+
+ // append it
+ textNode.appendChild(tspan);
+
+ spanNo++;
+
+ // check width and apply soft breaks
+ if (width) {
+ var words = span.replace(/-/g, '- ').split(' '),
+ tooLong,
+ actualWidth,
+ rest = [];
+
+ while (words.length || rest.length) {
+ actualWidth = textNode.getBBox().width;
+ tooLong = actualWidth > width;
+ if (!tooLong || words.length == 1) { // new line needed
+ words = rest;
+ rest = [];
+ if (words.length) {
+ tspan = doc.createElementNS(SVG_NS, 'tspan');
+ attr(tspan, {
+ x: parentX,
+ dy: textLineHeight || 16
+ });
+ textNode.appendChild(tspan);
+
+ if (actualWidth > width) { // a single word is pressing it out
+ width = actualWidth;
+ }
+ }
+ } else { // append to existing line tspan
+ tspan.removeChild(tspan.firstChild);
+ rest.unshift(words.pop());
+ }
+
+ tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
+ }
+
+ }
+ }
+ });
+ });
+
+
+ },
+
+ /**
+ * Make a straight line crisper by not spilling out to neighbour pixels
+ * @param {Array} points
+ * @param {Number} width
+ */
+ crispLine: function(points, width) {
+ // points format: [M, 0, 0, L, 100, 0]
+ // normalize to a crisp line
+ if (points[1] == points[4]) {
+ points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
+ }
+ if (points[2] == points[5]) {
+ points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
+ }
+ return points;
+ },
+
+
+ /**
+ * Draw a path
+ * @param {Array} path An SVG path in array form
+ */
+ path: function (path) {
+ return this.createElement('path').attr({
+ d: path,
+ fill: NONE
+ });
+ },
+
+ /**
+ * Draw and return an SVG circle
+ * @param {Number} x The x position
+ * @param {Number} y The y position
+ * @param {Number} r The radius
+ */
+ circle: function (x, y, r) {
+ var attr = isObject(x) ?
+ x :
+ {
+ x: x,
+ y: y,
+ r: r
+ };
+
+ return this.createElement('circle').attr(attr);
+ },
+
+ /**
+ * Draw and return an arc
+ * @param {Number} x X position
+ * @param {Number} y Y position
+ * @param {Number} r Radius
+ * @param {Number} innerR Inner radius like used in donut charts
+ * @param {Number} start Starting angle
+ * @param {Number} end Ending angle
+ */
+ arc: function (x, y, r, innerR, start, end) {
+ // arcs are defined as symbols for the ability to set
+ // attributes in attr and animate
+
+ if (isObject(x)) {
+ y = x.y;
+ r = x.r;
+ innerR = x.innerR;
+ start = x.start;
+ end = x.end;
+ x = x.x;
+ }
+
+ return this.symbol('arc', x || 0, y || 0, r || 0, {
+ innerR: innerR || 0,
+ start: start || 0,
+ end: end || 0
+ });
+ },
+
+ /**
+ * Draw and return a rectangle
+ * @param {Number} x Left position
+ * @param {Number} y Top position
+ * @param {Number} width
+ * @param {Number} height
+ * @param {Number} r Border corner radius
+ * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
+ */
+ rect: function (x, y, width, height, r, strokeWidth) {
+ if (isObject(x)) {
+ y = x.y;
+ width = x.width;
+ height = x.height;
+ r = x.r;
+ x = x.x;
+ }
+ var wrapper = this.createElement('rect').attr({
+ rx: r,
+ ry: r,
+ fill: NONE
+ });
+
+ return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
+ },
+
+ /**
+ * Resize the box and re-align all aligned elements
+ * @param {Object} width
+ * @param {Object} height
+ * @param {Boolean} animate
+ *
+ */
+ setSize: function(width, height, animate) {
+ var renderer = this,
+ alignedObjects = renderer.alignedObjects,
+ i = alignedObjects.length;
+
+ renderer.width = width;
+ renderer.height = height;
+
+ renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
+ width: width,
+ height: height
+ });
+
+ while (i--) {
+ alignedObjects[i].align();
+ }
+ },
+
+ /**
+ * Create a group
+ * @param {String} name The group will be given a class name of 'highcharts-{name}'.
+ * This can be used for styling and scripting.
+ */
+ g: function(name) {
+ return this.createElement('g').attr(
+ defined(name) && { 'class': PREFIX + name }
+ );
+ },
+
+ /**
+ * Display an image
+ * @param {String} src
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} width
+ * @param {Number} height
+ */
+ image: function(src, x, y, width, height) {
+ var attribs = {
+ preserveAspectRatio: NONE
+ },
+ elemWrapper;
+
+ // optional properties
+ if (arguments.length > 1) {
+ extend(attribs, {
+ x: x,
+ y: y,
+ width: width,
+ height: height
+ });
+ }
+
+ elemWrapper = this.createElement('image').attr(attribs);
+
+ // set the href in the xlink namespace
+ elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
+ 'href', src);
+
+ return elemWrapper;
+ },
+
+ /**
+ * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
+ *
+ * @param {Object} symbol
+ * @param {Object} x
+ * @param {Object} y
+ * @param {Object} radius
+ * @param {Object} options
+ */
+ symbol: function(symbol, x, y, radius, options) {
+
+ var obj,
+
+ // get the symbol definition function
+ symbolFn = this.symbols[symbol],
+
+ // check if there's a path defined for this symbol
+ path = symbolFn && symbolFn(
+ x,
+ y,
+ radius,
+ options
+ ),
+
+ imageRegex = /^url\((.*?)\)$/,
+ imageSrc;
+
+ if (path) {
+
+ obj = this.path(path);
+ // expando properties for use in animate and attr
+ extend(obj, {
+ symbolName: symbol,
+ x: x,
+ y: y,
+ r: radius
+ });
+ if (options) {
+ extend(obj, options);
+ }
+
+
+ // image symbols
+ } else if (imageRegex.test(symbol)) {
+
+ imageSrc = symbol.match(imageRegex)[1];
+
+ // create the image synchronously, add attribs async
+ obj = this.image(imageSrc)
+ .attr({
+ x: x,
+ y: y
+ });
+
+ // create a dummy JavaScript image to get the width and height
+ createElement('img', {
+ onload: function() {
+ var img = this,
+ size = symbolSizes[img.src] || [img.width, img.height];
+ obj.attr({
+ width: size[0],
+ height: size[1]
+ }).translate(
+ -mathRound(size[0] / 2),
+ -mathRound(size[1] / 2)
+ );
+ },
+ src: imageSrc
+ });
+
+ // default circles
+ } else {
+ obj = this.circle(x, y, radius);
+ }
+
+ return obj;
+ },
+
+ /**
+ * An extendable collection of functions for defining symbol paths.
+ */
+ symbols: {
+ 'square': function (x, y, radius) {
+ var len = 0.707 * radius;
+ return [
+ M, x-len, y-len,
+ L, x+len, y-len,
+ x+len, y+len,
+ x-len, y+len,
+ 'Z'
+ ];
+ },
+
+ 'triangle': function (x, y, radius) {
+ return [
+ M, x, y-1.33 * radius,
+ L, x+radius, y + 0.67 * radius,
+ x-radius, y + 0.67 * radius,
+ 'Z'
+ ];
+ },
+
+ 'triangle-down': function (x, y, radius) {
+ return [
+ M, x, y + 1.33 * radius,
+ L, x-radius, y-0.67 * radius,
+ x+radius, y-0.67 * radius,
+ 'Z'
+ ];
+ },
+ 'diamond': function (x, y, radius) {
+ return [
+ M, x, y-radius,
+ L, x+radius, y,
+ x, y+radius,
+ x-radius, y,
+ 'Z'
+ ];
+ },
+ 'arc': function (x, y, radius, options) {
+ var start = options.start,
+ end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
+ innerRadius = options.innerR,
+ cosStart = mathCos(start),
+ sinStart = mathSin(start),
+ cosEnd = mathCos(end),
+ sinEnd = mathSin(end),
+ longArc = options.end - start < mathPI ? 0 : 1;
+
+ return [
+ M,
+ x + radius * cosStart,
+ y + radius * sinStart,
+ 'A', // arcTo
+ radius, // x radius
+ radius, // y radius
+ 0, // slanting
+ longArc, // long or short arc
+ 1, // clockwise
+ x + radius * cosEnd,
+ y + radius * sinEnd,
+ L,
+ x + innerRadius * cosEnd,
+ y + innerRadius * sinEnd,
+ 'A', // arcTo
+ innerRadius, // x radius
+ innerRadius, // y radius
+ 0, // slanting
+ longArc, // long or short arc
+ 0, // clockwise
+ x + innerRadius * cosStart,
+ y + innerRadius * sinStart,
+
+ 'Z' // close
+ ];
+ }
+ },
+
+ /**
+ * Define a clipping rectangle
+ * @param {String} id
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} width
+ * @param {Number} height
+ */
+ clipRect: function (x, y, width, height) {
+ var wrapper,
+ id = PREFIX + idCounter++,
+
+ clipPath = this.createElement('clipPath').attr({
+ id: id
+ }).add(this.defs);
+
+ wrapper = this.rect(x, y, width, height, 0).add(clipPath);
+ wrapper.id = id;
+
+ return wrapper;
+ },
+
+
+ /**
+ * Take a color and return it if it's a string, make it a gradient if it's a
+ * gradient configuration object
+ *
+ * @param {Object} color The color or config object
+ */
+ color: function(color, elem, prop) {
+ var colorObject,
+ regexRgba = /^rgba/;
+ if (color && color.linearGradient) {
+ var renderer = this,
+ strLinearGradient = 'linearGradient',
+ linearGradient = color[strLinearGradient],
+ id = PREFIX + idCounter++,
+ gradientObject,
+ stopColor,
+ stopOpacity;
+ gradientObject = renderer.createElement(strLinearGradient).attr({
+ id: id,
+ gradientUnits: 'userSpaceOnUse',
+ x1: linearGradient[0],
+ y1: linearGradient[1],
+ x2: linearGradient[2],
+ y2: linearGradient[3]
+ }).add(renderer.defs);
+
+ each(color.stops, function(stop) {
+ if (regexRgba.test(stop[1])) {
+ colorObject = Color(stop[1]);
+ stopColor = colorObject.get('rgb');
+ stopOpacity = colorObject.get('a');
+ } else {
+ stopColor = stop[1];
+ stopOpacity = 1;
+ }
+ renderer.createElement('stop').attr({
+ offset: stop[0],
+ 'stop-color': stopColor,
+ 'stop-opacity': stopOpacity
+ }).add(gradientObject);
+ });
+
+ return 'url('+ this.url +'#'+ id +')';
+
+ // Webkit and Batik can't show rgba.
+ } else if (regexRgba.test(color)) {
+ colorObject = Color(color);
+ attr(elem, prop +'-opacity', colorObject.get('a'));
+
+ return colorObject.get('rgb');
+
+
+ } else {
+ return color;
+ }
+
+ },
+
+
+ /**
+ * Add text to the SVG object
+ * @param {String} str
+ * @param {Number} x Left position
+ * @param {Number} y Top position
+ */
+ text: function(str, x, y) {
+
+ // declare variables
+ var defaultChartStyle = defaultOptions.chart.style,
+ wrapper;
+
+ x = mathRound(pick(x, 0));
+ y = mathRound(pick(y, 0));
+
+ wrapper = this.createElement('text')
+ .attr({
+ x: x,
+ y: y,
+ text: str
+ })
+ .css({
+ 'font-family': defaultChartStyle.fontFamily,
+ 'font-size': defaultChartStyle.fontSize
+ });
+
+ wrapper.x = x;
+ wrapper.y = y;
+ return wrapper;
+ }
+}; // end SVGRenderer
+
+
+
+
+/* ****************************************************************************
+ * *
+ * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
+ * *
+ * For applications and websites that don't need IE support, like platform *
+ * targeted mobile apps and web apps, this code can be removed. *
+ * *
+ *****************************************************************************/
+var VMLRenderer;
+if (!hasSVG) {
+
+/**
+ * The VML element wrapper.
+ */
+var VMLElement = extendClass( SVGElement, {
+
+ /**
+ * Initialize a new VML element wrapper. It builds the markup as a string
+ * to minimize DOM traffic.
+ * @param {Object} renderer
+ * @param {Object} nodeName
+ */
+ init: function(renderer, nodeName) {
+ var markup = ['<', nodeName, ' filled="f" stroked="f"'],
+ style = ['position: ', ABSOLUTE, ';'];
+
+ // divs and shapes need size
+ if (nodeName == 'shape' || nodeName == DIV) {
+ style.push('left:0;top:0;width:10px;height:10px;');
+ }
+ if (docMode8) {
+ style.push('visibility: ', nodeName == DIV ? HIDDEN : VISIBLE);
+ }
+
+ markup.push(' style="', style.join(''), '"/>');
+
+ // create element with default attributes and style
+ if (nodeName) {
+ markup = nodeName == DIV || nodeName == 'span' || nodeName == 'img' ?
+ markup.join('')
+ : renderer.prepVML(markup);
+ this.element = createElement(markup);
+ }
+
+ this.renderer = renderer;
+ },
+
+ /**
+ * Add the node to the given parent
+ * @param {Object} parent
+ */
+ add: function(parent) {
+ var wrapper = this,
+ renderer = wrapper.renderer,
+ element = wrapper.element,
+ box = renderer.box,
+ inverted = parent && parent.inverted,
+
+ // get the parent node
+ parentNode = parent ?
+ parent.element || parent :
+ box;
+
+
+ // if the parent group is inverted, apply inversion on all children
+ if (inverted) { // only on groups
+ renderer.invertChild(element, parentNode);
+ }
+
+ // issue #140 workaround - related to #61 and #74
+ if (docMode8 && parentNode.gVis == HIDDEN) {
+ css(element, { visibility: HIDDEN });
+ }
+
+ // append it
+ parentNode.appendChild(element);
+
+ // align text after adding to be able to read offset
+ wrapper.added = true;
+ if (wrapper.alignOnAdd) {
+ wrapper.updateTransform();
+ }
+
+ return wrapper;
+ },
+
+ /**
+ * Get or set attributes
+ */
+ attr: function(hash, val) {
+ var key,
+ value,
+ i,
+ element = this.element || {},
+ elemStyle = element.style,
+ nodeName = element.nodeName,
+ renderer = this.renderer,
+ symbolName = this.symbolName,
+ childNodes,
+ hasSetSymbolSize,
+ shadows = this.shadows,
+ skipAttr,
+ ret = this;
+
+ // single key-value pair
+ if (isString(hash) && defined(val)) {
+ key = hash;
+ hash = {};
+ hash[key] = val;
+ }
+
+ // used as a getter, val is undefined
+ if (isString(hash)) {
+ key = hash;
+ if (key == 'strokeWidth' || key == 'stroke-width') {
+ ret = this.strokeweight;
+ } else {
+ ret = this[key];
+ }
+
+ // setter
+ } else {
+ for (key in hash) {
+ value = hash[key];
+ skipAttr = false;
+
+ // prepare paths
+ // symbols
+ if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.test(key)) {
+ // if one of the symbol size affecting parameters are changed,
+ // check all the others only once for each call to an element's
+ // .attr() method
+ if (!hasSetSymbolSize) {
+
+ this.symbolAttr(hash);
+
+ hasSetSymbolSize = true;
+ }
+
+ skipAttr = true;
+
+ } else if (key == 'd') {
+ value = value || [];
+ this.d = value.join(' '); // used in getter for animation
+
+ // convert paths
+ i = value.length;
+ var convertedPath = [];
+ while (i--) {
+
+ // Multiply by 10 to allow subpixel precision.
+ // Substracting half a pixel seems to make the coordinates
+ // align with SVG, but this hasn't been tested thoroughly
+ if (isNumber(value[i])) {
+ convertedPath[i] = mathRound(value[i] * 10) - 5;
+ }
+ // close the path
+ else if (value[i] == 'Z') {
+ convertedPath[i] = 'x';
+ }
+ else {
+ convertedPath[i] = value[i];
+ }
+
+ }
+ value = convertedPath.join(' ') || 'x';
+ element.path = value;
+
+ // update shadows
+ if (shadows) {
+ i = shadows.length;
+ while (i--) {
+ shadows[i].path = value;
+ }
+ }
+ skipAttr = true;
+
+ // directly mapped to css
+ } else if (key == 'zIndex' || key == 'visibility') {
+
+ // issue 61 workaround
+ if (docMode8 && key == 'visibility' && nodeName == 'DIV') {
+ element.gVis = value;
+ childNodes = element.childNodes;
+ i = childNodes.length;
+ while (i--) {
+ css(childNodes[i], { visibility: value });
+ }
+ if (value == VISIBLE) { // issue 74
+ value = null;
+ }
+ }
+
+ if (value) {
+ elemStyle[key] = value;
+ }
+
+
+
+ skipAttr = true;
+
+ // width and height
+ } else if (/^(width|height)$/.test(key)) {
+
+
+ // clipping rectangle special
+ if (this.updateClipping) {
+ this[key] = value;
+ this.updateClipping();
+
+ } else {
+ // normal
+ elemStyle[key] = value;
+ }
+
+ skipAttr = true;
+
+ // x and y
+ } else if (/^(x|y)$/.test(key)) {
+
+ this[key] = value; // used in getter
+
+ if (element.tagName == 'SPAN') {
+ this.updateTransform();
+
+ } else {
+ elemStyle[{ x: 'left', y: 'top' }[key]] = value;
+ }
+
+ // class name
+ } else if (key == 'class') {
+ // IE8 Standards mode has problems retrieving the className
+ element.className = value;
+
+ // stroke
+ } else if (key == 'stroke') {
+
+ value = renderer.color(value, element, key);
+
+ key = 'strokecolor';
+
+ // stroke width
+ } else if (key == 'stroke-width' || key == 'strokeWidth') {
+ element.stroked = value ? true : false;
+ key = 'strokeweight';
+ this[key] = value; // used in getter, issue #113
+ if (isNumber(value)) {
+ value += PX;
+ }
+
+ // dashStyle
+ } else if (key == 'dashstyle') {
+ var strokeElem = element.getElementsByTagName('stroke')[0] ||
+ createElement(renderer.prepVML(['<stroke/>']), null, null, element);
+ strokeElem[key] = value || 'solid';
+ this.dashstyle = value; /* because changing stroke-width will change the dash length
+ and cause an epileptic effect */
+ skipAttr = true;
+
+ // fill
+ } else if (key == 'fill') {
+
+ if (nodeName == 'SPAN') { // text color
+ elemStyle.color = value;
+ } else {
+ element.filled = value != NONE ? true : false;
+
+ value = renderer.color(value, element, key);
+
+ key = 'fillcolor';
+ }
+
+ // translation for animation
+ } else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'align') {
+ if (key == 'align') {
+ key = 'textAlign';
+ }
+ this[key] = value;
+ this.updateTransform();
+
+ skipAttr = true;
+ }
+
+ // text for rotated and non-rotated elements
+ else if (key == 'text') {
+ element.innerHTML = value;
+ skipAttr = true;
+ }
+
+
+ // let the shadow follow the main element
+ if (shadows && key == 'visibility') {
+ i = shadows.length;
+ while (i--) {
+ shadows[i].style[key] = value;
+ }
+ }
+
+
+
+ if (!skipAttr) {
+ if (docMode8) { // IE8 setAttribute bug
+ element[key] = value;
+ } else {
+ attr(element, key, value);
+ }
+ }
+ }
+ }
+ return ret;
+ },
+
+ /**
+ * Set the element's clipping to a predefined rectangle
+ *
+ * @param {String} id The id of the clip rectangle
+ */
+ clip: function(clipRect) {
+ var wrapper = this,
+ clipMembers = clipRect.members;
+
+ clipMembers.push(wrapper);
+ wrapper.destroyClip = function() {
+ erase(clipMembers, wrapper);
+ };
+ return wrapper.css(clipRect.getCSS(wrapper.inverted));
+ },
+
+ /**
+ * Set styles for the element
+ * @param {Object} styles
+ */
+ css: function(styles) {
+ var wrapper = this,
+ element = wrapper.element,
+ textWidth = styles && element.tagName == 'SPAN' && styles.width;
+
+ /*if (textWidth) {
+ extend(styles, {
+ display: 'block',
+ whiteSpace: 'normal'
+ });
+ }*/
+ if (textWidth) {
+ delete styles.width;
+ wrapper.textWidth = textWidth;
+ wrapper.updateTransform();
+ }
+
+ wrapper.styles = extend(wrapper.styles, styles);
+ css(wrapper.element, styles);
+
+
+
+ return wrapper;
+ },
+
+ /**
+ * Extend element.destroy by removing it from the clip members array
+ */
+ destroy: function() {
+ var wrapper = this;
+
+ if (wrapper.destroyClip) {
+ wrapper.destroyClip();
+ }
+
+ SVGElement.prototype.destroy.apply(wrapper);
+ },
+
+ /**
+ * Remove all child nodes of a group, except the v:group element
+ */
+ empty: function() {
+ var element = this.element,
+ childNodes = element.childNodes,
+ i = childNodes.length,
+ node;
+
+ while (i--) {
+ node = childNodes[i];
+ node.parentNode.removeChild(node);
+ }
+ },
+
+ /**
+ * VML override for calculating the bounding box based on offsets
+ *
+ * @return {Object} A hash containing values for x, y, width and height
+ */
+
+ getBBox: function() {
+ var element = this.element;
+
+ // faking getBBox in exported SVG in legacy IE
+ if (element.nodeName == 'text') {
+ element.style.position = ABSOLUTE;
+ }
+
+ return {
+ x: element.offsetLeft,
+ y: element.offsetTop,
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+
+ },
+
+ /**
+ * Add an event listener. VML override for normalizing event parameters.
+ * @param {String} eventType
+ * @param {Function} handler
+ */
+ on: function(eventType, handler) {
+ // simplest possible event model for internal use
+ this.element['on'+ eventType] = function() {
+ var evt = win.event;
+ evt.target = evt.srcElement;
+ handler(evt);
+ };
+ return this;
+ },
+
+
+ /**
+ * VML override private method to update elements based on internal
+ * properties based on SVG transform
+ */
+ updateTransform: function(hash) {
+ // aligning non added elements is expensive
+ if (!this.added) {
+ this.alignOnAdd = true;
+ return;
+ }
+
+ var wrapper = this,
+ elem = wrapper.element,
+ translateX = wrapper.translateX || 0,
+ translateY = wrapper.translateY || 0,
+ x = wrapper.x || 0,
+ y = wrapper.y || 0,
+ align = wrapper.textAlign || 'left',
+ alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
+ nonLeft = align && align != 'left';
+
+ // apply translate
+ if (translateX || translateY) {
+ wrapper.css({
+ marginLeft: translateX,
+ marginTop: translateY
+ });
+ }
+
+ // apply inversion
+ if (wrapper.inverted) { // wrapper is a group
+ each(elem.childNodes, function(child) {
+ wrapper.renderer.invertChild(child, elem);
+ });
+ }
+
+ if (elem.tagName == 'SPAN') {
+
+ var width, height,
+ rotation = wrapper.rotation,
+ lineHeight,
+ radians = 0,
+ costheta = 1,
+ sintheta = 0,
+ quad,
+ textWidth = pInt(wrapper.textWidth),
+ xCorr = wrapper.xCorr || 0,
+ yCorr = wrapper.yCorr || 0,
+ currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
+
+ if (currentTextTransform != wrapper.cTT) { // do the calculations and DOM access only if properties changed
+
+ if (defined(rotation)) {
+ radians = rotation * deg2rad; // deg to rad
+ costheta = mathCos(radians);
+ sintheta = mathSin(radians);
+
+ // Adjust for alignment and rotation.
+ // Test case: http://highcharts.com/tests/?file=text-rotation
+ css(elem, {
+ filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
+ ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
+ ', sizingMethod=\'auto expand\')'].join('') : NONE
+ });
+ }
+
+ width = elem.offsetWidth;
+ height = elem.offsetHeight;
+
+ // update textWidth
+ if (width > textWidth) {
+ css(elem, {
+ width: textWidth +PX,
+ display: 'block',
+ whiteSpace: 'normal'
+ });
+ width = textWidth;
+ }
+
+ // correct x and y
+ lineHeight = mathRound(pInt(elem.style.fontSize || 12) * 1.2);
+ xCorr = costheta < 0 && -width;
+ yCorr = sintheta < 0 && -height;
+
+ // correct for lineHeight and corners spilling out after rotation
+ quad = costheta * sintheta < 0;
+ xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
+ yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
+
+ // correct for the length/height of the text
+ if (nonLeft) {
+ xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
+ if (rotation) {
+ yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
+ }
+ css(elem, {
+ textAlign: align
+ });
+ }
+
+ // record correction
+ wrapper.xCorr = xCorr;
+ wrapper.yCorr = yCorr;
+ }
+
+ // apply position with correction
+ css(elem, {
+ left: x + xCorr,
+ top: y + yCorr
+ });
+
+ // record current text transform
+ wrapper.cTT = currentTextTransform;
+ }
+ },
+
+ /**
+ * Apply a drop shadow by copying elements and giving them different strokes
+ * @param {Boolean} apply
+ */
+ shadow: function(apply) {
+ var shadows = [],
+ i,
+ element = this.element,
+ renderer = this.renderer,
+ shadow,
+ elemStyle = element.style,
+ markup,
+ path = element.path;
+
+ // the path is some mysterious string-like object that can be cast to a string
+ if (''+ element.path === '') {
+ path = 'x';
+ }
+
+ if (apply) {
+ for (i = 1; i <= 3; i++) {
+ markup = ['<shape isShadow="true" strokeweight="', ( 7 - 2 * i ) ,
+ '" filled="false" path="', path,
+ '" coordsize="100,100" style="', element.style.cssText, '" />'];
+ shadow = createElement(renderer.prepVML(markup),
+ null, {
+ left: pInt(elemStyle.left) + 1,
+ top: pInt(elemStyle.top) + 1
+ }
+ );
+
+ // apply the opacity
+ markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
+ createElement(renderer.prepVML(markup), null, null, shadow);
+
+
+ // insert it
+ element.parentNode.insertBefore(shadow, element);
+
+ // record it
+ shadows.push(shadow);
+
+ }
+
+ this.shadows = shadows;
+ }
+ return this;
+
+ }
+});
+
+/**
+ * The VML renderer
+ */
+VMLRenderer = function() {
+ this.init.apply(this, arguments);
+};
+VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
+
+ isIE8: userAgent.indexOf('MSIE 8.0') > -1,
+
+
+ /**
+ * Initialize the VMLRenderer
+ * @param {Object} container
+ * @param {Number} width
+ * @param {Number} height
+ */
+ init: function(container, width, height) {
+ var renderer = this,
+ boxWrapper;
+
+ renderer.Element = VMLElement;
+ renderer.alignedObjects = [];
+
+ boxWrapper = renderer.createElement(DIV);
+ container.appendChild(boxWrapper.element);
+
+
+ // generate the containing box
+ renderer.box = boxWrapper.element;
+ renderer.boxWrapper = boxWrapper;
+
+
+ renderer.setSize(width, height, false);
+
+ // The only way to make IE6 and IE7 print is to use a global namespace. However,
+ // with IE8 the only way to make the dynamic shapes visible in screen and print mode
+ // seems to be to add the xmlns attribute and the behaviour style inline.
+ if (!doc.namespaces.hcv) {
+
+ doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
+
+ // setup default css
+ doc.createStyleSheet().cssText =
+ 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke'+
+ '{ behavior:url(#default#VML); display: inline-block; } ';
+
+ }
+ },
+
+ /**
+ * Define a clipping rectangle. In VML it is accomplished by storing the values
+ * for setting the CSS style to all associated members.
+ *
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} width
+ * @param {Number} height
+ */
+ clipRect: function (x, y, width, height) {
+
+ // create a dummy element
+ var clipRect = this.createElement();
+
+ // mimic a rectangle with its style object for automatic updating in attr
+ return extend(clipRect, {
+ members: [],
+ left: x,
+ top: y,
+ width: width,
+ height: height,
+ getCSS: function(inverted) {
+ var rect = this,//clipRect.element.style,
+ top = rect.top,
+ left = rect.left,
+ right = left + rect.width,
+ bottom = top + rect.height,
+ ret = {
+ clip: 'rect('+
+ mathRound(inverted ? left : top) + 'px,'+
+ mathRound(inverted ? bottom : right) + 'px,'+
+ mathRound(inverted ? right : bottom) + 'px,'+
+ mathRound(inverted ? top : left) +'px)'
+ };
+
+ // issue 74 workaround
+ if (!inverted && docMode8) {
+ extend(ret, {
+ width: right +PX,
+ height: bottom +PX
+ });
+ }
+ return ret;
+ },
+
+ // used in attr and animation to update the clipping of all members
+ updateClipping: function() {
+ each(clipRect.members, function(member) {
+ member.css(clipRect.getCSS(member.inverted));
+ });
+ }
+ });
+
+ },
+
+
+ /**
+ * Take a color and return it if it's a string, make it a gradient if it's a
+ * gradient configuration object, and apply opacity.
+ *
+ * @param {Object} color The color or config object
+ */
+ color: function(color, elem, prop) {
+ var colorObject,
+ regexRgba = /^rgba/,
+ markup;
+
+ if (color && color.linearGradient) {
+
+ var stopColor,
+ stopOpacity,
+ linearGradient = color.linearGradient,
+ angle,
+ color1,
+ opacity1,
+ color2,
+ opacity2;
+
+ each(color.stops, function(stop, i) {
+ if (regexRgba.test(stop[1])) {
+ colorObject = Color(stop[1]);
+ stopColor = colorObject.get('rgb');
+ stopOpacity = colorObject.get('a');
+ } else {
+ stopColor = stop[1];
+ stopOpacity = 1;
+ }
+
+ if (!i) { // first
+ color1 = stopColor;
+ opacity1 = stopOpacity;
+ } else {
+ color2 = stopColor;
+ opacity2 = stopOpacity;
+ }
+ });
+
+
+
+ // calculate the angle based on the linear vector
+ angle = 90 - math.atan(
+ (linearGradient[3] - linearGradient[1]) / // y vector
+ (linearGradient[2] - linearGradient[0]) // x vector
+ ) * 180 / mathPI;
+
+ // when colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle,
+ '" opacity="', opacity2, '" o:opacity2="', opacity1,
+ '" type="gradient" focus="100%" />'];
+ createElement(this.prepVML(markup), null, null, elem);
+
+
+
+ // if the color is an rgba color, split it and add a fill node
+ // to hold the opacity component
+ } else if (regexRgba.test(color) && elem.tagName != 'IMG') {
+
+ colorObject = Color(color);
+
+ markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
+ createElement(this.prepVML(markup), null, null, elem);
+
+ return colorObject.get('rgb');
+
+
+ } else {
+ return color;
+ }
+
+ },
+
+ /**
+ * Take a VML string and prepare it for either IE8 or IE6/IE7.
+ * @param {Array} markup A string array of the VML markup to prepare
+ */
+ prepVML: function(markup) {
+ var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
+ isIE8 = this.isIE8;
+
+ markup = markup.join('');
+
+ if (isIE8) { // add xmlns and style inline
+ markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
+ if (markup.indexOf('style="') == -1) {
+ markup = markup.replace('/>', ' style="'+ vmlStyle +'" />');
+ } else {
+ markup = markup.replace('style="', 'style="'+ vmlStyle);
+ }
+
+ } else { // add namespace
+ markup = markup.replace('<', '<hcv:');
+ }
+
+ return markup;
+ },
+
+ /**
+ * Create rotated and aligned text
+ * @param {String} str
+ * @param {Number} x
+ * @param {Number} y
+ */
+ text: function(str, x, y) {
+
+ var defaultChartStyle = defaultOptions.chart.style;
+
+ return this.createElement('span')
+ .attr({
+ text: str,
+ x: mathRound(x),
+ y: mathRound(y)
+ })
+ .css({
+ whiteSpace: 'nowrap',
+ fontFamily: defaultChartStyle.fontFamily,
+ fontSize: defaultChartStyle.fontSize
+ });
+ },
+
+ /**
+ * Create and return a path element
+ * @param {Array} path
+ */
+ path: function (path) {
+ // create the shape
+ return this.createElement('shape').attr({
+ // subpixel precision down to 0.1 (width and height = 10px)
+ coordsize: '100 100',
+ d: path
+ });
+ },
+
+ /**
+ * Create and return a circle element. In VML circles are implemented as
+ * shapes, which is faster than v:oval
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} r
+ */
+ circle: function(x, y, r) {
+ return this.path(this.symbols.circle(x, y, r));
+ },
+
+ /**
+ * Create a group using an outer div and an inner v:group to allow rotating
+ * and flipping. A simple v:group would have problems with positioning
+ * child HTML elements and CSS clip.
+ *
+ * @param {String} name The name of the group
+ */
+ g: function(name) {
+ var wrapper,
+ attribs;
+
+ // set the class name
+ if (name) {
+ attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
+ }
+
+ // the div to hold HTML and clipping
+ wrapper = this.createElement(DIV).attr(attribs);
+
+ return wrapper;
+ },
+
+ /**
+ * VML override to create a regular HTML image
+ * @param {String} src
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} width
+ * @param {Number} height
+ */
+ image: function(src, x, y, width, height) {
+ var obj = this.createElement('img')
+ .attr({ src: src });
+
+ if (arguments.length > 1) {
+ obj.css({
+ left: x,
+ top: y,
+ width: width,
+ height: height
+ });
+ }
+ return obj;
+ },
+
+ /**
+ * VML uses a shape for rect to overcome bugs and rotation problems
+ */
+ rect: function(x, y, width, height, r, strokeWidth) {
+
+ if (isObject(x)) {
+ y = x.y;
+ width = x.width;
+ height = x.height;
+ r = x.r;
+ x = x.x;
+ }
+ var wrapper = this.symbol('rect');
+ wrapper.r = r;
+
+ return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
+ },
+
+ /**
+ * In the VML renderer, each child of an inverted div (group) is inverted
+ * @param {Object} element
+ * @param {Object} parentNode
+ */
+ invertChild: function(element, parentNode) {
+ var parentStyle = parentNode.style;
+
+ css(element, {
+ flip: 'x',
+ left: pInt(parentStyle.width) - 10,
+ top: pInt(parentStyle.height) - 10,
+ rotation: -90
+ });
+ },
+
+ /**
+ * Symbol definitions that override the parent SVG renderer's symbols
+ *
+ */
+ symbols: {
+ // VML specific arc function
+ arc: function (x, y, radius, options) {
+ var start = options.start,
+ end = options.end,
+ cosStart = mathCos(start),
+ sinStart = mathSin(start),
+ cosEnd = mathCos(end),
+ sinEnd = mathSin(end),
+ innerRadius = options.innerR,
+ circleCorrection = 0.07 / radius,
+ innerCorrection = innerRadius && 0.1 / innerRadius || 0;
+
+ if (end - start === 0) { // no angle, don't show it.
+ return ['x'];
+
+ //} else if (end - start == 2 * mathPI) { // full circle
+ } else if (2 * mathPI - end + start < circleCorrection) { // full circle
+ // empirical correction found by trying out the limits for different radii
+ cosEnd = - circleCorrection;
+ } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
+ cosEnd = mathCos(start + innerCorrection);
+ }
+
+ return [
+ 'wa', // clockwise arc to
+ x - radius, // left
+ y - radius, // top
+ x + radius, // right
+ y + radius, // bottom
+ x + radius * cosStart, // start x
+ y + radius * sinStart, // start y
+ x + radius * cosEnd, // end x
+ y + radius * sinEnd, // end y
+
+
+ 'at', // anti clockwise arc to
+ x - innerRadius, // left
+ y - innerRadius, // top
+ x + innerRadius, // right
+ y + innerRadius, // bottom
+ x + innerRadius * cosEnd, // start x
+ y + innerRadius * sinEnd, // start y
+ x + innerRadius * cosStart, // end x
+ y + innerRadius * sinStart, // end y
+
+ 'x', // finish path
+ 'e' // close
+ ];
+
+ },
+ // Add circle symbol path. This performs significantly faster than v:oval.
+ circle: function (x, y, r) {
+ return [
+ 'wa', // clockwisearcto
+ x - r, // left
+ y - r, // top
+ x + r, // right
+ y + r, // bottom
+ x + r, // start x
+ y, // start y
+ x + r, // end x
+ y, // end y
+ //'x', // finish path
+ 'e' // close
+ ];
+ },
+ /**
+ * Add rectangle symbol path which eases rotation and omits arcsize problems
+ * compared to the built-in VML roundrect shape
+ *
+ * @param {Number} left Left position
+ * @param {Number} top Top position
+ * @param {Number} r Border radius
+ * @param {Object} options Width and height
+ */
+
+ rect: function (left, top, r, options) {
+ if (!defined(options)) {
+ return [];
+ }
+ var width = options.width,
+ height = options.height,
+ right = left + width,
+ bottom = top + height;
+
+ r = mathMin(r, width, height);
+
+ return [
+ M,
+ left + r, top,
+
+ L,
+ right - r, top,
+ 'wa',
+ right - 2 * r, top,
+ right, top + 2 * r,
+ right - r, top,
+ right, top + r,
+
+ L,
+ right, bottom - r,
+ 'wa',
+ right - 2 * r, bottom - 2 * r,
+ right, bottom,
+ right, bottom - r,
+ right - r, bottom,
+
+ L,
+ left + r, bottom,
+ 'wa',
+ left, bottom - 2 * r,
+ left + 2 * r, bottom,
+ left + r, bottom,
+ left, bottom - r,
+
+ L,
+ left, top + r,
+ 'wa',
+ left, top,
+ left + 2 * r, top + 2 * r,
+ left, top + r,
+ left + r, top,
+
+
+ 'x',
+ 'e'
+ ];
+
+ }
+ }
+});
+}
+/* ****************************************************************************
+ * *
+ * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
+ * *
+ *****************************************************************************/
+
+/**
+ * General renderer
+ */
+var Renderer = hasSVG ? SVGRenderer : VMLRenderer;
+
+
+/**
+ * The chart class
+ * @param {Object} options
+ * @param {Function} callback Function to run when the chart has loaded
+ */
+function Chart (options, callback) {
+
+ defaultXAxisOptions = merge(defaultXAxisOptions, defaultOptions.xAxis);
+ defaultYAxisOptions = merge(defaultYAxisOptions, defaultOptions.yAxis);
+ defaultOptions.xAxis = defaultOptions.yAxis = null;
+
+ // Handle regular options
+ options = merge(defaultOptions, options);
+
+ // Define chart variables
+ var optionsChart = options.chart,
+ optionsMargin = optionsChart.margin,
+ margin = isObject(optionsMargin) ?
+ optionsMargin :
+ [optionsMargin, optionsMargin, optionsMargin, optionsMargin],
+ optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
+ optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
+ optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
+ optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
+ spacingTop = optionsChart.spacingTop,
+ spacingRight = optionsChart.spacingRight,
+ spacingBottom = optionsChart.spacingBottom,
+ spacingLeft = optionsChart.spacingLeft,
+ spacingBox,
+ chartTitleOptions,
+ chartSubtitleOptions,
+ plotTop,
+ marginRight,
+ marginBottom,
+ plotLeft,
+ axisOffset,
+ renderTo,
+ renderToClone,
+ container,
+ containerId,
+ containerWidth,
+ containerHeight,
+ chartWidth,
+ chartHeight,
+ oldChartWidth,
+ oldChartHeight,
+ chartBackground,
+ plotBackground,
+ plotBGImage,
+ plotBorder,
+ chart = this,
+ chartEvents = optionsChart.events,
+ runChartClick = chartEvents && !!chartEvents.click,
+ eventType,
+ isInsidePlot, // function
+ tooltip,
+ mouseIsDown,
+ loadingDiv,
+ loadingSpan,
+ loadingShown,
+ plotHeight,
+ plotWidth,
+ tracker,
+ trackerGroup,
+ placeTrackerGroup,
+ legend,
+ legendWidth,
+ legendHeight,
+ chartPosition,// = getPosition(container),
+ hasCartesianSeries = optionsChart.showAxes,
+ isResizing = 0,
+ axes = [],
+ maxTicks, // handle the greatest amount of ticks on grouped axes
+ series = [],
+ inverted,
+ renderer,
+ tooltipTick,
+ tooltipInterval,
+ hoverX,
+ drawChartBox, // function
+ getMargins, // function
+ resetMargins, // function
+ setChartSize, // function
+ resize,
+ zoom, // function
+ zoomOut; // function
+
+
+ /**
+ * Create a new axis object
+ * @param {Object} chart
+ * @param {Object} options
+ */
+ function Axis (chart, options) {
+
+ // Define variables
+ var isXAxis = options.isX,
+ opposite = options.opposite, // needed in setOptions
+ horiz = inverted ? !isXAxis : isXAxis,
+ side = horiz ?
+ (opposite ? 0 /* top */ : 2 /* bottom */) :
+ (opposite ? 1 /* right*/ : 3 /* left */ ),
+ stacks = {};
+
+
+ options = merge(
+ isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
+ [defaultTopAxisOptions, defaultRightAxisOptions,
+ defaultBottomAxisOptions, defaultLeftAxisOptions][side],
+ options
+ );
+
+ var axis = this,
+ isDatetimeAxis = options.type == 'datetime',
+ offset = options.offset || 0,
+ xOrY = isXAxis ? 'x' : 'y',
+ axisLength,
+ transA, // translation factor
+ oldTransA, // used for prerendering
+ transB = horiz ? plotLeft : marginBottom, // translation addend
+ translate, // fn
+ getPlotLinePath, // fn
+ axisGroup,
+ gridGroup,
+ axisLine,
+ dataMin,
+ dataMax,
+ associatedSeries,
+ userSetMin,
+ userSetMax,
+ max = null,
+ min = null,
+ oldMin,
+ oldMax,
+ minPadding = options.minPadding,
+ maxPadding = options.maxPadding,
+ isLinked = defined(options.linkedTo),
+ ignoreMinPadding, // can be set to true by a column or bar series
+ ignoreMaxPadding,
+ usePercentage,
+ events = options.events,
+ eventType,
+ plotLinesAndBands = [],
+ tickInterval,
+ minorTickInterval,
+ magnitude,
+ tickPositions, // array containing predefined positions
+ ticks = {},
+ minorTicks = {},
+ alternateBands = {},
+ tickAmount,
+ labelOffset,
+ axisTitleMargin,// = options.title.margin,
+ dateTimeLabelFormat,
+ categories = options.categories,
+ labelFormatter = options.labels.formatter || // can be overwritten by dynamic format
+ function() {
+ var value = this.value,
+ ret;
+
+ if (dateTimeLabelFormat) { // datetime axis
+ ret = dateFormat(dateTimeLabelFormat, value);
+
+ } else if (tickInterval % 1000000 === 0) { // use M abbreviation
+ ret = (value / 1000000) +'M';
+
+ } else if (tickInterval % 1000 === 0) { // use k abbreviation
+ ret = (value / 1000) +'k';
+
+ } else if (!categories && value >= 1000) { // add thousands separators
+ ret = numberFormat(value, 0);
+
+ } else { // strings (categories) and small numbers
+ ret = value;
+ }
+ return ret;
+ },
+
+ staggerLines = horiz && options.labels.staggerLines,
+ reversed = options.reversed,
+ tickmarkOffset = (categories && options.tickmarkPlacement == 'between') ? 0.5 : 0;
+
+ /**
+ * The Tick class
+ */
+ function Tick(pos, minor) {
+ var tick = this;
+ tick.pos = pos;
+ tick.minor = minor;
+ tick.isNew = true;
+
+ if (!minor) {
+ tick.addLabel();
+ }
+ }
+ Tick.prototype = {
+ /**
+ * Write the tick label
+ */
+ addLabel: function() {
+ var pos = this.pos,
+ labelOptions = options.labels,
+ str,
+ withLabel = !((pos == min && !pick(options.showFirstLabel, 1)) ||
+ (pos == max && !pick(options.showLastLabel, 0))),
+ width = categories && horiz && categories.length &&
+ !labelOptions.step && !labelOptions.staggerLines &&
+ !labelOptions.rotation &&
+ plotWidth / categories.length ||
+ !horiz && plotWidth / 2,
+ css,
+ label = this.label;
+
+
+ // get the string
+ str = labelFormatter.call({
+ isFirst: pos == tickPositions[0],
+ isLast: pos == tickPositions[tickPositions.length - 1],
+ dateTimeLabelFormat: dateTimeLabelFormat,
+ value: (categories && categories[pos] ? categories[pos] : pos)
+ });
+
+ // prepare CSS
+ css = width && { width: (width - 2 * (labelOptions.padding || 10)) +PX };
+ css = extend(css, labelOptions.style);
+
+ // first call
+ if (label === UNDEFINED) {
+ this.label =
+ defined(str) && withLabel && labelOptions.enabled ?
+ renderer.text(
+ str,
+ 0,
+ 0
+ )
+ .attr({
+ align: labelOptions.align,
+ rotation: labelOptions.rotation
+ })
+ // without position absolute, IE export sometimes is wrong
+ .css(css)
+ .add(axisGroup):
+ null;
+
+ // update
+ } else if (label) {
+ label.attr({ text: str })
+ .css(css);
+ }
+ },
+ /**
+ * Get the offset height or width of the label
+ */
+ getLabelSize: function() {
+ var label = this.label;
+ return label ?
+ ((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
+ 0;
+ },
+ /**
+ * Put everything in place
+ *
+ * @param index {Number}
+ * @param old {Boolean} Use old coordinates to prepare an animation into new position
+ */
+ render: function(index, old) {
+ var tick = this,
+ major = !tick.minor,
+ label = tick.label,
+ pos = tick.pos,
+ labelOptions = options.labels,
+ gridLine = tick.gridLine,
+ gridLineWidth = major ? options.gridLineWidth : options.minorGridLineWidth,
+ gridLineColor = major ? options.gridLineColor : options.minorGridLineColor,
+ dashStyle = major ?
+ options.gridLineDashStyle :
+ options.minorGridLineDashStyle,
+ gridLinePath,
+ mark = tick.mark,
+ markPath,
+ tickLength = major ? options.tickLength : options.minorTickLength,
+ tickWidth = major ? options.tickWidth : (options.minorTickWidth || 0),
+ tickColor = major ? options.tickColor : options.minorTickColor,
+ tickPosition = major ? options.tickPosition : options.minorTickPosition,
+ step = labelOptions.step,
+ cHeight = old && oldChartHeight || chartHeight,
+ attribs,
+ x,
+ y;
+
+ // get x and y position for ticks and labels
+ x = horiz ?
+ translate(pos + tickmarkOffset, null, null, old) + transB :
+ plotLeft + offset + (opposite ? (old && oldChartWidth || chartWidth) - marginRight - plotLeft : 0);
+
+ y = horiz ?
+ cHeight - marginBottom + offset - (opposite ? plotHeight : 0) :
+ cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;
+
+ // create the grid line
+ if (gridLineWidth) {
+ gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
+
+ if (gridLine === UNDEFINED) {
+ attribs = {
+ stroke: gridLineColor,
+ 'stroke-width': gridLineWidth
+ };
+ if (dashStyle) {
+ attribs.dashstyle = dashStyle;
+ }
+ tick.gridLine = gridLine =
+ gridLineWidth ?
+ renderer.path(gridLinePath)
+ .attr(attribs).add(gridGroup) :
+ null;
+ }
+ if (gridLine && gridLinePath) {
+ gridLine.animate({
+ d: gridLinePath
+ });
+ }
+ }
+
+ // create the tick mark
+ if (tickWidth) {
+
+ // negate the length
+ if (tickPosition == 'inside') {
+ tickLength = -tickLength;
+ }
+ if (opposite) {
+ tickLength = -tickLength;
+ }
+
+ markPath = renderer.crispLine([
+ M,
+ x,
+ y,
+ L,
+ x + (horiz ? 0 : -tickLength),
+ y + (horiz ? tickLength : 0)
+ ], tickWidth);
+
+ if (mark) { // updating
+ mark.animate({
+ d: markPath
+ });
+ } else { // first time
+ tick.mark = renderer.path(
+ markPath
+ ).attr({
+ stroke: tickColor,
+ 'stroke-width': tickWidth
+ }).add(axisGroup);
+ }
+ }
+
+ // the label is created on init - now move it into place
+ if (label) {
+ x = x + labelOptions.x - (tickmarkOffset && horiz ?
+ tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
+ y = y + labelOptions.y - (tickmarkOffset && !horiz ?
+ tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
+
+ // vertically centered
+ if (!defined(labelOptions.y)) {
+ y += parseInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
+ }
+
+
+ // correct for staggered labels
+ if (staggerLines) {
+ y += (index % staggerLines) * 16;
+ }
+ // apply step
+ if (step) {
+ // show those indices dividable by step
+ label[index % step ? 'hide' : 'show']();
+ }
+
+ label[tick.isNew ? 'attr' : 'animate']({
+ x: x,
+ y: y
+ });
+
+ }
+
+ tick.isNew = false;
+ },
+ /**
+ * Destructor for the tick prototype
+ */
+ destroy: function() {
+ var tick = this,
+ n;
+ for (n in tick) {
+ if (tick[n] && tick[n].destroy) {
+ tick[n].destroy();
+ }
+ }
+ }
+ };
+
+ /**
+ * The object wrapper for plot lines and plot bands
+ * @param {Object} options
+ */
+ function PlotLineOrBand(options) {
+ var plotLine = this;
+ if (options) {
+ plotLine.options = options;
+ plotLine.id = options.id;
+ }
+
+ //plotLine.render()
+ return plotLine;
+ }
+
+ PlotLineOrBand.prototype = {
+
+ /**
+ * Render the plot line or plot band. If it is already existing,
+ * move it.
+ */
+ render: function () {
+ var plotLine = this,
+ options = plotLine.options,
+ optionsLabel = options.label,
+ label = plotLine.label,
+ width = options.width,
+ to = options.to,
+ toPath, // bands only
+ from = options.from,
+ dashStyle = options.dashStyle,
+ svgElem = plotLine.svgElem,
+ path = [],
+ addEvent,
+ eventType,
+ xs,
+ ys,
+ x,
+ y,
+ color = options.color,
+ zIndex = options.zIndex,
+ events = options.events,
+ attribs;
+
+ // plot line
+ if (width) {
+ path = getPlotLinePath(options.value, width);
+ attribs = {
+ stroke: color,
+ 'stroke-width': width
+ };
+ if (dashStyle) {
+ attribs.dashstyle = dashStyle;
+ }
+ }
+
+ // plot band
+ else if (defined(from) && defined(to)) {
+ // keep within plot area
+ from = mathMax(from, min);
+ to = mathMin(to, max);
+
+ toPath = getPlotLinePath(to);
+ path = getPlotLinePath(from);
+ if (path && toPath) {
+ path.push(
+ toPath[4],
+ toPath[5],
+ toPath[1],
+ toPath[2]
+ );
+ } else { // outside the axis area
+ path = null;
+ }
+ attribs = {
+ fill: color
+ };
+ } else {
+ return;
+ }
+ // zIndex
+ if (defined(zIndex)) {
+ attribs.zIndex = zIndex;
+ }
+
+ // common for lines and bands
+ if (svgElem) {
+ if (path) {
+ svgElem.animate({
+ d: path
+ }, null, svgElem.onGetPath);
+ } else {
+ svgElem.hide();
+ svgElem.onGetPath = function() {
+ svgElem.show();
+ }
+ }
+ } else if (path && path.length) {
+ plotLine.svgElem = svgElem = renderer.path(path)
+ .attr(attribs).add();
+
+ // events
+ if (events) {
+ addEvent = function(eventType) {
+ svgElem.on(eventType, function(e) {
+ events[eventType].apply(plotLine, [e]);
+ });
+ };
+ for (eventType in events) {
+ addEvent(eventType);
+ }
+ }
+ }
+
+ // the plot band/line label
+ if (optionsLabel && defined(optionsLabel.text) && path && path.length && plotWidth > 0 && plotHeight > 0) {
+ // apply defaults
+ optionsLabel = merge({
+ align: horiz && toPath && 'center',
+ x: horiz ? !toPath && 4 : 10,
+ verticalAlign : !horiz && toPath && 'middle',
+ y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
+ rotation: horiz && !toPath && 90
+ }, optionsLabel);
+
+ // add the SVG element
+ if (!label) {
+ plotLine.label = label = renderer.text(
+ optionsLabel.text,
+ 0,
+ 0
+ )
+ .attr({
+ align: optionsLabel.textAlign || optionsLabel.align,
+ rotation: optionsLabel.rotation,
+ zIndex: zIndex
+ })
+ .css(optionsLabel.style)
+ .add();
+ }
+
+ // get the bounding box and align the label
+ xs = [path[1], path[4], path[6] || path[1]];
+ ys = [path[2], path[5], path[7] || path[2]];
+ x = mathMin.apply(math, xs);
+ y = mathMin.apply(math, ys);
+
+ label.align(optionsLabel, false, {
+ x: x,
+ y: y,
+ width: mathMax.apply(math, xs) - x,
+ height: mathMax.apply(math, ys) - y
+ });
+ label.show();
+
+ } else if (label) { // move out of sight
+ label.hide();
+ }
+
+ // chainable
+ return plotLine;
+ },
+
+ /**
+ * Remove the plot line or band
+ */
+ destroy: function() {
+ var obj = this,
+ n;
+
+ for (n in obj) {
+ if (obj[n] && obj[n].destroy) {
+ obj[n].destroy(); // destroy SVG wrappers
+ }
+ delete obj[n];
+ }
+ // remove it from the lookup
+ erase(plotLinesAndBands, obj);
+ }
+ };
+
+
+ /**
+ * Get the minimum and maximum for the series of each axis
+ */
+ function getSeriesExtremes() {
+ var posStack = [],
+ negStack = [],
+ run;
+
+ // reset dataMin and dataMax in case we're redrawing
+ dataMin = dataMax = null;
+
+ // get an overview of what series are associated with this axis
+ associatedSeries = [];
+
+ each(series, function(serie) {
+ run = false;
+
+
+ // match this axis against the series' given or implicated axis
+ each(['xAxis', 'yAxis'], function(strAxis) {
+ if (
+ // the series is a cartesian type, and...
+ serie.isCartesian &&
+ // we're in the right x or y dimension, and...
+ (strAxis == 'xAxis' && isXAxis || strAxis == 'yAxis' && !isXAxis) && (
+ // the axis number is given in the options and matches this axis index, or
+ (serie.options[strAxis] == options.index) ||
+ // the axis index is not given
+ (serie.options[strAxis] === UNDEFINED && options.index === 0)
+ )
+ ) {
+ serie[strAxis] = axis;
+ associatedSeries.push(serie);
+
+ // the series is visible, run the min/max detection
+ run = true;
+ }
+ });
+ // ignore hidden series if opted
+ if (!serie.visible && optionsChart.ignoreHiddenSeries) {
+ run = false;
+ }
+
+ if (run) {
+
+ var stacking,
+ posPointStack,
+ negPointStack,
+ stackKey,
+ negKey;
+
+ if (!isXAxis) {
+ stacking = serie.options.stacking;
+ usePercentage = stacking == 'percent';
+
+ // create a stack for this particular series type
+ if (stacking) {
+ stackKey = serie.type + pick(serie.options.stack, '');
+ negKey = '-'+ stackKey;
+ serie.stackKey = stackKey; // used in translate
+
+ posPointStack = posStack[stackKey] || []; // contains the total values for each x
+ posStack[stackKey] = posPointStack;
+
+ negPointStack = negStack[negKey] || [];
+ negStack[negKey] = negPointStack;
+ }
+ if (usePercentage) {
+ dataMin = 0;
+ dataMax = 99;
+ }
+ }
+ if (serie.isCartesian) { // line, column etc. need axes, pie doesn't
+ each(serie.data, function(point, i) {
+ var pointX = point.x,
+ pointY = point.y,
+ isNegative = pointY < 0,
+ pointStack = isNegative ? negPointStack : posPointStack,
+ key = isNegative ? negKey : stackKey,
+ totalPos,
+ pointLow;
+
+ // initial values
+ if (dataMin === null) {
+
+ // start out with the first point
+ dataMin = dataMax = point[xOrY];
+ }
+
+ // x axis
+ if (isXAxis) {
+ if (pointX > dataMax) {
+ dataMax = pointX;
+ } else if (pointX < dataMin) {
+ dataMin = pointX;
+ }
+ }
+
+ // y axis
+ else if (defined(pointY)) {
+ if (stacking) {
+ pointStack[pointX] =
+ defined(pointStack[pointX]) ?
+ pointStack[pointX] + pointY : pointY;
+ }
+ totalPos = pointStack ? pointStack[pointX] : pointY;
+ pointLow = pick(point.low, totalPos);
+ if (!usePercentage) {
+ if (totalPos > dataMax) {
+ dataMax = totalPos;
+ } else if (pointLow < dataMin) {
+ dataMin = pointLow;
+ }
+ }
+ if (stacking) {
+ // add the series
+ if (!stacks[key]) {
+ stacks[key] = {};
+ }
+ stacks[key][pointX] = {
+ total: totalPos,
+ cum: totalPos
+ };
+ }
+ }
+ });
+
+
+ // For column, areas and bars, set the minimum automatically to zero
+ // and prevent that minPadding is added in setScale
+ if (/(area|column|bar)/.test(serie.type) && !isXAxis) {
+ if (dataMin >= 0) {
+ dataMin = 0;
+ ignoreMinPadding = true;
+ } else if (dataMax < 0) {
+ dataMax = 0;
+ ignoreMaxPadding = true;
+ }
+ }
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Translate from axis value to pixel position on the chart, or back
+ *
+ */
+ translate = function(val, backwards, cvsCoord, old) {
+ var sign = 1,
+ cvsOffset = 0,
+ localA = old ? oldTransA : transA,
+ localMin = old ? oldMin : min,
+ returnValue;
+
+ if (!localA) {
+ localA = transA;
+ }
+
+ if (cvsCoord) {
+ sign *= -1; // canvas coordinates inverts the value
+ cvsOffset = axisLength;
+ }
+ if (reversed) { // reversed axis
+ sign *= -1;
+ cvsOffset -= sign * axisLength;
+ }
+
+ if (backwards) { // reverse translation
+ if (reversed) {
+ val = axisLength - val;
+ }
+ returnValue = val / localA + localMin; // from chart pixel to value
+
+ } else { // normal translation
+ returnValue = sign * (val - localMin) * localA + cvsOffset; // from value to chart pixel
+ }
+
+ return returnValue;
+ };
+
+ /**
+ * Create the path for a plot line that goes from the given value on
+ * this axis, across the plot to the opposite side
+ * @param {Number} value
+ * @param {Number} lineWidth Used for calculation crisp line
+ * @param {Number] old Use old coordinates (for resizing and rescaling)
+ */
+ getPlotLinePath = function(value, lineWidth, old) {
+ var x1,
+ y1,
+ x2,
+ y2,
+ translatedValue = translate(value, null, null, old),
+ cHeight = old && oldChartHeight || chartHeight,
+ cWidth = old && oldChartWidth || chartWidth,
+ skip;
+
+ x1 = x2 = mathRound(translatedValue + transB);
+ y1 = y2 = mathRound(cHeight - translatedValue - transB);
+
+ if (isNaN(translatedValue)) { // no min or max
+ skip = true;
+
+ } else if (horiz) {
+ y1 = plotTop;
+ y2 = cHeight - marginBottom;
+ if (x1 < plotLeft || x1 > plotLeft + plotWidth) {
+ skip = true;
+ }
+ } else {
+ x1 = plotLeft;
+ x2 = cWidth - marginRight;
+ if (y1 < plotTop || y1 > plotTop + plotHeight) {
+ skip = true;
+ }
+ }
+ return skip ?
+ null :
+ renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
+ };
+
+ /**
+ * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
+ * @param {Number} interval
+ */
+ function normalizeTickInterval(interval, multiples) {
+ var normalized;
+
+ // round to a tenfold of 1, 2, 2.5 or 5
+ magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
+ normalized = interval / magnitude;
+
+ // multiples for a linear scale
+ if (!multiples) {
+ multiples = [1, 2, 2.5, 5, 10];
+ //multiples = [1, 2, 2.5, 4, 5, 7.5, 10];
+
+ // the allowDecimals option
+ if (options.allowDecimals === false) {
+ if (magnitude == 1) {
+ multiples = [1, 2, 5, 10];
+ } else if (magnitude <= 0.1) {
+ multiples = [1 / magnitude];
+ }
+ }
+ }
+
+ // normalize the interval to the nearest multiple
+ for (var i = 0; i < multiples.length; i++) {
+ interval = multiples[i];
+ if (normalized <= (multiples[i] + (multiples[i+1] || multiples[i])) / 2) {
+ break;
+ }
+ }
+
+ // multiply back to the correct magnitude
+ interval *= magnitude;
+
+ return interval;
+ }
+
+ /**
+ * Set the tick positions to a time unit that makes sense, for example
+ * on the first of each month or on every Monday.
+ */
+ function setDateTimeTickPositions() {
+ tickPositions = [];
+ var i,
+ useUTC = defaultOptions.global.useUTC,
+ oneSecond = 1000 / timeFactor,
+ oneMinute = 60000 / timeFactor,
+ oneHour = 3600000 / timeFactor,
+ oneDay = 24 * 3600000 / timeFactor,
+ oneWeek = 7 * 24 * 3600000 / timeFactor,
+ oneMonth = 30 * 24 * 3600000 / timeFactor,
+ oneYear = 31556952000 / timeFactor,
+
+ units = [[
+ 'second', // unit name
+ oneSecond, // fixed incremental unit
+ [1, 2, 5, 10, 15, 30] // allowed multiples
+ ], [
+ 'minute', // unit name
+ oneMinute, // fixed incremental unit
+ [1, 2, 5, 10, 15, 30] // allowed multiples
+ ], [
+ 'hour', // unit name
+ oneHour, // fixed incremental unit
+ [1, 2, 3, 4, 6, 8, 12] // allowed multiples
+ ], [
+ 'day', // unit name
+ oneDay, // fixed incremental unit
+ [1, 2] // allowed multiples
+ ], [
+ 'week', // unit name
+ oneWeek, // fixed incremental unit
+ [1, 2] // allowed multiples
+ ], [
+ 'month',
+ oneMonth,
+ [1, 2, 3, 4, 6]
+ ], [
+ 'year',
+ oneYear,
+ null
+ ]],
+
+ unit = units[6], // default unit is years
+ interval = unit[1],
+ multiples = unit[2];
+
+ // loop through the units to find the one that best fits the tickInterval
+ for (i = 0; i < units.length; i++) {
+ unit = units[i];
+ interval = unit[1];
+ multiples = unit[2];
+
+
+ if (units[i+1]) {
+ // lessThan is in the middle between the highest multiple and the next unit.
+ var lessThan = (interval * multiples[multiples.length - 1] +
+ units[i + 1][1]) / 2;
+
+ // break and keep the current unit
+ if (tickInterval <= lessThan) {
+ break;
+ }
+ }
+ }
+
+ // prevent 2.5 years intervals, though 25, 250 etc. are allowed
+ if (interval == oneYear && tickInterval < 5 * interval) {
+ multiples = [1, 2, 5];
+ }
+
+ // get the minimum value by flooring the date
+ var multitude = normalizeTickInterval(tickInterval / interval, multiples),
+ minYear, // used in months and years as a basis for Date.UTC()
+ minDate = new Date(min * timeFactor);
+
+ minDate.setMilliseconds(0);
+
+ if (interval >= oneSecond) { // second
+ minDate.setSeconds(interval >= oneMinute ? 0 :
+ multitude * mathFloor(minDate.getSeconds() / multitude));
+ }
+
+ if (interval >= oneMinute) { // minute
+ minDate[setMinutes](interval >= oneHour ? 0 :
+ multitude * mathFloor(minDate[getMinutes]() / multitude));
+ }
+
+ if (interval >= oneHour) { // hour
+ minDate[setHours](interval >= oneDay ? 0 :
+ multitude * mathFloor(minDate[getHours]() / multitude));
+ }
+
+ if (interval >= oneDay) { // day
+ minDate[setDate](interval >= oneMonth ? 1 :
+ multitude * mathFloor(minDate[getDate]() / multitude));
+ }
+
+ if (interval >= oneMonth) { // month
+ minDate[setMonth](interval >= oneYear ? 0 :
+ multitude * mathFloor(minDate[getMonth]() / multitude));
+ minYear = minDate[getFullYear]();
+ }
+
+ if (interval >= oneYear) { // year
+ minYear -= minYear % multitude;
+ minDate[setFullYear](minYear);
+ }
+
+ // week is a special case that runs outside the hierarchy
+ if (interval == oneWeek) {
+ // get start of current week, independent of multitude
+ minDate[setDate](minDate[getDate]() - minDate[getDay]() +
+ options.startOfWeek);
+ }
+
+
+ // get tick positions
+ i = 1; // prevent crash just in case
+ minYear = minDate[getFullYear]();
+ var time = minDate.getTime() / timeFactor,
+ minMonth = minDate[getMonth](),
+ minDateDate = minDate[getDate]();
+
+ // iterate and add tick positions at appropriate values
+ while (time < max && i < plotWidth) {
+ tickPositions.push(time);
+
+ // if the interval is years, use Date.UTC to increase years
+ if (interval == oneYear) {
+ time = makeTime(minYear + i * multitude, 0) / timeFactor;
+
+ // if the interval is months, use Date.UTC to increase months
+ } else if (interval == oneMonth) {
+ time = makeTime(minYear, minMonth + i * multitude) / timeFactor;
+
+ // if we're using global time, the interval is not fixed as it jumps
+ // one hour at the DST crossover
+ } else if (!useUTC && (interval == oneDay || interval == oneWeek)) {
+ time = makeTime(minYear, minMonth, minDateDate +
+ i * multitude * (interval == oneDay ? 1 : 7));
+
+ // else, the interval is fixed and we use simple addition
+ } else {
+ time += interval * multitude;
+ }
+
+ i++;
+ }
+ // push the last time
+ tickPositions.push(time);
+
+
+ // dynamic label formatter
+ dateTimeLabelFormat = options.dateTimeLabelFormats[unit[0]];
+ }
+
+ /**
+ * Fix JS round off float errors
+ * @param {Number} num
+ */
+ function correctFloat(num) {
+ var invMag, ret = num;
+ if (defined(magnitude)) {
+ invMag = (magnitude < 1 ? mathRound(1 / magnitude) : 1) * 10;
+ ret = mathRound(num * invMag) / invMag;
+ }
+ return ret;
+ }
+
+ /**
+ * Set the tick positions of a linear axis to round values like whole tens or every five.
+ */
+ function setLinearTickPositions() {
+
+ var i,
+ roundedMin = mathFloor(min / tickInterval) * tickInterval,
+ roundedMax = mathCeil(max / tickInterval) * tickInterval;
+
+ tickPositions = [];
+
+ // populate the intermediate values
+ i = correctFloat(roundedMin);
+ while (i <= roundedMax) {
+ tickPositions.push(i);
+ i = correctFloat(i + tickInterval);
+ }
+
+ }
+
+ /**
+ * Set the tick positions to round values and optionally extend the extremes
+ * to the nearest tick
+ */
+ function setTickPositions(secondPass) {
+ var length,
+ catPad,
+ linkedParent,
+ linkedParentExtremes,
+ tickIntervalOption = options.tickInterval,
+ tickPixelIntervalOption = options.tickPixelInterval,
+ maxZoom = options.maxZoom || (
+ isXAxis ?
+ mathMin(chart.smallestInterval * 5, dataMax - dataMin) :
+ null
+ ),
+ zoomOffset;
+
+
+ axisLength = horiz ? plotWidth : plotHeight;
+
+ // linked axis gets the extremes from the parent axis
+ if (isLinked) {
+ linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
+ linkedParentExtremes = linkedParent.getExtremes();
+ min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
+ max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
+ }
+
+ // initial min and max from the extreme data values
+ else {
+ min = pick(userSetMin, options.min, dataMin);
+ max = pick(userSetMax, options.max, dataMax);
+ }
+
+ // maxZoom exceeded, just center the selection
+ if (max - min < maxZoom) {
+ zoomOffset = (maxZoom - max + min) / 2;
+ // if min and max options have been set, don't go beyond it
+ min = mathMax(min - zoomOffset, pick(options.min, min - zoomOffset), dataMin);
+ max = mathMin(min + maxZoom, pick(options.max, min + maxZoom), dataMax);
+ }
+
+ // pad the values to get clear of the chart's edges
+ if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
+ length = (max - min) || 1;
+ if (!defined(options.min) && !defined(userSetMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
+ min -= length * minPadding;
+ }
+ if (!defined(options.max) && !defined(userSetMax) && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
+ max += length * maxPadding;
+ }
+ }
+
+ // get tickInterval
+ if (min == max) {
+ tickInterval = 1;
+ } else if (isLinked && !tickIntervalOption &&
+ tickPixelIntervalOption == linkedParent.options.tickPixelInterval) {
+ tickInterval = linkedParent.tickInterval;
+ } else {
+ tickInterval = pick(
+ tickIntervalOption,
+ categories ? // for categoried axis, 1 is default, for linear axis use tickPix
+ 1 :
+ (max - min) * tickPixelIntervalOption / axisLength
+ );
+ }
+
+ if (!isDatetimeAxis && !defined(options.tickInterval)) { // linear
+ tickInterval = normalizeTickInterval(tickInterval);
+ }
+ axis.tickInterval = tickInterval; // record for linked axis
+
+ // get minorTickInterval
+ minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
+ tickInterval / 5 : options.minorTickInterval;
+
+ // find the tick positions
+ if (isDatetimeAxis) {
+ setDateTimeTickPositions();
+ } else {
+ setLinearTickPositions();
+ }
+
+ if (!isLinked) {
+ // pad categorised axis to nearest half unit
+ if (categories || (isXAxis && chart.hasColumn)) {
+ catPad = (categories ? 1 : tickInterval) * 0.5;
+ if (categories || !defined(pick(options.min, userSetMin))) {
+ min -= catPad;
+ }
+ if (categories || !defined(pick(options.max, userSetMax))) {
+ max += catPad;
+ }
+ }
+
+ // reset min/max or remove extremes based on start/end on tick
+ var roundedMin = tickPositions[0],
+ roundedMax = tickPositions[tickPositions.length - 1];
+
+ if (options.startOnTick) {
+ min = roundedMin;
+ } else if (min > roundedMin) {
+ tickPositions.shift();
+ }
+
+ if (options.endOnTick) {
+ max = roundedMax;
+ } else if (max < roundedMax) {
+ tickPositions.pop();
+ }
+
+ // record the greatest number of ticks for multi axis
+ if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
+ maxTicks = {
+ x: 0,
+ y: 0
+ };
+ }
+
+ if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY]) {
+ maxTicks[xOrY] = tickPositions.length;
+ }
+ }
+
+
+ }
+
+ /**
+ * When using multiple axes, adjust the number of ticks to match the highest
+ * number of ticks in that group
+ */
+ function adjustTickAmount() {
+
+ if (maxTicks && !isDatetimeAxis && !categories && !isLinked) { // only apply to linear scale
+ var oldTickAmount = tickAmount,
+ calculatedTickAmount = tickPositions.length;
+
+ // set the axis-level tickAmount to use below
+ tickAmount = maxTicks[xOrY];
+
+ if (calculatedTickAmount < tickAmount) {
+ while (tickPositions.length < tickAmount) {
+ tickPositions.push( correctFloat(
+ tickPositions[tickPositions.length - 1] + tickInterval
+ ));
+ }
+ transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
+ max = tickPositions[tickPositions.length - 1];
+
+ }
+ if (defined(oldTickAmount) && tickAmount != oldTickAmount) {
+ axis.isDirty = true;
+ }
+ }
+
+ }
+
+ /**
+ * Set the scale based on data min and max, user set min and max or options
+ *
+ */
+ function setScale() {
+ var type,
+ i;
+
+ oldMin = min;
+ oldMax = max;
+
+ // get data extremes if needed
+ getSeriesExtremes();
+
+ // get fixed positions based on tickInterval
+ setTickPositions();
+
+ // the translation factor used in translate function
+ oldTransA = transA;
+ transA = axisLength / ((max - min) || 1);
+
+ // reset stacks
+ if (!isXAxis) {
+ for (type in stacks) {
+ for (i in stacks[type]) {
+ stacks[type][i].cum = stacks[type][i].total;
+ }
+ }
+ }
+
+ // mark as dirty if it is not already set to dirty and extremes have changed
+ if (!axis.isDirty) {
+ axis.isDirty = (min != oldMin || max != oldMax);
+ }
+
+ }
+
+ /**
+ * Set the extremes and optionally redraw
+ * @param {Number} newMin
+ * @param {Number} newMax
+ * @param {Boolean} redraw
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ *
+ */
+ function setExtremes(newMin, newMax, redraw, animation) {
+
+ redraw = pick(redraw, true); // defaults to true
+
+ fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts
+ min: newMin,
+ max: newMax
+ }, function() { // the default event handler
+
+ userSetMin = newMin;
+ userSetMax = newMax;
+
+
+ // redraw
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ });
+
+ }
+
+ /**
+ * Get the actual axis extremes
+ */
+ function getExtremes() {
+ return {
+ min: min,
+ max: max,
+ dataMin: dataMin,
+ dataMax: dataMax
+ };
+ }
+
+ /**
+ * Get the zero plane either based on zero or on the min or max value.
+ * Used in bar and area plots
+ */
+ function getThreshold(threshold) {
+ if (min > threshold) {
+ threshold = min;
+ } else if (max < threshold) {
+ threshold = max;
+ }
+
+ return translate(threshold, 0, 1);
+ }
+
+ /**
+ * Add a plot band or plot line after render time
+ *
+ * @param options {Object} The plotBand or plotLine configuration object
+ */
+ function addPlotBandOrLine(options) {
+ var obj = new PlotLineOrBand(options).render();
+ plotLinesAndBands.push(obj);
+ return obj;
+ }
+
+ /**
+ * Render the tick labels to a preliminary position to get their sizes
+ */
+ function getOffset() {
+
+ var hasData = associatedSeries.length && defined(min) && defined(max),
+ titleOffset = 0,
+ titleMargin = 0,
+ axisTitleOptions = options.title,
+ labelOptions = options.labels,
+ directionFactor = [-1, 1, 1, -1][side];
+
+ if (!axisGroup) {
+ axisGroup = renderer.g('axis')
+ .attr({ zIndex: 7 })
+ .add();
+ gridGroup = renderer.g('grid')
+ .attr({ zIndex: 1 })
+ .add();
+ }
+
+ labelOffset = 0; // reset
+
+ if (hasData || isLinked) {
+ each(tickPositions, function(pos) {
+ if (!ticks[pos]) {
+ ticks[pos] = new Tick(pos);
+ } else {
+ ticks[pos].addLabel(); // update labels depending on tick interval
+ }
+
+ // left side must be align: right and right side must have align: left for labels
+ if (side === 0 || side == 2 || { 1: 'left', 3: 'right' }[side] == labelOptions.align) {
+
+ // get the highest offset
+ labelOffset = mathMax(
+ ticks[pos].getLabelSize(),
+ labelOffset
+ );
+ }
+
+ });
+
+ if (staggerLines) {
+ labelOffset += (staggerLines - 1) * 16;
+ }
+
+ } else { // doesn't have data
+ for (var n in ticks) {
+ ticks[n].destroy();
+ delete ticks[n];
+ }
+ }
+
+ if (axisTitleOptions && axisTitleOptions.text) {
+ if (!axis.axisTitle) {
+ axis.axisTitle = renderer.text(
+ axisTitleOptions.text,
+ 0,
+ 0
+ )
+ .attr({
+ zIndex: 7,
+ rotation: axisTitleOptions.rotation || 0,
+ align:
+ axisTitleOptions.textAlign ||
+ { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
+ })
+ .css(axisTitleOptions.style)
+ .add();
+ }
+
+ titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
+ titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
+
+ }
+
+ // handle automatic or user set offset
+ offset = directionFactor * (options.offset || axisOffset[side]);
+
+ axisTitleMargin =
+ labelOffset +
+ (side != 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) +
+ titleMargin;
+
+ axisOffset[side] = mathMax(
+ axisOffset[side],
+ axisTitleMargin + titleOffset + directionFactor * offset
+ );
+
+ }
+
+ /**
+ * Render the axis
+ */
+ function render() {
+ var axisTitleOptions = options.title,
+ alternateGridColor = options.alternateGridColor,
+ lineWidth = options.lineWidth,
+ lineLeft,
+ lineTop,
+ linePath,
+ hasRendered = chart.hasRendered,
+ slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
+ hasData = associatedSeries.length && defined(min) && defined(max);
+
+ // update metrics
+ axisLength = horiz ? plotWidth : plotHeight;
+ transA = axisLength / ((max - min) || 1);
+ transB = horiz ? plotLeft : marginBottom; // translation addend
+
+ // If the series has data draw the ticks. Else only the line and title
+ if (hasData || isLinked) {
+
+ // minor ticks
+ if (minorTickInterval && !categories) {
+ var pos = min + (tickPositions[0] - min) % minorTickInterval;
+ for (pos; pos <= max; pos += minorTickInterval) {
+ if (!minorTicks[pos]) {
+ minorTicks[pos] = new Tick(pos, true);
+ }
+
+ // render new ticks in old position
+ if (slideInTicks && minorTicks[pos].isNew) {
+ minorTicks[pos].render(null, true);
+ }
+
+
+ minorTicks[pos].isActive = true;
+ minorTicks[pos].render();
+ }
+ }
+
+ // major ticks
+ each(tickPositions, function(pos, i) {
+ // linked axes need an extra check to find out if
+ if (!isLinked || (pos >= min && pos <= max)) {
+
+ // render new ticks in old position
+ if (slideInTicks && ticks[pos].isNew) {
+ ticks[pos].render(i, true);
+ }
+
+ ticks[pos].isActive = true;
+ ticks[pos].render(i);
+ }
+ });
+
+ // alternate grid color
+ if (alternateGridColor) {
+ each(tickPositions, function(pos, i) {
+ if (i % 2 === 0 && pos < max) {
+ /*plotLinesAndBands.push(new PlotLineOrBand({
+ from: pos,
+ to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
+ color: alternateGridColor
+ }));*/
+
+ if (!alternateBands[pos]) {
+ alternateBands[pos] = new PlotLineOrBand();
+ }
+ alternateBands[pos].options = {
+ from: pos,
+ to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
+ color: alternateGridColor
+ };
+ alternateBands[pos].render();
+ alternateBands[pos].isActive = true;
+ }
+ });
+ }
+
+ // custom plot bands (behind grid lines)
+ /*if (!hasRendered) { // only first time
+ each(options.plotBands || [], function(plotBandOptions) {
+ plotLinesAndBands.push(new PlotLineOrBand(
+ extend({ zIndex: 1 }, plotBandOptions)
+ ).render());
+ });
+ }*/
+
+
+
+
+ // custom plot lines and bands
+ if (!hasRendered) { // only first time
+ each((options.plotLines || []).concat(options.plotBands || []), function(plotLineOptions) {
+ plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
+ });
+ }
+
+
+
+ } // end if hasData
+
+ // remove inactive ticks
+ each([ticks, minorTicks, alternateBands], function(coll) {
+ for (var pos in coll) {
+ if (!coll[pos].isActive) {
+ coll[pos].destroy();
+ delete coll[pos];
+ } else {
+ coll[pos].isActive = false; // reset
+ }
+ }
+ });
+
+
+
+
+ // Static items. As the axis group is cleared on subsequent calls
+ // to render, these items are added outside the group.
+ // axis line
+ if (lineWidth) {
+ lineLeft = plotLeft + (opposite ? plotWidth : 0) + offset;
+ lineTop = chartHeight - marginBottom - (opposite ? plotHeight : 0) + offset;
+
+ linePath = renderer.crispLine([
+ M,
+ horiz ?
+ plotLeft:
+ lineLeft,
+ horiz ?
+ lineTop:
+ plotTop,
+ L,
+ horiz ?
+ chartWidth - marginRight :
+ lineLeft,
+ horiz ?
+ lineTop:
+ chartHeight - marginBottom
+ ], lineWidth);
+ if (!axisLine) {
+ axisLine = renderer.path(linePath)
+ .attr({
+ stroke: options.lineColor,
+ 'stroke-width': lineWidth,
+ zIndex: 7
+ })
+ .add();
+ } else {
+ axisLine.animate({ d: linePath });
+ }
+
+ }
+
+ if (axis.axisTitle) {
+ // compute anchor points for each of the title align options
+ var margin = horiz ? plotLeft : plotTop,
+ fontSize = pInt(axisTitleOptions.style.fontSize || 12),
+ // the position in the length direction of the axis
+ alongAxis = {
+ low: margin + (horiz ? 0 : axisLength),
+ middle: margin + axisLength / 2,
+ high: margin + (horiz ? axisLength : 0)
+ }[axisTitleOptions.align],
+
+ // the position in the perpendicular direction of the axis
+ offAxis = (horiz ? plotTop + plotHeight : plotLeft) +
+ (horiz ? 1 : -1) * // horizontal axis reverses the margin
+ (opposite ? -1 : 1) * // so does opposite axes
+ axisTitleMargin +
+ //(isIE ? fontSize / 3 : 0)+ // preliminary fix for vml's centerline
+ (side == 2 ? fontSize : 0);
+
+ axis.axisTitle[hasRendered ? 'animate' : 'attr']({
+ x: horiz ?
+ alongAxis:
+ offAxis + (opposite ? plotWidth : 0) + offset +
+ (axisTitleOptions.x || 0), // x
+ y: horiz ?
+ offAxis - (opposite ? plotHeight : 0) + offset:
+ alongAxis + (axisTitleOptions.y || 0) // y
+ });
+
+ }
+
+ axis.isDirty = false;
+ }
+
+ /**
+ * Remove a plot band or plot line from the chart by id
+ * @param {Object} id
+ */
+ function removePlotBandOrLine(id) {
+ var i = plotLinesAndBands.length;
+ while (i--) {
+ if (plotLinesAndBands[i].id == id) {
+ plotLinesAndBands[i].destroy();
+ }
+ }
+ }
+
+ /**
+ * Redraw the axis to reflect changes in the data or axis extremes
+ */
+ function redraw() {
+
+ // hide tooltip and hover states
+ if (tracker.resetTracker) {
+ tracker.resetTracker();
+ }
+
+ // render the axis
+ render();
+
+ // move plot lines and bands
+ each(plotLinesAndBands, function(plotLine) {
+ plotLine.render();
+ });
+
+ // mark associated series as dirty and ready for redraw
+ each(associatedSeries, function(series) {
+ series.isDirty = true;
+ });
+
+ }
+
+ /**
+ * Set new axis categories and optionally redraw
+ * @param {Array} newCategories
+ * @param {Boolean} doRedraw
+ */
+ function setCategories(newCategories, doRedraw) {
+ // set the categories
+ axis.categories = categories = newCategories;
+
+ // force reindexing tooltips
+ each(associatedSeries, function(series) {
+ series.translate();
+ series.setTooltipPoints(true);
+ });
+
+
+ // optionally redraw
+ axis.isDirty = true;
+
+ if (pick(doRedraw, true)) {
+ chart.redraw();
+ }
+ }
+
+
+
+ // Run Axis
+
+ // inverted charts have reversed xAxes as default
+ if (inverted && isXAxis && reversed === UNDEFINED) {
+ reversed = true;
+ }
+
+
+ // expose some variables
+ extend(axis, {
+ addPlotBand: addPlotBandOrLine,
+ addPlotLine: addPlotBandOrLine,
+ adjustTickAmount: adjustTickAmount,
+ categories: categories,
+ getExtremes: getExtremes,
+ getPlotLinePath: getPlotLinePath,
+ getThreshold: getThreshold,
+ isXAxis: isXAxis,
+ options: options,
+ plotLinesAndBands: plotLinesAndBands,
+ getOffset: getOffset,
+ render: render,
+ setCategories: setCategories,
+ setExtremes: setExtremes,
+ setScale: setScale,
+ setTickPositions: setTickPositions,
+ translate: translate,
+ redraw: redraw,
+ removePlotBand: removePlotBandOrLine,
+ removePlotLine: removePlotBandOrLine,
+ reversed: reversed,
+ stacks: stacks
+ });
+
+ // register event listeners
+ for (eventType in events) {
+ addEvent(axis, eventType, events[eventType]);
+ }
+
+ // set min and max
+ setScale();
+
+ } // end Axis
+
+
+ /**
+ * The toolbar object
+ *
+ * @param {Object} chart
+ */
+ function Toolbar(chart) {
+ var buttons = {};
+
+ function add(id, text, title, fn) {
+ if (!buttons[id]) {
+ var button = renderer.text(
+ text,
+ 0,
+ 0
+ )
+ .css(options.toolbar.itemStyle)
+ .align({
+ align: 'right',
+ x: - marginRight - 20,
+ y: plotTop + 30
+ })
+ .on('click', fn)
+ /*.on('touchstart', function(e) {
+ e.stopPropagation(); // don't fire the container event
+ fn();
+ })*/
+ .attr({
+ align: 'right',
+ zIndex: 20
+ })
+ .add();
+ buttons[id] = button;
+ }
+ }
+ function remove(id) {
+ discardElement(buttons[id].element);
+ buttons[id] = null;
+ }
+
+ // public
+ return {
+ add: add,
+ remove: remove
+ };
+ }
+
+ /**
+ * The tooltip object
+ * @param {Object} options Tooltip options
+ */
+ function Tooltip (options) {
+ var currentSeries,
+ borderWidth = options.borderWidth,
+ crosshairsOptions = options.crosshairs,
+ crosshairs = [],
+ style = options.style,
+ shared = options.shared,
+ padding = pInt(style.padding),
+ boxOffLeft = borderWidth + padding, // off left/top position as IE can't
+ //properly handle negative positioned shapes
+ tooltipIsHidden = true,
+ boxWidth,
+ boxHeight,
+ currentX = 0,
+ currentY = 0;
+
+ // remove padding CSS and apply padding on box instead
+ style.padding = 0;
+
+ // create the elements
+ var group = renderer.g('tooltip')
+ .attr({ zIndex: 8 })
+ .add(),
+
+ box = renderer.rect(boxOffLeft, boxOffLeft, 0, 0, options.borderRadius, borderWidth)
+ .attr({
+ fill: options.backgroundColor,
+ 'stroke-width': borderWidth
+ })
+ .add(group)
+ .shadow(options.shadow),
+ label = renderer.text('', padding + boxOffLeft, pInt(style.fontSize) + padding + boxOffLeft)
+ .attr({ zIndex: 1 })
+ .css(style)
+ .add(group);
+
+ group.hide();
+
+ /**
+ * In case no user defined formatter is given, this will be used
+ */
+ function defaultFormatter() {
+ var pThis = this,
+ items = pThis.points || splat(pThis),
+ xAxis = items[0].series.xAxis,
+ x = pThis.x,
+ isDateTime = xAxis && xAxis.options.type == 'datetime',
+ useHeader = isString(x) || isDateTime,
+ series,
+ s;
+
+ // build the header
+ s = useHeader ?
+ ['<span style="font-size: 10px">',
+ (isDateTime ? dateFormat('%A, %b %e, %Y', x) : x),
+ '</span><br/>'] : [];
+
+ // build the values
+ each(items, function(item) {
+ s.push(item.point.tooltipFormatter(useHeader));
+ });
+ return s.join('');
+ }
+
+ /**
+ * Provide a soft movement for the tooltip
+ *
+ * @param {Number} finalX
+ * @param {Number} finalY
+ */
+ function move(finalX, finalY) {
+
+ currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
+ currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;
+
+ group.translate(currentX, currentY);
+
+
+ // run on next tick of the mouse tracker
+ if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
+ tooltipTick = function() {
+ move(finalX, finalY);
+ };
+ } else {
+ tooltipTick = null;
+ }
+ }
+
+ /**
+ * Hide the tooltip
+ */
+ function hide() {
+ if (!tooltipIsHidden) {
+ var hoverPoints = chart.hoverPoints;
+
+ group.hide();
+
+ each(crosshairs, function(crosshair) {
+ if (crosshair) {
+ crosshair.hide();
+ }
+ });
+
+ // hide previous hoverPoints and set new
+ if (hoverPoints) {
+ each (hoverPoints, function(point) {
+ point.setState();
+ });
+ }
+ chart.hoverPoints = null;
+
+
+ tooltipIsHidden = true;
+ }
+
+ }
+
+ /**
+ * Refresh the tooltip's text and position.
+ * @param {Object} point
+ *
+ */
+ function refresh(point) {
+ var x,
+ y,
+ boxX,
+ boxY,
+ show,
+ bBox,
+ plotX,
+ plotY = 0,
+ textConfig = {},
+ text,
+ pointConfig = [],
+ tooltipPos = point.tooltipPos,
+ formatter = options.formatter || defaultFormatter,
+ hoverPoints = chart.hoverPoints,
+ getConfig = function(point) {
+ return {
+ series: point.series,
+ point: point,
+ x: point.category,
+ y: point.y,
+ percentage: point.percentage,
+ total: point.total || point.stackTotal
+ };
+ };
+
+ // shared tooltip, array is sent over
+ if (shared) {
+
+ // hide previous hoverPoints and set new
+ if (hoverPoints) {
+ each (hoverPoints, function(point) {
+ point.setState();
+ });
+ }
+ chart.hoverPoints = point;
+
+ each(point, function(item, i) {
+ /*var series = item.series,
+ hoverPoint = series.hoverPoint;
+ if (hoverPoint) {
+ hoverPoint.setState();
+ }
+ series.hoverPoint = item;*/
+ item.setState(HOVER_STATE);
+ plotY += item.plotY; // for average
+
+ pointConfig.push(getConfig(item));
+ });
+
+ plotX = point[0].plotX;
+ plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here
+
+ textConfig = {
+ x: point[0].category
+ };
+ textConfig.points = pointConfig;
+ point = point[0];
+
+ // single point tooltip
+ } else {
+ textConfig = getConfig(point);
+ }
+ text = formatter.call(textConfig);
+
+ // register the current series
+ currentSeries = point.series;
+
+ // get the reference point coordinates (pie charts use tooltipPos)
+ plotX = shared ? plotX : point.plotX;
+ plotY = shared ? plotY : point.plotY;
+ x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
+ y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));
+
+
+ // hide tooltip if the point falls outside the plot
+ show = shared || !point.series.isCartesian || isInsidePlot(x, y);
+
+ // update the inner HTML
+ if (text === false || !show) {
+ hide();
+ } else {
+
+ // show it
+ if (tooltipIsHidden) {
+ group.show();
+ tooltipIsHidden = false;
+ }
+
+ // update text
+ label.attr({
+ text: text
+ });
+
+ // get the bounding box
+ bBox = label.getBBox();
+ boxWidth = bBox.width + 2 * padding;
+ boxHeight = bBox.height + 2 * padding;
+
+ // set the size of the box
+ box.attr({
+ width: boxWidth,
+ height: boxHeight,
+ stroke: options.borderColor || point.color || currentSeries.color || '#606060'
+ });
+
+ // keep the box within the chart area
+ boxX = x - boxWidth + plotLeft - 25;
+ boxY = y - boxHeight + plotTop + 10;
+
+ // it is too far to the left, adjust it
+ if (boxX < 7) {
+ boxX = 7;
+ boxY -= 30;
+ }
+
+
+ if (boxY < 5) {
+ boxY = 5; // above
+ } else if (boxY + boxHeight > chartHeight) {
+ boxY = chartHeight - boxHeight - 5; // below
+ }
+
+ // do the move
+ move(mathRound(boxX - boxOffLeft), mathRound(boxY - boxOffLeft));
+
+
+ }
+
+
+ // crosshairs
+ if (crosshairsOptions) {
+ crosshairsOptions = splat(crosshairsOptions); // [x, y]
+
+ var path,
+ i = crosshairsOptions.length,
+ attribs,
+ axis;
+
+ while (i--) {
+ if (crosshairsOptions[i] && (axis = point.series[i ? 'yAxis' : 'xAxis'])) {
+ path = axis
+ .getPlotLinePath(point[i ? 'y' : 'x'], 1);
+ if (crosshairs[i]) {
+ crosshairs[i].attr({ d: path, visibility: VISIBLE });
+
+ } else {
+ attribs = {
+ 'stroke-width': crosshairsOptions[i].width || 1,
+ stroke: crosshairsOptions[i].color || '#C0C0C0',
+ zIndex: 2
+ };
+ if (crosshairsOptions[i].dashStyle) {
+ attribs.dashstyle = crosshairsOptions[i].dashStyle;
+ }
+ crosshairs[i] = renderer.path(path)
+ .attr(attribs)
+ .add();
+ }
+ }
+ }
+ }
+ }
+
+
+
+ // public members
+ return {
+ shared: shared,
+ refresh: refresh,
+ hide: hide
+ };
+ }
+
+ /**
+ * The mouse tracker object
+ * @param {Object} chart
+ * @param {Object} options
+ */
+ function MouseTracker (chart, options) {
+
+
+ var mouseDownX,
+ mouseDownY,
+ hasDragged,
+ selectionMarker,
+ zoomType = optionsChart.zoomType,
+ zoomX = /x/.test(zoomType),
+ zoomY = /y/.test(zoomType),
+ zoomHor = zoomX && !inverted || zoomY && inverted,
+ zoomVert = zoomY && !inverted || zoomX && inverted;
+
+ /**
+ * Add crossbrowser support for chartX and chartY
+ * @param {Object} e The event object in standard browsers
+ */
+ function normalizeMouseEvent(e) {
+ var ePos;
+
+ // common IE normalizing
+ e = e || win.event;
+ if (!e.target) {
+ e.target = e.srcElement;
+ }
+
+ // iOS
+ ePos = e.touches ? e.touches.item(0) : e;
+
+ // in certain cases, get mouse position
+ if (e.type != 'mousemove' || win.opera) { // only Opera needs position on mouse move, see below
+ chartPosition = getPosition(container);
+ }
+
+ // chartX and chartY
+ if (isIE) { // IE including IE9 that has chartX but in a different meaning
+ e.chartX = e.x;
+ e.chartY = e.y;
+ } else {
+ if (ePos.layerX === UNDEFINED) { // Opera and iOS
+ e.chartX = ePos.pageX - chartPosition.left;
+ e.chartY = ePos.pageY - chartPosition.top;
+ } else {
+ e.chartX = e.layerX;
+ e.chartY = e.layerY;
+ }
+ }
+
+ return e;
+ }
+
+ /**
+ * Get the click position in terms of axis values.
+ *
+ * @param {Object} e A mouse event
+ */
+ function getMouseCoordinates(e) {
+ var coordinates = {
+ xAxis: [],
+ yAxis: []
+ };
+ each(axes, function(axis, i) {
+ var translate = axis.translate,
+ isXAxis = axis.isXAxis,
+ isHorizontal = inverted ? !isXAxis : isXAxis;
+
+ coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
+ axis: axis,
+ value: translate(
+ isHorizontal ?
+ e.chartX - plotLeft :
+ plotHeight - e.chartY + plotTop,
+ true
+ )
+ });
+ });
+ return coordinates;
+ }
+
+ /**
+ * With line type charts with a single tracker, get the point closest to the mouse
+ */
+ function onmousemove (e) {
+ var point,
+ points,
+ hoverPoint = chart.hoverPoint,
+ hoverSeries = chart.hoverSeries,
+ i,
+ j,
+ distance = chartWidth,
+ index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?
+
+ // shared tooltip
+ if (tooltip && options.shared) {
+ points = [];
+
+ // loop over all series and find the ones with points closest to the mouse
+ i = series.length;
+ for (j = 0; j < i; j++) {
+ if (series[j].visible && series[j].tooltipPoints.length) {
+ point = series[j].tooltipPoints[index];
+ point._dist = mathAbs(index - point.plotX);
+ distance = mathMin(distance, point._dist);
+ points.push(point);
+ }
+ }
+ // remove furthest points
+ i = points.length;
+ while (i--) {
+ if (points[i]._dist > distance) {
+ points.splice(i, 1);
+ }
+ }
+ // refresh the tooltip if necessary
+ if (points.length && (points[0].plotX != hoverX)) {
+ tooltip.refresh(points);
+ hoverX = points[0].plotX;
+ }
+ }
+
+ // separate tooltip and general mouse events
+ if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
+
+ // get the point
+ point = hoverSeries.tooltipPoints[index];
+
+ // a new point is hovered, refresh the tooltip
+ if (point && point != hoverPoint) {
+
+ // trigger the events
+ point.onMouseOver();
+
+ }
+ }
+ }
+
+
+
+ /**
+ * Reset the tracking by hiding the tooltip, the hover series state and the hover point
+ */
+ function resetTracker() {
+ var hoverSeries = chart.hoverSeries,
+ hoverPoint = chart.hoverPoint;
+
+ if (hoverPoint) {
+ hoverPoint.onMouseOut();
+ }
+
+ if (hoverSeries) {
+ hoverSeries.onMouseOut();
+ }
+
+ if (tooltip) {
+ tooltip.hide();
+ }
+
+ hoverX = null;
+ }
+
+ /**
+ * Mouse up or outside the plot area
+ */
+ function drop() {
+ if (selectionMarker) {
+ var selectionData = {
+ xAxis: [],
+ yAxis: []
+ },
+ selectionBox = selectionMarker.getBBox(),
+ selectionLeft = selectionBox.x - plotLeft,
+ selectionTop = selectionBox.y - plotTop;
+
+
+ // a selection has been made
+ if (hasDragged) {
+
+ // record each axis' min and max
+ each(axes, function(axis, i) {
+ var translate = axis.translate,
+ isXAxis = axis.isXAxis,
+ isHorizontal = inverted ? !isXAxis : isXAxis,
+ selectionMin = translate(
+ isHorizontal ?
+ selectionLeft :
+ plotHeight - selectionTop - selectionBox.height,
+ true
+ ),
+ selectionMax = translate(
+ isHorizontal ?
+ selectionLeft + selectionBox.width :
+ plotHeight - selectionTop,
+ true
+ );
+
+ selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
+ axis: axis,
+ min: mathMin(selectionMin, selectionMax), // for reversed axes
+ max: mathMax(selectionMin, selectionMax)
+ });
+
+ });
+ fireEvent(chart, 'selection', selectionData, zoom);
+
+ }
+ selectionMarker = selectionMarker.destroy();
+ }
+
+ chart.mouseIsDown = mouseIsDown = hasDragged = false;
+ removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
+
+ }
+
+ /**
+ * Set the JS events on the container element
+ */
+ function setDOMEvents () {
+ var lastWasOutsidePlot = true;
+
+ /*
+ * Record the starting position of a dragoperation
+ */
+ container.onmousedown = function(e) {
+ e = normalizeMouseEvent(e);
+
+ // record the start position
+ //e.preventDefault && e.preventDefault();
+
+ chart.mouseIsDown = mouseIsDown = true;
+ mouseDownX = e.chartX;
+ mouseDownY = e.chartY;
+
+ addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
+ };
+
+ // The mousemove, touchmove and touchstart event handler
+ var mouseMove = function(e) {
+
+ // let the system handle multitouch operations like two finger scroll
+ // and pinching
+ if (e && e.touches && e.touches.length > 1) {
+ return;
+ }
+
+ // normalize
+ e = normalizeMouseEvent(e);
+ if (!hasTouch) { // not for touch devices
+ e.returnValue = false;
+ }
+
+ var chartX = e.chartX,
+ chartY = e.chartY,
+ isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);
+
+ // on touch devices, only trigger click if a handler is defined
+ if (hasTouch && e.type == 'touchstart') {
+ if (attr(e.target, 'isTracker')) {
+ if (!chart.runTrackerClick) {
+ e.preventDefault();
+ }
+ } else if (!runChartClick && !isOutsidePlot) {
+ e.preventDefault();
+ }
+ }
+
+ // cancel on mouse outside
+ if (isOutsidePlot) {
+
+ if (!lastWasOutsidePlot) {
+ // reset the tracker
+ resetTracker();
+ }
+
+ // drop the selection if any and reset mouseIsDown and hasDragged
+ //drop();
+ if (chartX < plotLeft) {
+ chartX = plotLeft;
+ } else if (chartX > plotLeft + plotWidth) {
+ chartX = plotLeft + plotWidth;
+ }
+
+ if (chartY < plotTop) {
+ chartY = plotTop;
+ } else if (chartY > plotTop + plotHeight) {
+ chartY = plotTop + plotHeight;
+ }
+
+ }
+
+ if (mouseIsDown && e.type != 'touchstart') { // make selection
+
+ // determine if the mouse has moved more than 10px
+ if ((hasDragged = Math.sqrt(
+ Math.pow(mouseDownX - chartX, 2) +
+ Math.pow(mouseDownY - chartY, 2)
+ ) > 10)) {
+
+ // make a selection
+ if (hasCartesianSeries && (zoomX || zoomY) &&
+ isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop)) {
+ if (!selectionMarker) {
+ selectionMarker = renderer.rect(
+ plotLeft,
+ plotTop,
+ zoomHor ? 1 : plotWidth,
+ zoomVert ? 1 : plotHeight,
+ 0
+ )
+ .attr({
+ fill: 'rgba(69,114,167,0.25)',
+ zIndex: 7
+ })
+ .add();
+ }
+ }
+
+ // adjust the width of the selection marker
+ if (selectionMarker && zoomHor) {
+ var xSize = chartX - mouseDownX;
+ selectionMarker.attr({
+ width: mathAbs(xSize),
+ x: (xSize > 0 ? 0 : xSize) + mouseDownX
+ });
+ }
+ // adjust the height of the selection marker
+ if (selectionMarker && zoomVert) {
+ var ySize = chartY - mouseDownY;
+ selectionMarker.attr({
+ height: mathAbs(ySize),
+ y: (ySize > 0 ? 0 : ySize) + mouseDownY
+ });
+ }
+ }
+
+ } else if (!isOutsidePlot) {
+ // show the tooltip
+ onmousemove(e);
+ }
+
+ lastWasOutsidePlot = isOutsidePlot;
+
+ // when outside plot, allow touch-drag by returning true
+ return isOutsidePlot || !hasCartesianSeries;
+ };
+
+ /*
+ * When the mouse enters the container, run mouseMove
+ */
+ container.onmousemove = mouseMove;
+
+ /*
+ * When the mouse leaves the container, hide the tracking (tooltip).
+ */
+ addEvent(container, 'mouseleave', resetTracker);
+
+
+ container.ontouchstart = function(e) {
+ // For touch devices, use touchmove to zoom
+ if (zoomX || zoomY) {
+ container.onmousedown(e);
+ }
+ // Show tooltip and prevent the lower mouse pseudo event
+ mouseMove(e);
+ };
+
+ /*
+ * Allow dragging the finger over the chart to read the values on touch
+ * devices
+ */
+ container.ontouchmove = mouseMove;
+
+ /*
+ * Allow dragging the finger over the chart to read the values on touch
+ * devices
+ */
+ container.ontouchend = function() {
+ if (hasDragged) {
+ resetTracker();
+ }
+ };
+
+
+ // MooTools 1.2.3 doesn't fire this in IE when using addEvent
+ container.onclick = function(e) {
+ var hoverPoint = chart.hoverPoint;
+ e = normalizeMouseEvent(e);
+
+ e.cancelBubble = true; // IE specific
+
+
+ if (!hasDragged) {
+ if (hoverPoint && attr(e.target, 'isTracker')) {
+ var plotX = hoverPoint.plotX,
+ plotY = hoverPoint.plotY;
+
+ // add page position info
+ extend(hoverPoint, {
+ pageX: chartPosition.left + plotLeft +
+ (inverted ? plotWidth - plotY : plotX),
+ pageY: chartPosition.top + plotTop +
+ (inverted ? plotHeight - plotX : plotY)
+ });
+
+ // the series click event
+ fireEvent(hoverPoint.series, 'click', extend(e, {
+ point: hoverPoint
+ }));
+
+ // the point click event
+ hoverPoint.firePointEvent('click', e);
+
+ } else {
+ extend(e, getMouseCoordinates(e));
+
+ // fire a click event in the chart
+ if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
+ fireEvent(chart, 'click', e);
+ }
+ }
+
+
+ }
+ // reset mouseIsDown and hasDragged
+ hasDragged = false;
+ };
+
+ }
+
+ /**
+ * Create the image map that listens for mouseovers
+ */
+ placeTrackerGroup = function() {
+
+ // first create - plot positions is not final at this stage
+ if (!trackerGroup) {
+ chart.trackerGroup = trackerGroup = renderer.g('tracker')
+ .attr({ zIndex: 9 })
+ .add();
+
+ // then position - this happens on load and after resizing and changing
+ // axis or box positions
+ } else {
+ trackerGroup.translate(plotLeft, plotTop);
+ if (inverted) {
+ trackerGroup.attr({
+ width: chart.plotWidth,
+ height: chart.plotHeight
+ }).invert();
+ }
+ }
+ };
+
+
+ // Run MouseTracker
+ placeTrackerGroup();
+ if (options.enabled) {
+ chart.tooltip = tooltip = Tooltip(options);
+ }
+
+ setDOMEvents();
+
+ // set the fixed interval ticking for the smooth tooltip
+ tooltipInterval = setInterval(function() {
+ if (tooltipTick) {
+ tooltipTick();
+ }
+ }, 32);
+
+ // expose properties
+ extend(this, {
+ zoomX: zoomX,
+ zoomY: zoomY,
+ resetTracker: resetTracker
+ });
+ }
+
+
+
+ /**
+ * The overview of the chart's series
+ * @param {Object} chart
+ */
+ var Legend = function(chart) {
+
+ var options = chart.options.legend;
+
+ if (!options.enabled) {
+ return;
+ }
+
+ var horizontal = options.layout == 'horizontal',
+ symbolWidth = options.symbolWidth,
+ symbolPadding = options.symbolPadding,
+ allItems,
+ style = options.style,
+ itemStyle = options.itemStyle,
+ itemHoverStyle = options.itemHoverStyle,
+ itemHiddenStyle = options.itemHiddenStyle,
+ padding = pInt(style.padding),
+ rightPadding = 20,
+ //lineHeight = options.lineHeight || 16,
+ y = 18,
+ initialItemX = 4 + padding + symbolWidth + symbolPadding,
+ itemX,
+ itemY,
+ lastItemY,
+ itemHeight = 0,
+ box,
+ legendBorderWidth = options.borderWidth,
+ legendBackgroundColor = options.backgroundColor,
+ legendGroup,
+ offsetWidth,
+ widthOption = options.width,
+ series = chart.series,
+ reversedLegend = options.reversed;
+
+
+
+ /**
+ * Set the colors for the legend item
+ * @param {Object} item A Series or Point instance
+ * @param {Object} visible Dimmed or colored
+ */
+ function colorizeItem(item, visible) {
+ var legendItem = item.legendItem,
+ legendLine = item.legendLine,
+ legendSymbol = item.legendSymbol,
+ hiddenColor = itemHiddenStyle.color,
+ textColor = visible ? options.itemStyle.color : hiddenColor,
+ symbolColor = visible ? item.color : hiddenColor;
+ if (legendItem) {
+ legendItem.css({ fill: textColor });
+ }
+ if (legendLine) {
+ legendLine.attr({ stroke: symbolColor });
+ }
+ if (legendSymbol) {
+ legendSymbol.attr({
+ stroke: symbolColor,
+ fill: symbolColor
+ });
+ }
+ }
+
+ /**
+ * Position the legend item
+ * @param {Object} item A Series or Point instance
+ * @param {Object} visible Dimmed or colored
+ */
+ function positionItem(item, itemX, itemY) {
+ var legendItem = item.legendItem,
+ legendLine = item.legendLine,
+ legendSymbol = item.legendSymbol,
+ checkbox = item.checkbox;
+ if (legendItem) {
+ legendItem.attr({
+ x: itemX,
+ y: itemY
+ });
+ }
+ if (legendLine) {
+ legendLine.translate(itemX, itemY - 4);
+ }
+ if (legendSymbol) {
+ legendSymbol.attr({
+ x: itemX + legendSymbol.xOff,
+ y: itemY + legendSymbol.yOff
+ });
+ }
+ if (checkbox) {
+ checkbox.x = itemX;
+ checkbox.y = itemY;
+ }
+ }
+
+ /**
+ * Destroy a single legend item
+ * @param {Object} item The series or point
+ */
+ function destroyItem(item) {
+ var checkbox = item.checkbox;
+
+ // pull out from the array
+ //erase(allItems, item);
+
+ // destroy SVG elements
+ each(['legendItem', 'legendLine', 'legendSymbol'], function(key) {
+ if (item[key]) {
+ item[key].destroy();
+ }
+ });
+
+ if (checkbox) {
+ discardElement(item.checkbox);
+ }
+
+
+ }
+
+
+ /**
+ * Position the checkboxes after the width is determined
+ */
+ function positionCheckboxes() {
+ each(allItems, function(item) {
+ var checkbox = item.checkbox;
+ if (checkbox) {
+ css(checkbox, {
+ left: (legendGroup.attr('translateX') + item.legendItemWidth + checkbox.x - 40) +PX,
+ top: (legendGroup.attr('translateY') + checkbox.y - 11) + PX
+ });
+ }
+ });
+ }
+
+ /**
+ * Render a single specific legend item
+ * @param {Object} item A series or point
+ */
+ function renderItem(item) {
+ var bBox,
+ itemWidth,
+ legendSymbol,
+ symbolX,
+ symbolY,
+ attribs,
+ simpleSymbol,
+ li = item.legendItem,
+ series = item.series || item,
+ i = allItems.length;
+
+
+ if (!li) { // generate it once, later move it
+
+ // let these series types use a simple symbol
+ simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);
+
+ // generate the list item text
+ item.legendItem = li = renderer.text(
+ options.labelFormatter.call(item),
+ 0,
+ 0
+ )
+ .css(item.visible ? itemStyle : itemHiddenStyle)
+ .on('mouseover', function() {
+ item.setState(HOVER_STATE);
+ li.css(itemHoverStyle);
+ })
+ .on('mouseout', function() {
+ li.css(item.visible ? itemStyle : itemHiddenStyle);
+ item.setState();
+ })
+ .on('click', function(event) {
+ var strLegendItemClick = 'legendItemClick',
+ fnLegendItemClick = function() {
+ item.setVisible();
+ };
+
+ // click the name or symbol
+ if (item.firePointEvent) { // point
+ item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
+ } else {
+ fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
+ }
+ })
+ .attr({ zIndex: 2 })
+ .add(legendGroup);
+
+ // draw the line
+ if (!simpleSymbol && item.options && item.options.lineWidth) {
+ var itemOptions = item.options;
+ attribs = {
+ 'stroke-width': itemOptions.lineWidth,
+ zIndex: 2
+ };
+ if (itemOptions.dashStyle) {
+ attribs.dashstyle = itemOptions.dashStyle;
+ }
+ item.legendLine = renderer.path([
+ M,
+ -symbolWidth - symbolPadding,
+ 0,
+ L,
+ -symbolPadding,
+ 0
+ ])
+ .attr(attribs)
+ .add(legendGroup);
+ }
+
+ // draw a simple symbol
+ if (simpleSymbol) { // bar|pie|area|column
+ //legendLayer.drawRect(
+ legendSymbol = renderer.rect(
+ (symbolX = -symbolWidth - symbolPadding),
+ (symbolY = -11),
+ symbolWidth,
+ 12,
+ 2
+ ).attr({
+ 'stroke-width': 0,
+ zIndex: 3
+ }).add(legendGroup);
+ }
+
+ // draw the marker
+ else if (item.options && item.options.marker && item.options.marker.enabled) {
+ legendSymbol = renderer.symbol(
+ item.symbol,
+ (symbolX = -symbolWidth / 2 - symbolPadding),
+ (symbolY = -4),
+ item.options.marker.radius
+ )
+ .attr(item.pointAttr[NORMAL_STATE])
+ .attr({ zIndex: 3 })
+ .add(legendGroup);
+
+
+ }
+ if (legendSymbol) {
+ legendSymbol.xOff = symbolX;
+ legendSymbol.yOff = symbolY;
+ }
+
+ item.legendSymbol = legendSymbol;
+
+ // colorize the items
+ colorizeItem(item, item.visible);
+
+
+ // add the HTML checkbox on top
+ if (item.options && item.options.showCheckbox) {
+ item.checkbox = createElement('input', {
+ type: 'checkbox',
+ checked: item.selected,
+ defaultChecked: item.selected // required by IE7
+ }, options.itemCheckboxStyle, container);
+
+ addEvent(item.checkbox, 'click', function(event) {
+ var target = event.target;
+ fireEvent(item, 'checkboxClick', {
+ checked: target.checked
+ },
+ function() {
+ item.select();
+ }
+ );
+ });
+ }
+ }
+
+
+ // calculate the positions for the next line
+ bBox = li.getBBox();
+
+ itemWidth = item.legendItemWidth =
+ options.itemWidth || symbolWidth + symbolPadding + bBox.width + rightPadding;
+ itemHeight = bBox.height;
+
+ // if the item exceeds the width, start a new line
+ if (horizontal && itemX - initialItemX + itemWidth >
+ (widthOption || (chartWidth - 2 * padding - initialItemX))) {
+ itemX = initialItemX;
+ itemY += itemHeight;
+ }
+ lastItemY = itemY;
+
+ // position the newly generated or reordered items
+ positionItem(item, itemX, itemY);
+
+ // advance
+ if (horizontal) {
+ itemX += itemWidth;
+ } else {
+ itemY += itemHeight;
+ }
+
+ // the width of the widest item
+ offsetWidth = widthOption || mathMax(
+ horizontal ? itemX - initialItemX : itemWidth,
+ offsetWidth
+ );
+
+
+
+ // add it all to an array to use below
+ allItems.push(item);
+ }
+
+ /**
+ * Render the legend. This method can be called both before and after
+ * chart.render. If called after, it will only rearrange items instead
+ * of creating new ones.
+ */
+ function renderLegend() {
+ itemX = initialItemX;
+ itemY = y;
+ offsetWidth = 0;
+ lastItemY = 0;
+
+ allItems = [];
+
+ if (!legendGroup) {
+ legendGroup = renderer.g('legend')
+ .attr({ zIndex: 7 })
+ .add();
+ }
+
+
+ // add HTML for each series
+ if (reversedLegend) {
+ series.reverse();
+ }
+ each(series, function(serie) {
+ if (!serie.options.showInLegend) {
+ return;
+ }
+
+ // use points or series for the legend item depending on legendType
+ var items = (serie.options.legendType == 'point') ?
+ serie.data : [serie];
+
+ // render all items
+ each(items, renderItem);
+ });
+ if (reversedLegend) { // restore
+ series.reverse();
+ }
+
+
+
+ // Draw the border
+ legendWidth = widthOption || offsetWidth;
+ legendHeight = lastItemY - y + itemHeight;
+
+ if (legendBorderWidth || legendBackgroundColor) {
+ legendWidth += 2 * padding;
+ legendHeight += 2 * padding;
+
+ if (!box) {
+ box = renderer.rect(
+ 0,
+ 0,
+ legendWidth,
+ legendHeight,
+ options.borderRadius,
+ legendBorderWidth || 0
+ ).attr({
+ stroke: options.borderColor,
+ 'stroke-width': legendBorderWidth || 0,
+ fill: legendBackgroundColor || NONE
+ })
+ .add(legendGroup)
+ .shadow(options.shadow);
+
+ } else if (legendWidth > 0 && legendHeight > 0) {
+ box.animate(
+ box.crisp(null, null, null, legendWidth, legendHeight)
+ );
+ }
+
+ // hide the border if no items
+ box[allItems.length ? 'show' : 'hide']();
+ }
+
+ // 1.x compatibility: positioning based on style
+ var props = ['left', 'right', 'top', 'bottom'],
+ prop,
+ i = 4;
+ while(i--) {
+ prop = props[i];
+ if (style[prop] && style[prop] != 'auto') {
+ options[i < 2 ? 'align' : 'verticalAlign'] = prop;
+ options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
+ }
+ }
+
+ legendGroup.align(extend(options, {
+ width: legendWidth,
+ height: legendHeight
+ }), true, spacingBox);
+
+ if (!isResizing) {
+ positionCheckboxes();
+ }
+ }
+
+
+ // run legend
+ renderLegend();
+
+ // move checkboxes
+ addEvent(chart, 'endResize', positionCheckboxes);
+
+ // expose
+ return {
+ colorizeItem: colorizeItem,
+ destroyItem: destroyItem,
+ renderLegend: renderLegend
+ };
+ };
+
+
+
+
+
+
+ /**
+ * Initialize an individual series, called internally before render time
+ */
+ function initSeries(options) {
+ var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
+ typeClass = seriesTypes[type],
+ serie,
+ hasRendered = chart.hasRendered;
+
+ // an inverted chart can't take a column series and vice versa
+ if (hasRendered) {
+ if (inverted && type == 'column') {
+ typeClass = seriesTypes.bar;
+ } else if (!inverted && type == 'bar') {
+ typeClass = seriesTypes.column;
+ }
+ }
+
+ serie = new typeClass();
+
+ serie.init(chart, options);
+
+ // set internal chart properties
+ if (!hasRendered && serie.inverted) {
+ inverted = true;
+ }
+ if (serie.isCartesian) {
+ hasCartesianSeries = serie.isCartesian;
+ }
+
+ series.push(serie);
+
+ return serie;
+ }
+
+ /**
+ * Add a series dynamically after time
+ *
+ * @param {Object} options The config options
+ * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ *
+ * @return {Object} series The newly created series object
+ */
+ function addSeries(options, redraw, animation) {
+ var series;
+
+ if (options) {
+ setAnimation(animation, chart);
+ redraw = pick(redraw, true); // defaults to true
+
+ fireEvent(chart, 'addSeries', { options: options }, function() {
+ series = initSeries(options);
+ series.isDirty = true;
+
+ chart.isDirtyLegend = true; // the series array is out of sync with the display
+ if (redraw) {
+ chart.redraw();
+ }
+ });
+ }
+
+ return series;
+ }
+
+ /**
+ * Check whether a given point is within the plot area
+ *
+ * @param {Number} x Pixel x relative to the coordinateSystem
+ * @param {Number} y Pixel y relative to the coordinateSystem
+ */
+ isInsidePlot = function(x, y) {
+ return x >= 0 &&
+ x <= plotWidth &&
+ y >= 0 &&
+ y <= plotHeight;
+ };
+
+ /**
+ * Adjust all axes tick amounts
+ */
+ function adjustTickAmounts() {
+ if (optionsChart.alignTicks !== false) {
+ each(axes, function(axis) {
+ axis.adjustTickAmount();
+ });
+ }
+ maxTicks = null;
+ }
+
+ /**
+ * Redraw legend, axes or series based on updated data
+ *
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ */
+ function redraw(animation) {
+ var redrawLegend = chart.isDirtyLegend,
+ hasStackedSeries,
+ isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
+ seriesLength = series.length,
+ i = seriesLength,
+ clipRect = chart.clipRect,
+ serie;
+
+ setAnimation(animation, chart);
+
+ // link stacked series
+ while (i--) {
+ serie = series[i];
+ if (serie.isDirty && serie.options.stacking) {
+ hasStackedSeries = true;
+ break;
+ }
+ }
+ if (hasStackedSeries) { // mark others as dirty
+ i = seriesLength;
+ while (i--) {
+ serie = series[i];
+ if (serie.options.stacking) {
+ serie.isDirty = true;
+ }
+ }
+ }
+
+ // handle updated data in the series
+ each(series, function(serie) {
+ if (serie.isDirty) { // prepare the data so axis can read it
+ serie.cleanData();
+ serie.getSegments();
+
+ if (serie.options.legendType == 'point') {
+ redrawLegend = true;
+ }
+ }
+ });
+
+ // handle added or removed series
+ if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
+ // draw legend graphics
+ legend.renderLegend();
+
+ chart.isDirtyLegend = false;
+ }
+
+ if (hasCartesianSeries) {
+ if (!isResizing) {
+
+ // reset maxTicks
+ maxTicks = null;
+
+ // set axes scales
+ each(axes, function(axis) {
+ axis.setScale();
+ });
+ }
+ adjustTickAmounts();
+ getMargins();
+
+ // redraw axes
+ each(axes, function(axis) {
+ if (axis.isDirty || isDirtyBox) {
+ axis.redraw();
+ isDirtyBox = true; // always redraw box to reflect changes in the axis labels
+ }
+ });
+
+
+ }
+
+ // the plot areas size has changed
+ if (isDirtyBox) {
+ drawChartBox();
+ placeTrackerGroup();
+
+ // move clip rect
+ if (clipRect) {
+ stop(clipRect);
+ clipRect.animate({ // for chart resize
+ width: chart.plotSizeX,
+ height: chart.plotSizeY
+ });
+ }
+
+ }
+
+
+ // redraw affected series
+ each(series, function(serie) {
+ if (serie.isDirty && serie.visible &&
+ (!serie.isCartesian || serie.xAxis)) { // issue #153
+ serie.redraw();
+ }
+ });
+
+
+ // hide tooltip and hover states
+ if (tracker && tracker.resetTracker) {
+ tracker.resetTracker();
+ }
+
+ // fire the event
+ fireEvent(chart, 'redraw');
+ }
+
+
+
+ /**
+ * Dim the chart and show a loading text or symbol
+ * @param {String} str An optional text to show in the loading label instead of the default one
+ */
+ function showLoading(str) {
+ var loadingOptions = options.loading;
+
+ // create the layer at the first call
+ if (!loadingDiv) {
+ loadingDiv = createElement(DIV, {
+ className: 'highcharts-loading'
+ }, extend(loadingOptions.style, {
+ left: plotLeft + PX,
+ top: plotTop + PX,
+ width: plotWidth + PX,
+ height: plotHeight + PX,
+ zIndex: 10,
+ display: NONE
+ }), container);
+
+ loadingSpan = createElement(
+ 'span',
+ null,
+ loadingOptions.labelStyle,
+ loadingDiv
+ );
+
+ }
+
+ // update text
+ loadingSpan.innerHTML = str || options.lang.loading;
+
+ // show it
+ if (!loadingShown) {
+ css(loadingDiv, { opacity: 0, display: '' });
+ animate(loadingDiv, {
+ opacity: loadingOptions.style.opacity
+ }, {
+ duration: loadingOptions.showDuration
+ });
+ loadingShown = true;
+ }
+ }
+ /**
+ * Hide the loading layer
+ */
+ function hideLoading() {
+ animate(loadingDiv, {
+ opacity: 0
+ }, {
+ duration: options.loading.hideDuration,
+ complete: function() {
+ css(loadingDiv, { display: NONE });
+ }
+ });
+ loadingShown = false;
+ }
+
+ /**
+ * Get an axis, series or point object by id.
+ * @param id {String} The id as given in the configuration options
+ */
+ function get(id) {
+ var i,
+ j,
+ data;
+
+ // search axes
+ for (i = 0; i < axes.length; i++) {
+ if (axes[i].options.id == id) {
+ return axes[i];
+ }
+ }
+
+ // search series
+ for (i = 0; i < series.length; i++) {
+ if (series[i].options.id == id) {
+ return series[i];
+ }
+ }
+
+ // search points
+ for (i = 0; i < series.length; i++) {
+ data = series[i].data;
+ for (j = 0; j < data.length; j++) {
+ if (data[j].id == id) {
+ return data[j];
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create the Axis instances based on the config options
+ */
+ function getAxes() {
+ var xAxisOptions = options.xAxis || {},
+ yAxisOptions = options.yAxis || {},
+ axis;
+
+ // make sure the options are arrays and add some members
+ xAxisOptions = splat(xAxisOptions);
+ each(xAxisOptions, function(axis, i) {
+ axis.index = i;
+ axis.isX = true;
+ });
+
+ yAxisOptions = splat(yAxisOptions);
+ each(yAxisOptions, function(axis, i) {
+ axis.index = i;
+ });
+
+ // concatenate all axis options into one array
+ axes = xAxisOptions.concat(yAxisOptions);
+
+ // loop the options and construct axis objects
+ chart.xAxis = [];
+ chart.yAxis = [];
+ axes = map(axes, function(axisOptions) {
+ axis = new Axis(chart, axisOptions);
+ chart[axis.isXAxis ? 'xAxis' : 'yAxis'].push(axis);
+
+ return axis;
+ });
+
+ adjustTickAmounts();
+ }
+
+
+ /**
+ * Get the currently selected points from all series
+ */
+ function getSelectedPoints() {
+ var points = [];
+ each(series, function(serie) {
+ points = points.concat( grep( serie.data, function(point) {
+ return point.selected;
+ }));
+ });
+ return points;
+ }
+
+ /**
+ * Get the currently selected series
+ */
+ function getSelectedSeries() {
+ return grep(series, function (serie) {
+ return serie.selected;
+ });
+ }
+
+ /**
+ * Zoom out to 1:1
+ */
+ zoomOut = function () {
+ fireEvent(chart, 'selection', { resetSelection: true }, zoom);
+ chart.toolbar.remove('zoom');
+
+ };
+ /**
+ * Zoom into a given portion of the chart given by axis coordinates
+ * @param {Object} event
+ */
+ zoom = function (event) {
+
+ // add button to reset selection
+ var lang = defaultOptions.lang,
+ animate = chart.pointCount < 100;
+ chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, zoomOut);
+
+ // if zoom is called with no arguments, reset the axes
+ if (!event || event.resetSelection) {
+ each(axes, function(axis) {
+ axis.setExtremes(null, null, false, animate);
+ });
+ }
+
+ // else, zoom in on all axes
+ else {
+ each(event.xAxis.concat(event.yAxis), function(axisData) {
+ var axis = axisData.axis;
+
+ // don't zoom more than maxZoom
+ if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
+ axis.setExtremes(axisData.min, axisData.max, false, animate);
+ }
+ });
+ }
+
+ // redraw chart
+ redraw();
+ };
+
+ /**
+ * Show the title and subtitle of the chart
+ *
+ * @param titleOptions {Object} New title options
+ * @param subtitleOptions {Object} New subtitle options
+ *
+ */
+ function setTitle (titleOptions, subtitleOptions) {
+
+ chartTitleOptions = merge(options.title, titleOptions);
+ chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
+
+ // add title and subtitle
+ each([
+ ['title', titleOptions, chartTitleOptions],
+ ['subtitle', subtitleOptions, chartSubtitleOptions]
+ ], function(arr) {
+ var name = arr[0],
+ title = chart[name],
+ titleOptions = arr[1],
+ chartTitleOptions = arr[2];
+
+ if (title && titleOptions) {
+ title.destroy(); // remove old
+ title = null;
+ }
+ if (chartTitleOptions && chartTitleOptions.text && !title) {
+ chart[name] = renderer.text(
+ chartTitleOptions.text,
+ 0,
+ 0
+ )
+ .attr({
+ align: chartTitleOptions.align,
+ 'class': 'highcharts-'+ name,
+ zIndex: 1
+ })
+ .css(chartTitleOptions.style)
+ .add()
+ .align(chartTitleOptions, false, spacingBox);
+ }
+ });
+
+ }
+
+ /**
+ * Get chart width and height according to options and container size
+ */
+ function getChartSize() {
+
+ containerWidth = (renderToClone || renderTo).offsetWidth;
+ containerHeight = (renderToClone || renderTo).offsetHeight;
+ chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
+ chart.chartHeight = chartHeight = optionsChart.height ||
+ // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
+ (containerHeight > 19 ? containerHeight : 400);
+ }
+
+
+ /**
+ * Get the containing element, determine the size and create the inner container
+ * div to hold the chart
+ */
+ function getContainer() {
+ renderTo = optionsChart.renderTo;
+ containerId = PREFIX + idCounter++;
+
+ if (isString(renderTo)) {
+ renderTo = doc.getElementById(renderTo);
+ }
+
+ // remove previous chart
+ renderTo.innerHTML = '';
+
+ // If the container doesn't have an offsetWidth, it has or is a child of a node
+ // that has display:none. We need to temporarily move it out to a visible
+ // state to determine the size, else the legend and tooltips won't render
+ // properly
+ if (!renderTo.offsetWidth) {
+ renderToClone = renderTo.cloneNode(0);
+ css(renderToClone, {
+ position: ABSOLUTE,
+ top: '-9999px',
+ display: ''
+ });
+ doc.body.appendChild(renderToClone);
+ }
+
+ // get the width and height
+ getChartSize();
+
+ // create the inner container
+ chart.container = container = createElement(DIV, {
+ className: 'highcharts-container' +
+ (optionsChart.className ? ' '+ optionsChart.className : ''),
+ id: containerId
+ }, extend({
+ position: RELATIVE,
+ overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
+ // content overflow in IE
+ width: chartWidth + PX,
+ height: chartHeight + PX,
+ textAlign: 'left'
+ }, optionsChart.style),
+ renderToClone || renderTo
+ );
+
+ chart.renderer = renderer =
+ optionsChart.forExport ? // force SVG, used for SVG export
+ new SVGRenderer(container, chartWidth, chartHeight, true) :
+ new Renderer(container, chartWidth, chartHeight);
+
+ // Issue 110 workaround:
+ // In Firefox, if a div is positioned by percentage, its pixel position may land
+ // between pixels. The container itself doesn't display this, but an SVG element
+ // inside this container will be drawn at subpixel precision. In order to draw
+ // sharp lines, this must be compensated for. This doesn't seem to work inside
+ // iframes though (like in jsFiddle).
+ var subPixelFix, rect;
+ if (isFirefox && container.getBoundingClientRect) {
+ subPixelFix = function() {
+ css(container, { left: 0, top: 0 });
+ rect = container.getBoundingClientRect();
+ css(container, {
+ left: (-rect.left % 1) + PX,
+ top: (-rect.top % 1) + PX
+ });
+ };
+
+ // run the fix now
+ subPixelFix();
+
+ // run it on resize
+ addEvent(win, 'resize', subPixelFix);
+
+ // remove it on chart destroy
+ addEvent(chart, 'destroy', function() {
+ removeEvent(win, 'resize', subPixelFix);
+ });
+ }
+ }
+
+ /**
+ * Calculate margins by rendering axis labels in a preliminary position. Title,
+ * subtitle and legend have already been rendered at this stage, but will be
+ * moved into their final positions
+ */
+ getMargins = function() {
+ var legendOptions = options.legend,
+ legendMargin = pick(legendOptions.margin, 10),
+ legendX = legendOptions.x,
+ legendY = legendOptions.y,
+ align = legendOptions.align,
+ verticalAlign = legendOptions.verticalAlign,
+ titleOffset;
+
+ resetMargins();
+
+ // adjust for title and subtitle
+ if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
+ titleOffset = mathMax(
+ chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y || 0,
+ chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y || 0
+ );
+ if (titleOffset) {
+ plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
+ }
+ }
+ // adjust for legend
+ if (legendOptions.enabled && !legendOptions.floating) {
+ if (align == 'right') { // horizontal alignment handled first
+ if (!defined(optionsMarginRight)) {
+ marginRight = mathMax(
+ marginRight,
+ legendWidth - legendX + legendMargin + spacingRight
+ );
+ }
+ } else if (align == 'left') {
+ if (!defined(optionsMarginLeft)) {
+ plotLeft = mathMax(
+ plotLeft,
+ legendWidth + legendX + legendMargin + spacingLeft
+ );
+ }
+
+ } else if (verticalAlign == 'top') {
+ if (!defined(optionsMarginTop)) {
+ plotTop = mathMax(
+ plotTop,
+ legendHeight + legendY + legendMargin + spacingTop
+ );
+ }
+
+ } else if (verticalAlign == 'bottom') {
+ if (!defined(optionsMarginBottom)) {
+ marginBottom = mathMax(
+ marginBottom,
+ legendHeight - legendY + legendMargin + spacingBottom
+ );
+ }
+ }
+ }
+
+ // pre-render axes to get labels offset width
+ if (hasCartesianSeries) {
+ each(axes, function(axis) {
+ axis.getOffset();
+ });
+ }
+
+ if (!defined(optionsMarginLeft)) {
+ plotLeft += axisOffset[3];
+ }
+ if (!defined(optionsMarginTop)) {
+ plotTop += axisOffset[0];
+ }
+ if (!defined(optionsMarginBottom)) {
+ marginBottom += axisOffset[2];
+ }
+ if (!defined(optionsMarginRight)) {
+ marginRight += axisOffset[1];
+ }
+
+ setChartSize();
+
+ };
+
+ /**
+ * Add the event handlers necessary for auto resizing
+ *
+ */
+ function initReflow() {
+ var reflowTimeout;
+ function reflow() {
+ var width = optionsChart.width || renderTo.offsetWidth,
+ height = optionsChart.height || renderTo.offsetHeight;
+
+ if (width && height) { // means container is display:none
+ if (width != containerWidth || height != containerHeight) {
+ clearTimeout(reflowTimeout);
+ reflowTimeout = setTimeout(function() {
+ resize(width, height, false);
+ }, 100);
+ }
+ containerWidth = width;
+ containerHeight = height;
+ }
+ }
+ addEvent(window, 'resize', reflow);
+ addEvent(chart, 'destroy', function() {
+ removeEvent(window, 'resize', reflow);
+ });
+ }
+
+ /**
+ * Resize the chart to a given width and height
+ * @param {Number} width
+ * @param {Number} height
+ * @param {Object|Boolean} animation
+ */
+ resize = function(width, height, animation) {
+ var chartTitle = chart.title,
+ chartSubtitle = chart.subtitle;
+
+ isResizing += 1;
+
+ // set the animation for the current process
+ setAnimation(animation, chart);
+
+ oldChartHeight = chartHeight;
+ oldChartWidth = chartWidth;
+ chartWidth = mathRound(width);
+ chartHeight = mathRound(height);
+
+ css(container, {
+ width: chartWidth + PX,
+ height: chartHeight + PX
+ });
+ renderer.setSize(chartWidth, chartHeight, animation);
+
+ // update axis lengths for more correct tick intervals:
+ plotWidth = chartWidth - plotLeft - marginRight;
+ plotHeight = chartHeight - plotTop - marginBottom;
+
+ // handle axes
+ maxTicks = null;
+ each(axes, function(axis) {
+ axis.isDirty = true;
+ axis.setScale();
+ });
+
+ // make sure non-cartesian series are also handled
+ each(series, function(serie) {
+ serie.isDirty = true;
+ });
+
+ chart.isDirtyLegend = true; // force legend redraw
+ chart.isDirtyBox = true; // force redraw of plot and chart border
+
+ getMargins();
+
+ // move titles
+ if (chartTitle) {
+ chartTitle.align(null, null, spacingBox);
+ }
+ if (chartSubtitle) {
+ chartSubtitle.align(null, null, spacingBox);
+ }
+
+ redraw(animation);
+
+
+ oldChartHeight = null;
+ fireEvent(chart, 'resize');
+
+ // fire endResize and set isResizing back
+ setTimeout(function() {
+ fireEvent(chart, 'endResize', null, function() {
+ isResizing -= 1;
+ });
+ }, globalAnimation && globalAnimation.duration || 500);
+ };
+
+ /**
+ * Set the public chart properties. This is done before and after the pre-render
+ * to determine margin sizes
+ */
+ setChartSize = function() {
+
+ chart.plotLeft = plotLeft = mathRound(plotLeft);
+ chart.plotTop = plotTop = mathRound(plotTop);
+ chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
+ chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);
+
+ chart.plotSizeX = inverted ? plotHeight : plotWidth;
+ chart.plotSizeY = inverted ? plotWidth : plotHeight;
+
+ spacingBox = {
+ x: spacingLeft,
+ y: spacingTop,
+ width: chartWidth - spacingLeft - spacingRight,
+ height: chartHeight - spacingTop - spacingBottom
+ };
+ };
+
+ /**
+ * Initial margins before auto size margins are applied
+ */
+ resetMargins = function() {
+ plotTop = pick(optionsMarginTop, spacingTop);
+ marginRight = pick(optionsMarginRight, spacingRight);
+ marginBottom = pick(optionsMarginBottom, spacingBottom);
+ plotLeft = pick(optionsMarginLeft, spacingLeft);
+ axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
+ };
+
+ /**
+ * Draw the borders and backgrounds for chart and plot area
+ */
+ drawChartBox = function() {
+ var chartBorderWidth = optionsChart.borderWidth || 0,
+ chartBackgroundColor = optionsChart.backgroundColor,
+ plotBackgroundColor = optionsChart.plotBackgroundColor,
+ plotBackgroundImage = optionsChart.plotBackgroundImage,
+ mgn,
+ plotSize = {
+ x: plotLeft,
+ y: plotTop,
+ width: plotWidth,
+ height: plotHeight
+ };
+
+ // Chart area
+ mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
+
+ if (chartBorderWidth || chartBackgroundColor) {
+ if (!chartBackground) {
+ chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
+ optionsChart.borderRadius, chartBorderWidth)
+ .attr({
+ stroke: optionsChart.borderColor,
+ 'stroke-width': chartBorderWidth,
+ fill: chartBackgroundColor || NONE
+ })
+ .add()
+ .shadow(optionsChart.shadow);
+ } else { // resize
+ chartBackground.animate(
+ chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
+ );
+ }
+ }
+
+
+ // Plot background
+ if (plotBackgroundColor) {
+ if (!plotBackground) {
+ plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
+ .attr({
+ fill: plotBackgroundColor
+ })
+ .add()
+ .shadow(optionsChart.plotShadow);
+ } else {
+ plotBackground.animate(plotSize);
+ }
+ }
+ if (plotBackgroundImage) {
+ if (!plotBGImage) {
+ plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
+ .add();
+ } else {
+ plotBGImage.animate(plotSize);
+ }
+ }
+
+ // Plot area border
+ if (optionsChart.plotBorderWidth) {
+ if (!plotBorder) {
+ plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
+ .attr({
+ stroke: optionsChart.plotBorderColor,
+ 'stroke-width': optionsChart.plotBorderWidth,
+ zIndex: 4
+ })
+ .add();
+ } else {
+ plotBorder.animate(
+ plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
+ );
+ }
+ }
+
+ // reset
+ chart.isDirtyBox = false;
+ };
+
+ /**
+ * Render all graphics for the chart
+ */
+ function render () {
+ var labels = options.labels,
+ credits = options.credits,
+ creditsHref;
+
+ // Title
+ setTitle();
+
+
+ // Legend
+ legend = chart.legend = new Legend(chart);
+
+ // Get margins by pre-rendering axes
+ getMargins();
+ each(axes, function(axis) {
+ axis.setTickPositions(true); // update to reflect the new margins
+ });
+ adjustTickAmounts();
+ getMargins(); // second pass to check for new labels
+
+
+ // Draw the borders and backgrounds
+ drawChartBox();
+
+ // Axes
+ if (hasCartesianSeries) {
+ each(axes, function(axis) {
+ axis.render();
+ });
+ }
+
+
+ // The series
+ if (!chart.seriesGroup) {
+ chart.seriesGroup = renderer.g('series-group')
+ .attr({ zIndex: 3 })
+ .add();
+ }
+ each(series, function(serie) {
+ serie.translate();
+ serie.setTooltipPoints();
+ serie.render();
+ });
+
+
+ // Labels
+ if (labels.items) {
+ each(labels.items, function() {
+ var style = extend(labels.style, this.style),
+ x = pInt(style.left) + plotLeft,
+ y = pInt(style.top) + plotTop + 12;
+
+ // delete to prevent rewriting in IE
+ delete style.left;
+ delete style.top;
+
+ renderer.text(
+ this.html,
+ x,
+ y
+ )
+ .attr({ zIndex: 2 })
+ .css(style)
+ .add();
+
+ });
+ }
+
+ // Toolbar (don't redraw)
+ if (!chart.toolbar) {
+ chart.toolbar = Toolbar(chart);
+ }
+
+ // Credits
+ if (credits.enabled && !chart.credits) {
+ creditsHref = credits.href;
+ renderer.text(
+ credits.text,
+ 0,
+ 0
+ )
+ .on('click', function() {
+ if (creditsHref) {
+ location.href = creditsHref;
+ }
+ })
+ .attr({
+ align: credits.position.align,
+ zIndex: 8
+ })
+ .css(credits.style)
+ .add()
+ .align(credits.position);
+ }
+
+ placeTrackerGroup();
+
+ // Set flag
+ chart.hasRendered = true;
+
+ // If the chart was rendered outside the top container, put it back in
+ if (renderToClone) {
+ renderTo.appendChild(container);
+ discardElement(renderToClone);
+ //updatePosition(container);
+ }
+ }
+
+ /**
+ * Clean up memory usage
+ */
+ function destroy() {
+ var i = series.length,
+ parentNode = container && container.parentNode;
+
+ // fire the chart.destoy event
+ fireEvent(chart, 'destroy');
+
+ // remove events
+ removeEvent(win, 'unload', destroy);
+ removeEvent(chart);
+
+ each(axes, function(axis) {
+ removeEvent(axis);
+ });
+
+ // destroy each series
+ while (i--) {
+ series[i].destroy();
+ }
+
+ // remove container and all SVG
+ if (container) { // can break in IE when destroyed before finished loading
+ container.innerHTML = '';
+ removeEvent(container);
+ if (parentNode) {
+ parentNode.removeChild(container);
+ }
+
+ // IE6 leak
+ container = null;
+ }
+
+ // IE7 leak
+ if (renderer) { // can break in IE when destroyed before finished loading
+ renderer.alignedObjects = null;
+ }
+
+ // memory and CPU leak
+ clearInterval(tooltipInterval);
+
+ // clean it all up
+ for (i in chart) {
+ delete chart[i];
+ }
+
+ }
+ /**
+ * Prepare for first rendering after all data are loaded
+ */
+ function firstRender() {
+
+ // VML namespaces can't be added until after complete. Listening
+ // for Perini's doScroll hack is not enough.
+ var onreadystatechange = 'onreadystatechange';
+ if (!hasSVG && win == win.top && doc.readyState != 'complete') {
+ doc.attachEvent(onreadystatechange, function() {
+ doc.detachEvent(onreadystatechange, firstRender);
+ firstRender();
+ });
+ return;
+ }
+
+ // create the container
+ getContainer();
+
+ resetMargins();
+ setChartSize();
+
+ // Initialize the series
+ each(options.series || [], function(serieOptions) {
+ initSeries(serieOptions);
+ });
+
+ // Set the common inversion and transformation for inverted series after initSeries
+ chart.inverted = inverted = pick(inverted, options.chart.inverted);
+
+
+ getAxes();
+
+
+ chart.render = render;
+
+ // depends on inverted and on margins being set
+ chart.tracker = tracker = new MouseTracker(chart, options.tooltip);
+
+ //globalAnimation = false;
+ render();
+
+ fireEvent(chart, 'load');
+
+ //globalAnimation = true;
+
+ // run callbacks
+ if (callback) {
+ callback.apply(chart, [chart]);
+ }
+ each(chart.callbacks, function(fn) {
+ fn.apply(chart, [chart]);
+ });
+ }
+
+ // Run chart
+
+
+
+ // Set to zero for each new chart
+ colorCounter = 0;
+ symbolCounter = 0;
+
+ // Destroy the chart and free up memory.
+ addEvent(win, 'unload', destroy);
+
+ // Set up auto resize
+ if (optionsChart.reflow !== false) {
+ addEvent(chart, 'load', initReflow);
+ }
+
+ // Chart event handlers
+ if (chartEvents) {
+ for (eventType in chartEvents) {
+ addEvent(chart, eventType, chartEvents[eventType]);
+ }
+ }
+
+
+ chart.options = options;
+ chart.series = series;
+
+
+
+
+
+
+ // Expose methods and variables
+ chart.addSeries = addSeries;
+ chart.animation = pick(optionsChart.animation, true);
+ chart.destroy = destroy;
+ chart.get = get;
+ chart.getSelectedPoints = getSelectedPoints;
+ chart.getSelectedSeries = getSelectedSeries;
+ chart.hideLoading = hideLoading;
+ chart.isInsidePlot = isInsidePlot;
+ chart.redraw = redraw;
+ chart.setSize = resize;
+ chart.setTitle = setTitle;
+ chart.showLoading = showLoading;
+ chart.pointCount = 0;
+ /*
+ if ($) $(function() {
+ $container = $('#container');
+ var origChartWidth,
+ origChartHeight;
+ if ($container) {
+ $('<button>+</button>')
+ .insertBefore($container)
+ .click(function() {
+ if (origChartWidth === UNDEFINED) {
+ origChartWidth = chartWidth;
+ origChartHeight = chartHeight;
+ }
+ chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
+ });
+ $('<button>-</button>')
+ .insertBefore($container)
+ .click(function() {
+ if (origChartWidth === UNDEFINED) {
+ origChartWidth = chartWidth;
+ origChartHeight = chartHeight;
+ }
+ chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
+ });
+ $('<button>1:1</button>')
+ .insertBefore($container)
+ .click(function() {
+ if (origChartWidth === UNDEFINED) {
+ origChartWidth = chartWidth;
+ origChartHeight = chartHeight;
+ }
+ chart.resize(origChartWidth, origChartHeight);
+ });
+ }
+ })
+ */
+
+
+
+
+ firstRender();
+
+
+} // end Chart
+
+// Hook for exporting module
+Chart.prototype.callbacks = [];
+
+/**
+ * The Point object and prototype. Inheritable and used as base for PiePoint
+ */
+var Point = function() {};
+Point.prototype = {
+
+ /**
+ * Initialize the point
+ * @param {Object} series The series object containing this point
+ * @param {Object} options The data in either number, array or object format
+ */
+ init: function(series, options) {
+ var point = this,
+ defaultColors;
+ point.series = series;
+ point.applyOptions(options);
+ point.pointAttr = {};
+
+ if (series.options.colorByPoint) {
+ defaultColors = series.chart.options.colors;
+ if (!point.options) {
+ point.options = {};
+ }
+ point.color = point.options.color = point.color || defaultColors[colorCounter++];
+
+ // loop back to zero
+ if (colorCounter >= defaultColors.length) {
+ colorCounter = 0;
+ }
+ }
+
+ series.chart.pointCount++;
+ return point;
+ },
+ /**
+ * Apply the options containing the x and y data and possible some extra properties.
+ * This is called on point init or from point.update.
+ *
+ * @param {Object} options
+ */
+ applyOptions: function(options) {
+ var point = this,
+ series = point.series;
+
+ point.config = options;
+
+ // onedimensional array input
+ if (isNumber(options) || options === null) {
+ point.y = options;
+ }
+
+ // object input
+ else if (isObject(options) && !isNumber(options.length)) {
+
+ // copy options directly to point
+ extend(point, options);
+ point.options = options;
+ }
+
+ // categorized data with name in first position
+ else if (isString(options[0])) {
+ point.name = options[0];
+ point.y = options[1];
+ }
+
+ // two-dimentional array
+ else if (isNumber(options[0])) {
+ point.x = options[0];
+ point.y = options[1];
+ }
+
+ /*
+ * If no x is set by now, get auto incremented value. All points must have an
+ * x value, however the y value can be null to create a gap in the series
+ */
+ if (point.x === UNDEFINED) {
+ point.x = series.autoIncrement();
+ }
+
+ },
+
+ /**
+ * Destroy a point to clear memory. Its reference still stays in series.data.
+ */
+ destroy: function() {
+ var point = this,
+ series = point.series,
+ prop;
+
+ series.chart.pointCount--;
+
+ if (point == series.chart.hoverPoint) {
+ point.onMouseOut();
+ }
+ series.chart.hoverPoints = null; // remove reference
+
+ // remove all events
+ removeEvent(point);
+
+ each(['graphic', 'tracker', 'group', 'dataLabel', 'connector'], function(prop) {
+ if (point[prop]) {
+ point[prop].destroy();
+ }
+ });
+
+ if (point.legendItem) { // pies have legend items
+ point.series.chart.legend.destroyItem(point);
+ }
+
+ for (prop in point) {
+ point[prop] = null;
+ }
+
+
+ },
+
+ /**
+ * Toggle the selection status of a point
+ * @param {Boolean} selected Whether to select or unselect the point.
+ * @param {Boolean} accumulate Whether to add to the previous selection. By default,
+ * this happens if the control key (Cmd on Mac) was pressed during clicking.
+ */
+ select: function(selected, accumulate) {
+ var point = this,
+ series = point.series,
+ chart = series.chart;
+
+ point.selected = selected = pick(selected, !point.selected);
+
+ //series.isDirty = true;
+ point.firePointEvent(selected ? 'select' : 'unselect');
+ point.setState(selected && SELECT_STATE);
+
+ // unselect all other points unless Ctrl or Cmd + click
+ if (!accumulate) {
+ each(chart.getSelectedPoints(), function (loopPoint) {
+ if (loopPoint.selected && loopPoint != point) {
+ loopPoint.selected = false;
+ loopPoint.setState(NORMAL_STATE);
+ loopPoint.firePointEvent('unselect');
+ }
+ });
+ }
+
+ },
+
+ onMouseOver: function() {
+ var point = this,
+ chart = point.series.chart,
+ tooltip = chart.tooltip,
+ hoverPoint = chart.hoverPoint;
+
+ // set normal state to previous series
+ if (hoverPoint && hoverPoint != point) {
+ hoverPoint.onMouseOut();
+ }
+
+ // trigger the event
+ point.firePointEvent('mouseOver');
+
+ // update the tooltip
+ if (tooltip && !tooltip.shared) {
+ tooltip.refresh(point);
+ }
+
+ // hover this
+ point.setState(HOVER_STATE);
+ chart.hoverPoint = point;
+ },
+
+ onMouseOut: function() {
+ var point = this;
+ point.firePointEvent('mouseOut');
+
+ point.setState();
+ point.series.chart.hoverPoint = null;
+ },
+
+ /**
+ * Extendable method for formatting each point's tooltip line
+ *
+ * @param {Boolean} useHeader Whether a common header is used for multiple series in the tooltip
+ *
+ * @return {String} A string to be concatenated in to the common tooltip text
+ */
+ tooltipFormatter: function(useHeader) {
+ var point = this,
+ series = point.series;
+
+ return ['<span style="color:'+ series.color +'">', (point.name || series.name), '</span>: ',
+ (!useHeader ? ('<b>x = '+ (point.name || point.x) + ',</b> ') : ''),
+ '<b>', (!useHeader ? 'y = ' : '' ), point.y, '</b><br/>'].join('');
+
+ },
+
+ /**
+ * Get the formatted text for this point's data label
+ *
+ * @return {String} The formatted data label pseudo-HTML
+ */
+ getDataLabelText: function() {
+ var point = this;
+ return this.series.options.dataLabels.formatter.call({
+ x: point.x,
+ y: point.y,
+ series: point.series,
+ point: point,
+ percentage: point.percentage,
+ total: point.total || point.stackTotal
+ });
+ },
+
+ /**
+ * Update the point with new options (typically x/y data) and optionally redraw the series.
+ *
+ * @param {Object} options Point options as defined in the series.data array
+ * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ *
+ */
+ update: function(options, redraw, animation) {
+ var point = this,
+ series = point.series,
+ dataLabel = point.dataLabel,
+ graphic = point.graphic,
+ chart = series.chart;
+
+ redraw = pick(redraw, true);
+
+ // fire the event with a default handler of doing the update
+ point.firePointEvent('update', { options: options }, function() {
+
+ point.applyOptions(options);
+
+ if (dataLabel) {
+ dataLabel.attr({
+ text: point.getDataLabelText()
+ })
+ }
+
+ // update visuals
+ if (isObject(options)) {
+ series.getAttribs();
+ if (graphic) {
+ graphic.attr(point.pointAttr[series.state]);
+ }
+ }
+
+ // redraw
+ series.isDirty = true;
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ });
+ },
+
+ /**
+ * Remove a point and optionally redraw the series and if necessary the axes
+ * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ */
+ remove: function(redraw, animation) {
+ var point = this,
+ series = point.series,
+ chart = series.chart,
+ data = series.data;
+
+ setAnimation(animation, chart);
+ redraw = pick(redraw, true);
+
+ // fire the event with a default handler of removing the point
+ point.firePointEvent('remove', null, function() {
+
+ erase(data, point);
+
+ point.destroy();
+
+
+ // redraw
+ series.isDirty = true;
+ if (redraw) {
+ chart.redraw();
+ }
+ });
+
+
+ },
+
+ /**
+ * Fire an event on the Point object. Must not be renamed to fireEvent, as this
+ * causes a name clash in MooTools
+ * @param {String} eventType
+ * @param {Object} eventArgs Additional event arguments
+ * @param {Function} defaultFunction Default event handler
+ */
+ firePointEvent: function(eventType, eventArgs, defaultFunction) {
+ var point = this,
+ series = this.series,
+ seriesOptions = series.options;
+
+ // load event handlers on demand to save time on mouseover/out
+ if (seriesOptions.point.events[eventType] || (
+ point.options && point.options.events && point.options.events[eventType])) {
+ this.importEvents();
+ }
+
+ // add default handler if in selection mode
+ if (eventType == 'click' && seriesOptions.allowPointSelect) {
+ defaultFunction = function (event) {
+ // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
+ point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
+ };
+ }
+
+ fireEvent(this, eventType, eventArgs, defaultFunction);
+ },
+ /**
+ * Import events from the series' and point's options. Only do it on
+ * demand, to save processing time on hovering.
+ */
+ importEvents: function() {
+ if (!this.hasImportedEvents) {
+ var point = this,
+ options = merge(point.series.options.point, point.options),
+ events = options.events,
+ eventType;
+
+ point.events = events;
+
+ for (eventType in events) {
+ addEvent(point, eventType, events[eventType]);
+ }
+ this.hasImportedEvents = true;
+
+ }
+ },
+
+ /**
+ * Set the point's state
+ * @param {String} state
+ */
+ setState: function(state) {
+ var point = this,
+ series = point.series,
+ stateOptions = series.options.states,
+ markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
+ normalDisabled = markerOptions && !markerOptions.enabled,
+ markerStateOptions = markerOptions && markerOptions.states[state],
+ stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
+ stateMarkerGraphic = series.stateMarkerGraphic,
+ chart = series.chart,
+ pointAttr = point.pointAttr;
+
+ if (!state) {
+ state = NORMAL_STATE; // empty string
+ }
+
+ if (
+ // already has this state
+ state == point.state ||
+ // selected points don't respond to hover
+ (point.selected && state != SELECT_STATE) ||
+ // series' state options is disabled
+ (stateOptions[state] && stateOptions[state].enabled === false) ||
+ // point marker's state options is disabled
+ (state && (stateDisabled || normalDisabled && !markerStateOptions.enabled))
+
+ ) {
+ return;
+ }
+
+ // apply hover styles to the existing point
+ if (point.graphic) {
+ point.graphic.attr(pointAttr[state]);
+ }
+ // if a graphic is not applied to each point in the normal state, create a shared
+ // graphic for the hover state
+ else {
+ if (state) {
+ if (!stateMarkerGraphic) {
+ series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.circle(
+ 0, 0, pointAttr[state].r
+ )
+ .attr(pointAttr[state])
+ .add(series.group);
+ }
+
+ stateMarkerGraphic.translate(
+ point.plotX,
+ point.plotY
+ );
+ }
+
+ if (stateMarkerGraphic) {
+ stateMarkerGraphic[state ? 'show' : 'hide']();
+ }
+ }
+
+ point.state = state;
+ }
+};
+
+/**
+ * The base function which all other series types inherit from
+ * @param {Object} chart
+ * @param {Object} options
+ */
+var Series = function() {};
+
+Series.prototype = {
+
+ isCartesian: true,
+ type: 'line',
+ pointClass: Point,
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
+ stroke: 'lineColor',
+ 'stroke-width': 'lineWidth',
+ fill: 'fillColor',
+ r: 'radius'
+ },
+ init: function(chart, options) {
+ var series = this,
+ eventType,
+ events,
+ //pointEvent,
+ index = chart.series.length;
+
+ series.chart = chart;
+ options = series.setOptions(options); // merge with plotOptions
+
+ // set some variables
+ extend(series, {
+ index: index,
+ options: options,
+ name: options.name || 'Series '+ (index + 1),
+ state: NORMAL_STATE,
+ pointAttr: {},
+ visible: options.visible !== false, // true by default
+ selected: options.selected === true // false by default
+ });
+
+ // register event listeners
+ events = options.events;
+ for (eventType in events) {
+ addEvent(series, eventType, events[eventType]);
+ }
+ if (
+ (events && events.click) ||
+ (options.point && options.point.events && options.point.events.click) ||
+ options.allowPointSelect
+ ) {
+ chart.runTrackerClick = true;
+ }
+
+ series.getColor();
+ series.getSymbol();
+
+ // set the data
+ series.setData(options.data, false);
+
+ },
+
+
+ /**
+ * Return an auto incremented x value based on the pointStart and pointInterval options.
+ * This is only used if an x value is not given for the point that calls autoIncrement.
+ */
+ autoIncrement: function() {
+ var series = this,
+ options = series.options,
+ xIncrement = series.xIncrement;
+
+ xIncrement = pick(xIncrement, options.pointStart, 0);
+
+ series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
+
+ series.xIncrement = xIncrement + series.pointInterval;
+ return xIncrement;
+ },
+
+ /**
+ * Sort the data and remove duplicates
+ */
+ cleanData: function() {
+ var series = this,
+ chart = series.chart,
+ data = series.data,
+ closestPoints,
+ smallestInterval,
+ chartSmallestInterval = chart.smallestInterval,
+ interval,
+ i;
+
+ // sort the data points
+ data.sort(function(a, b){
+ return (a.x - b.x);
+ });
+
+ // remove points with equal x values
+ // record the closest distance for calculation of column widths
+ for (i = data.length - 1; i >= 0; i--) {
+ if (data[i - 1]) {
+ if (data[i - 1].x == data[i].x) {
+ data.splice(i - 1, 1); // remove the duplicate
+ }
+
+ }
+ }
+
+
+ // find the closes pair of points
+ for (i = data.length - 1; i >= 0; i--) {
+ if (data[i - 1]) {
+ interval = data[i].x - data[i - 1].x;
+ if (smallestInterval === UNDEFINED || interval < smallestInterval) {
+ smallestInterval = interval;
+ closestPoints = i;
+ }
+ }
+ }
+
+ if (chartSmallestInterval === UNDEFINED || smallestInterval < chartSmallestInterval) {
+ chart.smallestInterval = smallestInterval;
+ }
+ series.closestPoints = closestPoints;
+ },
+
+ /**
+ * Divide the series data into segments divided by null values. Also sort
+ * the data points and delete duplicate values.
+ */
+ getSegments: function() {
+ var lastNull = -1,
+ segments = [],
+ data = this.data;
+
+ // create the segments
+ each(data, function(point, i) {
+ if (point.y === null) {
+ if (i > lastNull + 1) {
+ segments.push(data.slice(lastNull + 1, i));
+ }
+ lastNull = i;
+ } else if (i == data.length - 1) { // last value
+ segments.push(data.slice(lastNull + 1, i + 1));
+ }
+ });
+ this.segments = segments;
+
+
+ },
+ /**
+ * Set the series options by merging from the options tree
+ * @param {Object} itemOptions
+ */
+ setOptions: function(itemOptions) {
+ var plotOptions = this.chart.options.plotOptions,
+ options = merge(
+ plotOptions[this.type],
+ plotOptions.series,
+ itemOptions
+ );
+
+ return options;
+
+ },
+ /**
+ * Get the series' color
+ */
+ getColor: function(){
+ var defaultColors = this.chart.options.colors;
+ this.color = this.options.color || defaultColors[colorCounter++] || '#0000ff';
+ if (colorCounter >= defaultColors.length) {
+ colorCounter = 0;
+ }
+ },
+ /**
+ * Get the series' symbol
+ */
+ getSymbol: function(){
+ var defaultSymbols = this.chart.options.symbols,
+ symbol = this.options.marker.symbol || defaultSymbols[symbolCounter++];
+ this.symbol = symbol;
+ if (symbolCounter >= defaultSymbols.length) {
+ symbolCounter = 0;
+ }
+ },
+
+ /**
+ * Add a point dynamically after chart load time
+ * @param {Object} options Point options as given in series.data
+ * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
+ * @param {Boolean} shift If shift is true, a point is shifted off the start
+ * of the series as one is appended to the end.
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ */
+ addPoint: function(options, redraw, shift, animation) {
+ var series = this,
+ data = series.data,
+ graph = series.graph,
+ area = series.area,
+ chart = series.chart,
+ point = (new series.pointClass()).init(series, options);
+
+ setAnimation(animation, chart);
+
+ if (graph && shift) { // make graph animate sideways
+ graph.shift = shift;
+ }
+ if (area) {
+ area.shift = shift;
+ area.isArea = true;
+ }
+
+ redraw = pick(redraw, true);
+
+ data.push(point);
+ if (shift) {
+ data[0].remove(false);
+ }
+
+
+ // redraw
+ series.isDirty = true;
+ if (redraw) {
+ chart.redraw();
+ }
+ },
+
+ /**
+ * Replace the series data with a new set of data
+ * @param {Object} data
+ * @param {Object} redraw
+ */
+ setData: function(data, redraw) {
+ var series = this,
+ oldData = series.data,
+ initialColor = series.initialColor,
+ chart = series.chart,
+ i = oldData && oldData.length || 0;
+
+ series.xIncrement = null; // reset for new data
+ if (defined(initialColor)) { // reset colors for pie
+ colorCounter = initialColor;
+ }
+
+ data = map(splat(data || []), function(pointOptions) {
+ return (new series.pointClass()).init(series, pointOptions);
+ });
+
+ // destroy old points
+ while (i--) {
+ oldData[i].destroy();
+ }
+
+ // set the data
+ series.data = data;
+
+ series.cleanData();
+ series.getSegments();
+
+ // redraw
+ series.isDirty = true;
+ chart.isDirtyBox = true;
+ if (pick(redraw, true)) {
+ chart.redraw(false);
+ }
+ },
+
+ /**
+ * Remove a series and optionally redraw the chart
+ *
+ * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
+ * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
+ * configuration
+ */
+
+ remove: function(redraw, animation) {
+ var series = this,
+ chart = series.chart;
+ redraw = pick(redraw, true);
+
+ if (!series.isRemoving) { /* prevent triggering native event in jQuery
+ (calling the remove function from the remove event) */
+ series.isRemoving = true;
+
+ // fire the event with a default handler of removing the point
+ fireEvent(series, 'remove', null, function() {
+
+
+ // destroy elements
+ series.destroy();
+
+
+ // redraw
+ chart.isDirtyLegend = chart.isDirtyBox = true;
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ });
+
+ }
+ series.isRemoving = false;
+ },
+
+ /**
+ * Translate data points from raw data values to chart specific positioning data
+ * needed later in drawPoints, drawGraph and drawTracker.
+ */
+ translate: function() {
+ var series = this,
+ chart = series.chart,
+ stacking = series.options.stacking,
+ categories = series.xAxis.categories,
+ yAxis = series.yAxis,
+ data = series.data,
+ i = data.length;
+
+ // do the translation
+ while (i--) {
+ var point = data[i],
+ xValue = point.x,
+ yValue = point.y,
+ yBottom = point.low,
+ stack = yAxis.stacks[(yValue < 0 ? '-' : '') + series.stackKey],
+ pointStack,
+ pointStackTotal;
+ point.plotX = series.xAxis.translate(xValue);
+
+ // calculate the bottom y value for stacked series
+ if (stacking && series.visible && stack && stack[xValue]) {
+ pointStack = stack[xValue];
+ pointStackTotal = pointStack.total;
+ pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
+ yValue = yBottom + yValue;
+
+ if (stacking == 'percent') {
+ yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
+ yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
+ }
+
+ point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
+ point.stackTotal = pointStackTotal;
+ }
+
+ if (defined(yBottom)) {
+ point.yBottom = yAxis.translate(yBottom, 0, 1);
+ }
+
+ // set the y value
+ if (yValue !== null) {
+ point.plotY = yAxis.translate(yValue, 0, 1);
+ }
+
+ // set client related positions for mouse tracking
+ point.clientX = chart.inverted ?
+ chart.plotHeight - point.plotX :
+ point.plotX; // for mouse tracking
+
+ // some API data
+ point.category = categories && categories[point.x] !== UNDEFINED ?
+ categories[point.x] : point.x;
+
+ }
+ },
+ /**
+ * Memoize tooltip texts and positions
+ */
+ setTooltipPoints: function (renew) {
+ var series = this,
+ chart = series.chart,
+ inverted = chart.inverted,
+ data = [],
+ plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
+ low,
+ high,
+ tooltipPoints = []; // a lookup array for each pixel in the x dimension
+
+ // renew
+ if (renew) {
+ series.tooltipPoints = null;
+ }
+
+ // concat segments to overcome null values
+ each(series.segments, function(segment){
+ data = data.concat(segment);
+ });
+
+ // loop the concatenated data and apply each point to all the closest
+ // pixel positions
+ if (series.xAxis && series.xAxis.reversed) {
+ data = data.reverse();//reverseArray(data);
+ }
+
+ each(data, function(point, i) {
+
+ low = data[i - 1] ? data[i - 1].high + 1 : 0;
+ high = point.high = data[i + 1] ? (
+ mathFloor((point.plotX + (data[i + 1] ?
+ data[i + 1].plotX : plotSize)) / 2)) :
+ plotSize;
+
+ while (low <= high) {
+ tooltipPoints[inverted ? plotSize - low++ : low++] = point;
+ }
+ });
+ series.tooltipPoints = tooltipPoints;
+ },
+
+
+
+
+ /**
+ * Series mouse over handler
+ */
+ onMouseOver: function() {
+ var series = this,
+ chart = series.chart,
+ hoverSeries = chart.hoverSeries;
+
+ if (!hasTouch && chart.mouseIsDown) {
+ return;
+ }
+
+ // set normal state to previous series
+ if (hoverSeries && hoverSeries != series) {
+ hoverSeries.onMouseOut();
+ }
+
+ // trigger the event, but to save processing time,
+ // only if defined
+ if (series.options.events.mouseOver) {
+ fireEvent(series, 'mouseOver');
+ }
+
+
+ // bring to front
+ // Todo: optimize. This is one of two operations slowing down the tooltip in Firefox.
+ // Can the tracking be done otherwise?
+ if (series.tracker) {
+ series.tracker.toFront();
+ }
+
+ // hover this
+ series.setState(HOVER_STATE);
+ chart.hoverSeries = series;
+ },
+
+ /**
+ * Series mouse out handler
+ */
+ onMouseOut: function() {
+ // trigger the event only if listeners exist
+ var series = this,
+ options = series.options,
+ chart = series.chart,
+ tooltip = chart.tooltip,
+ hoverPoint = chart.hoverPoint;
+
+ // trigger mouse out on the point, which must be in this series
+ if (hoverPoint) {
+ hoverPoint.onMouseOut();
+ }
+
+ // fire the mouse out event
+ if (series && options.events.mouseOut) {
+ fireEvent(series, 'mouseOut');
+ }
+
+
+ // hide the tooltip
+ if (tooltip && !options.stickyTracking) {
+ tooltip.hide();
+ }
+
+ // set normal state
+ series.setState();
+ chart.hoverSeries = null;
+ },
+
+ /**
+ * Animate in the series
+ */
+ animate: function(init) {
+ var series = this,
+ chart = series.chart,
+ clipRect = series.clipRect,
+ animation = series.options.animation;
+
+ if (animation && !isObject(animation)) {
+ animation = {};
+ }
+
+ if (init) { // initialize the animation
+ if (!clipRect.isAnimating) { // apply it only for one of the series
+ clipRect.attr( 'width', 0 );
+ clipRect.isAnimating = true;
+ }
+
+ } else { // run the animation
+ clipRect.animate({
+ width: chart.plotSizeX
+ }, animation);
+
+ // delete this function to allow it only once
+ this.animate = null;
+ }
+ },
+
+
+ /**
+ * Draw the markers
+ */
+ drawPoints: function(){
+ var series = this,
+ pointAttr,
+ data = series.data,
+ chart = series.chart,
+ plotX,
+ plotY,
+ i,
+ point,
+ radius,
+ graphic;
+
+ if (series.options.marker.enabled) {
+ i = data.length;
+ while (i--) {
+ point = data[i];
+ plotX = point.plotX;
+ plotY = point.plotY;
+ graphic = point.graphic;
+
+ // only draw the point if y is defined
+ if (plotY !== UNDEFINED && !isNaN(plotY)) {
+
+ /* && removed this code because points stayed after zoom
+ point.plotX >= 0 && point.plotX <= chart.plotSizeX &&
+ point.plotY >= 0 && point.plotY <= chart.plotSizeY*/
+
+ // shortcuts
+ pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
+ radius = pointAttr.r;
+
+ if (graphic) { // update
+ graphic.animate({
+ x: plotX,
+ y: plotY,
+ r: radius
+ });
+ } else {
+ point.graphic = chart.renderer.symbol(
+ pick(point.marker && point.marker.symbol, series.symbol),
+ plotX,
+ plotY,
+ radius
+ )
+ .attr(pointAttr)
+ .add(series.group);
+ }
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Convert state properties from API naming conventions to SVG attributes
+ *
+ * @param {Object} options API options object
+ * @param {Object} base1 SVG attribute object to inherit from
+ * @param {Object} base2 Second level SVG attribute object to inherit from
+ */
+ convertAttribs: function(options, base1, base2, base3) {
+ var conversion = this.pointAttrToOptions,
+ attr,
+ option,
+ obj = {};
+
+ options = options || {};
+ base1 = base1 || {};
+ base2 = base2 || {};
+ base3 = base3 || {};
+
+ for (attr in conversion) {
+ option = conversion[attr];
+ obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
+ }
+ return obj;
+ },
+
+ /**
+ * Get the state attributes. Each series type has its own set of attributes
+ * that are allowed to change on a point's state change. Series wide attributes are stored for
+ * all series, and additionally point specific attributes are stored for all
+ * points with individual marker options. If such options are not defined for the point,
+ * a reference to the series wide attributes is stored in point.pointAttr.
+ */
+ getAttribs: function() {
+ var series = this,
+ normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
+ stateOptions = normalOptions.states,
+ stateOptionsHover = stateOptions[HOVER_STATE],
+ pointStateOptionsHover,
+ seriesColor = series.color,
+ normalDefaults = {
+ stroke: seriesColor,
+ fill: seriesColor
+ },
+ data = series.data,
+ i,
+ point,
+ seriesPointAttr = [],
+ pointAttr,
+ pointAttrToOptions = series.pointAttrToOptions,
+ hasPointSpecificOptions;
+
+ // series type specific modifications
+ if (series.options.marker) { // line, spline, area, areaspline, scatter
+
+ // if no hover radius is given, default to normal radius + 2
+ stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
+ stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
+
+ } else { // column, bar, pie
+
+ // if no hover color is given, brighten the normal color
+ stateOptionsHover.color = stateOptionsHover.color ||
+ Color(stateOptionsHover.color || seriesColor)
+ .brighten(stateOptionsHover.brightness).get();
+ }
+
+ // general point attributes for the series normal state
+ seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
+
+ // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
+ each([HOVER_STATE, SELECT_STATE], function(state) {
+ seriesPointAttr[state] =
+ series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
+ });
+
+ // set it
+ series.pointAttr = seriesPointAttr;
+
+
+ // Generate the point-specific attribute collections if specific point
+ // options are given. If not, create a referance to the series wide point
+ // attributes
+ i = data.length;
+ while (i--) {
+ point = data[i];
+ normalOptions = (point.options && point.options.marker) || point.options;
+ if (normalOptions && normalOptions.enabled === false) {
+ normalOptions.radius = 0;
+ }
+ hasPointSpecificOptions = false;
+
+ // check if the point has specific visual options
+ if (point.options) {
+ for (var key in pointAttrToOptions) {
+ if (defined(normalOptions[pointAttrToOptions[key]])) {
+ hasPointSpecificOptions = true;
+ }
+ }
+ }
+
+
+
+ // a specific marker config object is defined for the individual point:
+ // create it's own attribute collection
+ if (hasPointSpecificOptions) {
+
+ pointAttr = [];
+ stateOptions = normalOptions.states || {}; // reassign for individual point
+ pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
+
+ // if no hover color is given, brighten the normal color
+ if (!series.options.marker) { // column, bar, point
+ pointStateOptionsHover.color =
+ Color(pointStateOptionsHover.color || point.options.color)
+ .brighten(pointStateOptionsHover.brightness ||
+ stateOptionsHover.brightness).get();
+
+ }
+
+ // normal point state inherits series wide normal state
+ pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
+
+ // inherit from point normal and series hover
+ pointAttr[HOVER_STATE] = series.convertAttribs(
+ stateOptions[HOVER_STATE],
+ seriesPointAttr[HOVER_STATE],
+ pointAttr[NORMAL_STATE]
+ );
+ // inherit from point normal and series hover
+ pointAttr[SELECT_STATE] = series.convertAttribs(
+ stateOptions[SELECT_STATE],
+ seriesPointAttr[SELECT_STATE],
+ pointAttr[NORMAL_STATE]
+ );
+
+
+
+ // no marker config object is created: copy a reference to the series-wide
+ // attribute collection
+ } else {
+ pointAttr = seriesPointAttr;
+ }
+
+ point.pointAttr = pointAttr;
+
+ }
+
+ },
+
+
+ /**
+ * Clear DOM objects and free up memory
+ */
+ destroy: function() {
+ var series = this,
+ chart = series.chart,
+ //chartSeries = series.chart.series,
+ clipRect = series.clipRect,
+ issue134 = /\/5[0-9\.]+ (Safari|Mobile)\//.test(userAgent), // todo: update when Safari bug is fixed
+ destroy,
+ prop;
+
+ // remove all events
+ removeEvent(series);
+
+ // remove legend items
+ if (series.legendItem) {
+ series.chart.legend.destroyItem(series);
+ }
+
+ // destroy all points with their elements
+ each(series.data, function(point) {
+ point.destroy();
+ });
+ // destroy all SVGElements associated to the series
+ each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function(prop) {
+ if (series[prop]) {
+
+ // issue 134 workaround
+ destroy = issue134 && prop == 'group' ?
+ 'hide' :
+ 'destroy';
+
+ series[prop][destroy]();
+ }
+ });
+
+ // remove from hoverSeries
+ if (chart.hoverSeries == series) {
+ chart.hoverSeries = null;
+ }
+ erase(chart.series, series);
+
+ // clear all members
+ for (prop in series) {
+ delete series[prop];
+ }
+ },
+
+ /**
+ * Draw the data labels
+ */
+ drawDataLabels: function() {
+ if (this.options.dataLabels.enabled) {
+ var series = this,
+ x,
+ y,
+ data = series.data,
+ options = series.options.dataLabels,
+ str,
+ dataLabelsGroup = series.dataLabelsGroup,
+ chart = series.chart,
+ inverted = chart.inverted,
+ seriesType = series.type,
+ color;
+
+ // create a separate group for the data labels to avoid rotation
+ if (!dataLabelsGroup) {
+ dataLabelsGroup = series.dataLabelsGroup =
+ chart.renderer.g(PREFIX +'data-labels')
+ .attr({
+ visibility: series.visible ? VISIBLE : HIDDEN,
+ zIndex: 5
+ })
+ .translate(chart.plotLeft, chart.plotTop)
+ .add();
+ }
+
+ // determine the color
+ color = options.color;
+ if (color == 'auto') { // 1.0 backwards compatibility
+ color = null;
+ }
+ options.style.color = pick(color, series.color);
+
+ // make the labels for each point
+ each(data, function(point, i){
+ var barX = point.barX,
+ plotX = barX && barX + point.barW / 2 || point.plotX || -999,
+ plotY = pick(point.plotY, -999),
+ dataLabel = point.dataLabel,
+ align = options.align;
+
+ // get the string
+ str = point.getDataLabelText();
+ x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
+ y = (inverted ? chart.plotHeight - plotX : plotY) + options.y;
+
+ // in columns, align the string to the column
+ if (seriesType == 'column') {
+ x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
+ }
+
+
+ if (dataLabel) {
+ dataLabel.animate({
+ x: x,
+ y: y
+ });
+ } else if (defined(str)) {
+ dataLabel = point.dataLabel = chart.renderer.text(
+ str,
+ x,
+ y
+ )
+ .attr({
+ align: align,
+ rotation: options.rotation,
+ zIndex: 1
+ })
+ .css(options.style)
+ .add(dataLabelsGroup);
+ }
+
+ // vertically centered
+ if (inverted && !options.y) {
+ dataLabel.attr({
+ y: y + parseInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
+ });
+ }
+
+ /*if (series.isCartesian) {
+ dataLabel[chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
+ }*/
+
+ });
+ }
+ },
+
+ /**
+ * Draw the actual graph
+ */
+ drawGraph: function(state) {
+ var series = this,
+ options = series.options,
+ chart = series.chart,
+ graph = series.graph,
+ graphPath = [],
+ fillColor,
+ area = series.area,
+ group = series.group,
+ color = options.lineColor || series.color,
+ lineWidth = options.lineWidth,
+ dashStyle = options.dashStyle,
+ segmentPath,
+ renderer = chart.renderer,
+ translatedThreshold = series.yAxis.getThreshold(options.threshold || 0),
+ useArea = /^area/.test(series.type),
+ singlePoints = [], // used in drawTracker
+ areaPath = [],
+ attribs;
+
+
+ // divide into segments and build graph and area paths
+ each(series.segments, function(segment) {
+ segmentPath = [];
+
+ // build the segment line
+ each(segment, function(point, i) {
+
+ if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
+ segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
+
+ } else {
+
+ // moveTo or lineTo
+ segmentPath.push(i ? L : M);
+
+ // step line?
+ if (i && options.step) {
+ var lastPoint = segment[i - 1];
+ segmentPath.push(
+ point.plotX,
+ lastPoint.plotY
+ );
+ }
+
+ // normal line to next point
+ segmentPath.push(
+ point.plotX,
+ point.plotY
+ );
+ }
+ });
+
+ // add the segment to the graph, or a single point for tracking
+ if (segment.length > 1) {
+ graphPath = graphPath.concat(segmentPath);
+ } else {
+ singlePoints.push(segment[0]);
+ }
+
+ // build the area
+ if (useArea) {
+ var areaSegmentPath = [],
+ i,
+ segLength = segmentPath.length;
+ for (i = 0; i < segLength; i++) {
+ areaSegmentPath.push(segmentPath[i]);
+ }
+ if (segLength == 3) { // for animation from 1 to two points
+ areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
+ }
+ if (options.stacking && series.type != 'areaspline') {
+ // follow stack back. Todo: implement areaspline
+ for (i = segment.length - 1; i >= 0; i--) {
+ areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
+ }
+
+ } else { // follow zero line back
+ areaSegmentPath.push(
+ L,
+ segment[segment.length - 1].plotX,
+ translatedThreshold,
+ L,
+ segment[0].plotX,
+ translatedThreshold
+ );
+ }
+ areaPath = areaPath.concat(areaSegmentPath);
+ }
+ });
+
+ // used in drawTracker:
+ series.graphPath = graphPath;
+ series.singlePoints = singlePoints;
+
+ // draw the area if area series or areaspline
+ if (useArea) {
+ fillColor = pick(
+ options.fillColor,
+ Color(series.color).setOpacity(options.fillOpacity || 0.75).get()
+ );
+ if (area) {
+ area.animate({ d: areaPath });
+
+ } else {
+ // draw the area
+ series.area = series.chart.renderer.path(areaPath)
+ .attr({
+ fill: fillColor
+ }).add(group);
+ }
+ }
+
+ // draw the graph
+ if (graph) {
+ //graph.animate({ d: graphPath.join(' ') });
+ graph.animate({ d: graphPath });
+
+ } else {
+ if (lineWidth) {
+ attribs = {
+ 'stroke': color,
+ 'stroke-width': lineWidth
+ };
+ if (dashStyle) {
+ attribs.dashstyle = dashStyle;
+ }
+
+ series.graph = renderer.path(graphPath)
+ .attr(attribs).add(group).shadow(options.shadow);
+ }
+ }
+ },
+
+
+ /**
+ * Render the graph and markers
+ */
+ render: function() {
+ var series = this,
+ chart = series.chart,
+ group,
+ setInvert,
+ options = series.options,
+ animation = options.animation,
+ doAnimation = animation && series.animate,
+ duration = doAnimation ? animation && animation.duration || 500 : 0,
+ clipRect = series.clipRect,
+ renderer = chart.renderer;
+
+
+ // Add plot area clipping rectangle. If this is before chart.hasRendered,
+ // create one shared clipRect.
+ if (!clipRect) {
+ clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
+ chart.clipRect :
+ renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY);
+ if (!chart.clipRect) {
+ chart.clipRect = clipRect;
+ }
+ }
+
+
+ // the group
+ if (!series.group) {
+ group = series.group = renderer.g('series');
+
+ if (chart.inverted) {
+ setInvert = function() {
+ group.attr({
+ width: chart.plotWidth,
+ height: chart.plotHeight
+ }).invert();
+ };
+
+ setInvert(); // do it now
+ addEvent(chart, 'resize', setInvert); // do it on resize
+ }
+ group.clip(series.clipRect)
+ .attr({
+ visibility: series.visible ? VISIBLE : HIDDEN,
+ zIndex: options.zIndex
+ })
+ .translate(chart.plotLeft, chart.plotTop)
+ .add(chart.seriesGroup);
+ }
+
+ series.drawDataLabels();
+
+ // initiate the animation
+ if (doAnimation) {
+ series.animate(true);
+ }
+
+ // cache attributes for shapes
+ series.getAttribs();
+
+ // draw the graph if any
+ if (series.drawGraph) {
+ series.drawGraph();
+ }
+
+ // draw the points
+ series.drawPoints();
+
+ // draw the mouse tracking area
+ if (series.options.enableMouseTracking !== false) {
+ series.drawTracker();
+ }
+
+ // run the animation
+ if (doAnimation) {
+ series.animate();
+ }
+
+ // finish the individual clipRect
+ setTimeout(function() {
+ clipRect.isAnimating = false;
+ group = series.group; // can be destroyed during the timeout
+ if (group && clipRect != chart.clipRect && clipRect.renderer) {
+ group.clip((series.clipRect = chart.clipRect));
+ clipRect.destroy();
+ }
+ }, duration);
+
+
+ series.isDirty = false; // means data is in accordance with what you see
+
+ },
+
+ /**
+ * Redraw the series after an update in the axes.
+ */
+ redraw: function() {
+ var series = this,
+ chart = series.chart,
+ clipRect = series.clipRect,
+ group = series.group;
+
+ /*if (clipRect) {
+ stop(clipRect);
+ clipRect.animate({ // for chart resize
+ width: chart.plotSizeX,
+ height: chart.plotSizeY
+ });
+ }*/
+
+ // reposition on resize
+ if (group) {
+ if (chart.inverted) {
+ group.attr({
+ width: chart.plotWidth,
+ height: chart.plotHeight
+ });
+ }
+
+ group.animate({
+ translateX: chart.plotLeft,
+ translateY: chart.plotTop
+ });
+ }
+
+ series.translate();
+ series.setTooltipPoints(true);
+ series.render();
+ },
+
+ /**
+ * Set the state of the graph
+ */
+ setState: function(state) {
+ var series = this,
+ options = series.options,
+ graph = series.graph,
+ stateOptions = options.states,
+ lineWidth = options.lineWidth;
+
+ state = state || NORMAL_STATE;
+
+ if (series.state != state) {
+ series.state = state;
+
+ if (stateOptions[state] && stateOptions[state].enabled === false) {
+ return;
+ }
+
+ if (state) {
+ lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
+ }
+
+ if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
+ graph.attr({ // use attr because animate will cause any other animation on the graph to stop
+ 'stroke-width': lineWidth
+ }, state ? 0 : 500);
+ }
+ }
+ },
+
+ /**
+ * Set the visibility of the graph
+ *
+ * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
+ * the visibility is toggled.
+ */
+ setVisible: function(vis, redraw) {
+ var series = this,
+ chart = series.chart,
+ legendItem = series.legendItem,
+ seriesGroup = series.group,
+ seriesTracker = series.tracker,
+ dataLabelsGroup = series.dataLabelsGroup,
+ showOrHide,
+ i,
+ data = series.data,
+ point,
+ ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
+ oldVisibility = series.visible;
+
+ // if called without an argument, toggle visibility
+ series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
+ showOrHide = vis ? 'show' : 'hide';
+
+ // show or hide series
+ if (seriesGroup) { // pies don't have one
+ seriesGroup[showOrHide]();
+ }
+
+ // show or hide trackers
+ if (seriesTracker) {
+ seriesTracker[showOrHide]();
+ } else {
+ i = data.length;
+ while (i--) {
+ point = data[i];
+ if (point.tracker) {
+ point.tracker[showOrHide]();
+ }
+ }
+ }
+
+
+ if (dataLabelsGroup) {
+ dataLabelsGroup[showOrHide]();
+ }
+
+ if (legendItem) {
+ chart.legend.colorizeItem(series, vis);
+ }
+
+
+ // rescale or adapt to resized chart
+ series.isDirty = true;
+ // in a stack, all other series are affected
+ if (series.options.stacking) {
+ each(chart.series, function(otherSeries) {
+ if (otherSeries.options.stacking && otherSeries.visible) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+
+ if (ignoreHiddenSeries) {
+ chart.isDirtyBox = true;
+ }
+ if (redraw !== false) {
+ chart.redraw();
+ }
+
+ fireEvent(series, showOrHide);
+ },
+
+ /**
+ * Show the graph
+ */
+ show: function() {
+ this.setVisible(true);
+ },
+
+ /**
+ * Hide the graph
+ */
+ hide: function() {
+ this.setVisible(false);
+ },
+
+
+ /**
+ * Set the selected state of the graph
+ *
+ * @param selected {Boolean} True to select the series, false to unselect. If
+ * UNDEFINED, the selection state is toggled.
+ */
+ select: function(selected) {
+ var series = this;
+ // if called without an argument, toggle
+ series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
+
+ if (series.checkbox) {
+ series.checkbox.checked = selected;
+ }
+
+ fireEvent(series, selected ? 'select' : 'unselect');
+ },
+
+
+ /**
+ * Draw the tracker object that sits above all data labels and markers to
+ * track mouse events on the graph or points. For the line type charts
+ * the tracker uses the same graphPath, but with a greater stroke width
+ * for better control.
+ */
+ drawTracker: function() {
+ var series = this,
+ options = series.options,
+ trackerPath = [].concat(series.graphPath),
+ trackerPathLength = trackerPath.length,
+ chart = series.chart,
+ snap = chart.options.tooltip.snap,
+ tracker = series.tracker,
+ cursor = options.cursor,
+ css = cursor && { cursor: cursor },
+ singlePoints = series.singlePoints,
+ singlePoint,
+ i;
+
+ // Extend end points. A better way would be to use round linecaps,
+ // but those are not clickable in VML.
+ if (trackerPathLength) {
+ i = trackerPathLength + 1;
+ while (i--) {
+ if (trackerPath[i] == M) { // extend left side
+ trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
+ }
+ if ((i && trackerPath[i] == M) || i == trackerPathLength) { // extend right side
+ trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
+ }
+ }
+ }
+
+ // handle single points
+ for (i = 0; i < singlePoints.length; i++) {
+ singlePoint = singlePoints[i];
+ trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
+ L, singlePoint.plotX + snap, singlePoint.plotY);
+ }
+
+ // draw the tracker
+ if (tracker) {
+ tracker.attr({ d: trackerPath });
+
+ } else { // create
+ series.tracker = chart.renderer.path(trackerPath)
+ .attr({
+ isTracker: true,
+ stroke: TRACKER_FILL,
+ fill: NONE,
+ 'stroke-width' : options.lineWidth + 2 * snap,
+ visibility: series.visible ? VISIBLE : HIDDEN,
+ zIndex: 1
+ })
+ .on(hasTouch ? 'touchstart' : 'mouseover', function() {
+ if (chart.hoverSeries != series) {
+ series.onMouseOver();
+ }
+ })
+ .on('mouseout', function() {
+ if (!options.stickyTracking) {
+ series.onMouseOut();
+ }
+ })
+ .css(css)
+ .add(chart.trackerGroup);
+ }
+
+ }
+
+}; // end Series prototype
+
+
+/**
+ * LineSeries object
+ */
+var LineSeries = extendClass(Series);
+seriesTypes.line = LineSeries;
+
+/**
+ * AreaSeries object
+ */
+var AreaSeries = extendClass(Series, {
+ type: 'area'
+});
+seriesTypes.area = AreaSeries;
+
+
+
+
+/**
+ * SplineSeries object
+ */
+var SplineSeries = extendClass( Series, {
+ type: 'spline',
+
+ /**
+ * Draw the actual graph
+ */
+ getPointSpline: function(segment, point, i) {
+ var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
+ denom = smoothing + 1,
+ plotX = point.plotX,
+ plotY = point.plotY,
+ lastPoint = segment[i - 1],
+ nextPoint = segment[i + 1],
+ leftContX,
+ leftContY,
+ rightContX,
+ rightContY,
+ ret;
+
+ // find control points
+ if (i && i < segment.length - 1) {
+ var lastX = lastPoint.plotX,
+ lastY = lastPoint.plotY,
+ nextX = nextPoint.plotX,
+ nextY = nextPoint.plotY,
+ correction;
+
+ leftContX = (smoothing * plotX + lastX) / denom;
+ leftContY = (smoothing * plotY + lastY) / denom;
+ rightContX = (smoothing * plotX + nextX) / denom;
+ rightContY = (smoothing * plotY + nextY) / denom;
+
+ // have the two control points make a straight line through main point
+ correction = ((rightContY - leftContY) * (rightContX - plotX)) /
+ (rightContX - leftContX) + plotY - rightContY;
+
+ leftContY += correction;
+ rightContY += correction;
+
+ // to prevent false extremes, check that control points are between
+ // neighbouring points' y values
+ if (leftContY > lastY && leftContY > plotY) {
+ leftContY = mathMax(lastY, plotY);
+ rightContY = 2 * plotY - leftContY; // mirror of left control point
+ } else if (leftContY < lastY && leftContY < plotY) {
+ leftContY = mathMin(lastY, plotY);
+ rightContY = 2 * plotY - leftContY;
+ }
+ if (rightContY > nextY && rightContY > plotY) {
+ rightContY = mathMax(nextY, plotY);
+ leftContY = 2 * plotY - rightContY;
+ } else if (rightContY < nextY && rightContY < plotY) {
+ rightContY = mathMin(nextY, plotY);
+ leftContY = 2 * plotY - rightContY;
+ }
+
+ // record for drawing in next point
+ point.rightContX = rightContX;
+ point.rightContY = rightContY;
+
+ }
+
+ // moveTo or lineTo
+ if (!i) {
+ ret = [M, plotX, plotY];
+ }
+
+ // curve from last point to this
+ else {
+ ret = [
+ 'C',
+ lastPoint.rightContX || lastPoint.plotX,
+ lastPoint.rightContY || lastPoint.plotY,
+ leftContX || plotX,
+ leftContY || plotY,
+ plotX,
+ plotY
+ ];
+ lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
+ }
+ return ret;
+ }
+});
+seriesTypes.spline = SplineSeries;
+
+
+
+/**
+ * AreaSplineSeries object
+ */
+var AreaSplineSeries = extendClass(SplineSeries, {
+ type: 'areaspline'
+});
+seriesTypes.areaspline = AreaSplineSeries;
+
+/**
+ * ColumnSeries object
+ */
+var ColumnSeries = extendClass(Series, {
+ type: 'column',
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
+ stroke: 'borderColor',
+ 'stroke-width': 'borderWidth',
+ fill: 'color',
+ r: 'borderRadius'
+ },
+ init: function() {
+ Series.prototype.init.apply(this, arguments);
+
+ var series = this,
+ chart = series.chart;
+
+ // flag the chart in order to pad the x axis
+ chart.hasColumn = true;
+
+ // if the series is added dynamically, force redraw of other
+ // series affected by a new column
+ if (chart.hasRendered) {
+ each(chart.series, function(otherSeries) {
+ if (otherSeries.type == series.type) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+ },
+
+ /**
+ * Translate each point to the plot area coordinate system and find shape positions
+ */
+ translate: function() {
+ var series = this,
+ chart = series.chart,
+ columnCount = 0,
+ reversedXAxis = series.xAxis.reversed,
+ categories = series.xAxis.categories,
+ stackGroups = {},
+ stackKey,
+ columnIndex;
+
+ Series.prototype.translate.apply(series);
+
+ // Get the total number of column type series.
+ // This is called on every series. Consider moving this logic to a
+ // chart.orderStacks() function and call it on init, addSeries and removeSeries
+ each(chart.series, function(otherSeries) {
+ if (otherSeries.type == series.type) {
+ if (otherSeries.options.stacking) {
+ stackKey = otherSeries.stackKey;
+ if (stackGroups[stackKey] === UNDEFINED) {
+ stackGroups[stackKey] = columnCount++;
+ }
+ columnIndex = stackGroups[stackKey];
+ } else if (otherSeries.visible){
+ columnIndex = columnCount++;
+ }
+ otherSeries.columnIndex = columnIndex;
+ }
+ });
+
+ // calculate the width and position of each column based on
+ // the number of column series in the plot, the groupPadding
+ // and the pointPadding options
+ var options = series.options,
+ data = series.data,
+ closestPoints = series.closestPoints,
+ categoryWidth = mathAbs(
+ data[1] ? data[closestPoints].plotX - data[closestPoints - 1].plotX :
+ chart.plotSizeX / (categories ? categories.length : 1)
+ ),
+ groupPadding = categoryWidth * options.groupPadding,
+ groupWidth = categoryWidth - 2 * groupPadding,
+ pointOffsetWidth = groupWidth / columnCount,
+ optionPointWidth = options.pointWidth,
+ pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
+ pointOffsetWidth * options.pointPadding,
+ pointWidth = mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1),
+ colIndex = (reversedXAxis ? columnCount -
+ series.columnIndex : series.columnIndex) || 0,
+ pointXOffset = pointPadding + (groupPadding + colIndex *
+ pointOffsetWidth -(categoryWidth / 2)) *
+ (reversedXAxis ? -1 : 1),
+ threshold = options.threshold || 0,
+ translatedThreshold = series.yAxis.getThreshold(threshold),
+ minPointLength = pick(options.minPointLength, 5);
+
+ // record the new values
+ each(data, function(point) {
+ var plotY = point.plotY,
+ yBottom = point.yBottom || translatedThreshold,
+ barX = point.plotX + pointXOffset,
+ barY = mathCeil(mathMin(plotY, yBottom)),
+ barH = mathCeil(mathMax(plotY, yBottom) - barY),
+ trackerY;
+
+ // handle options.minPointLength and tracker for small points
+ if (mathAbs(barH) < minPointLength) {
+ if (minPointLength) {
+ barH = minPointLength;
+ barY =
+ mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
+ yBottom - minPointLength : // keep position
+ translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
+ }
+ trackerY = barY - 3;
+ }
+
+ extend(point, {
+ barX: barX,
+ barY: barY,
+ barW: pointWidth,
+ barH: barH
+ });
+ point.shapeType = 'rect';
+ point.shapeArgs = {
+ x: barX,
+ y: barY,
+ width: pointWidth,
+ height: barH,
+ r: options.borderRadius
+ };
+
+ // make small columns responsive to mouse
+ point.trackerArgs = defined(trackerY) && merge(point.shapeArgs, {
+ height: mathMax(6, barH + 3),
+ y: trackerY
+ });
+ });
+
+ },
+
+ getSymbol: function(){
+ },
+
+ /**
+ * Columns have no graph
+ */
+ drawGraph: function() {},
+
+ /**
+ * Draw the columns. For bars, the series.group is rotated, so the same coordinates
+ * apply for columns and bars. This method is inherited by scatter series.
+ *
+ */
+ drawPoints: function() {
+ var series = this,
+ options = series.options,
+ renderer = series.chart.renderer,
+ graphic,
+ shapeArgs;
+
+
+ // draw the columns
+ each(series.data, function(point) {
+ var plotY = point.plotY;
+ if (plotY !== UNDEFINED && !isNaN(plotY)) {
+ graphic = point.graphic;
+ shapeArgs = point.shapeArgs;
+ if (graphic) { // update
+ stop(graphic);
+ graphic.animate(shapeArgs);
+
+ } else {
+ point.graphic = renderer[point.shapeType](shapeArgs)
+ .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
+ .add(series.group)
+ .shadow(options.shadow);
+ }
+
+ }
+ });
+ },
+ /**
+ * Draw the individual tracker elements.
+ * This method is inherited by scatter and pie charts too.
+ */
+ drawTracker: function() {
+ var series = this,
+ chart = series.chart,
+ renderer = chart.renderer,
+ shapeArgs,
+ tracker,
+ trackerLabel = +new Date(),
+ cursor = series.options.cursor,
+ css = cursor && { cursor: cursor },
+ rel;
+
+ each(series.data, function(point) {
+ tracker = point.tracker;
+ shapeArgs = point.trackerArgs || point.shapeArgs;
+ if (point.y !== null) {
+ if (tracker) {// update
+ tracker.attr(shapeArgs);
+
+ } else {
+ point.tracker =
+ renderer[point.shapeType](shapeArgs)
+ .attr({
+ isTracker: trackerLabel,
+ fill: TRACKER_FILL,
+ visibility: series.visible ? VISIBLE : HIDDEN,
+ zIndex: 1
+ })
+ .on(hasTouch ? 'touchstart' : 'mouseover', function(event) {
+ rel = event.relatedTarget || event.fromElement;
+ if (chart.hoverSeries != series && attr(rel, 'isTracker') != trackerLabel) {
+ series.onMouseOver();
+ }
+ point.onMouseOver();
+
+ })
+ .on('mouseout', function(event) {
+ if (!series.options.stickyTracking) {
+ rel = event.relatedTarget || event.toElement;
+ if (attr(rel, 'isTracker') != trackerLabel) {
+ series.onMouseOut();
+ }
+ }
+ })
+ .css(css)
+ .add(chart.trackerGroup);
+ }
+ }
+ });
+ },
+
+
+ /**
+ * Animate the column heights one by one from zero
+ * @param {Boolean} init Whether to initialize the animation or run it
+ */
+ animate: function(init) {
+ var series = this,
+ data = series.data;
+
+ if (!init) { // run the animation
+ /*
+ * Note: Ideally the animation should be initialized by calling
+ * series.group.hide(), and then calling series.group.show()
+ * after the animation was started. But this rendered the shadows
+ * invisible in IE8 standards mode. If the columns flicker on large
+ * datasets, this is the cause.
+ */
+
+ each(data, function(point) {
+ var graphic = point.graphic;
+
+ if (graphic) {
+ // start values
+ graphic.attr({
+ height: 0,
+ y: series.yAxis.translate(0, 0, 1)
+ });
+
+ // animate
+ graphic.animate({
+ height: point.barH,
+ y: point.barY
+ }, series.options.animation);
+ }
+ });
+
+
+ // delete this function to allow it only once
+ series.animate = null;
+ }
+
+ },
+ /**
+ * Remove this series from the chart
+ */
+ remove: function() {
+ var series = this,
+ chart = series.chart;
+
+ // column and bar series affects other series of the same type
+ // as they are either stacked or grouped
+ if (chart.hasRendered) {
+ each(chart.series, function(otherSeries) {
+ if (otherSeries.type == series.type) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+
+ Series.prototype.remove.apply(series, arguments);
+ }
+});
+seriesTypes.column = ColumnSeries;
+
+var BarSeries = extendClass(ColumnSeries, {
+ type: 'bar',
+ init: function(chart) {
+ chart.inverted = this.inverted = true;
+ ColumnSeries.prototype.init.apply(this, arguments);
+ }
+});
+seriesTypes.bar = BarSeries;
+
+/**
+ * The scatter series class
+ */
+var ScatterSeries = extendClass(Series, {
+ type: 'scatter',
+
+ /**
+ * Extend the base Series' translate method by adding shape type and
+ * arguments for the point trackers
+ */
+ translate: function() {
+ var series = this;
+
+ Series.prototype.translate.apply(series);
+
+ each(series.data, function(point) {
+ point.shapeType = 'circle';
+ point.shapeArgs = {
+ x: point.plotX,
+ y: point.plotY,
+ r: series.chart.options.tooltip.snap
+ };
+ });
+ },
+
+
+ /**
+ * Create individual tracker elements for each point
+ */
+ //drawTracker: ColumnSeries.prototype.drawTracker,
+ drawTracker: function() {
+ var series = this,
+ cursor = series.options.cursor,
+ css = cursor && { cursor: cursor },
+ graphic;
+
+ each(series.data, function(point) {
+ graphic = point.graphic;
+ if (graphic) { // doesn't exist for null points
+ graphic
+ .attr({ isTracker: true })
+ .on('mouseover', function(event) {
+ series.onMouseOver();
+ point.onMouseOver();
+ })
+ .on('mouseout', function(event) {
+ if (!series.options.stickyTracking) {
+ series.onMouseOut();
+ }
+ })
+ .css(css);
+ }
+ });
+
+ },
+
+ /**
+ * Cleaning the data is not necessary in a scatter plot
+ */
+ cleanData: function() {}
+});
+seriesTypes.scatter = ScatterSeries;
+
+/**
+ * Extended point object for pies
+ */
+var PiePoint = extendClass(Point, {
+ /**
+ * Initiate the pie slice
+ */
+ init: function () {
+
+ Point.prototype.init.apply(this, arguments);
+
+ var point = this,
+ toggleSlice;
+
+ //visible: options.visible !== false,
+ extend(point, {
+ visible: point.visible !== false,
+ name: pick(point.name, 'Slice')
+ });
+
+ // add event listener for select
+ toggleSlice = function() {
+ point.slice();
+ };
+ addEvent(point, 'select', toggleSlice);
+ addEvent(point, 'unselect', toggleSlice);
+
+ return point;
+ },
+
+ /**
+ * Toggle the visibility of the pie slice
+ * @param {Boolean} vis Whether to show the slice or not. If undefined, the
+ * visibility is toggled
+ */
+ setVisible: function(vis) {
+ var point = this,
+ chart = point.series.chart,
+ tracker = point.tracker,
+ dataLabel = point.dataLabel,
+ connector = point.connector,
+ method;
+
+ // if called without an argument, toggle visibility
+ point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
+
+ method = vis ? 'show' : 'hide';
+
+ point.group[method]();
+ if (tracker) {
+ tracker[method]();
+ }
+ if (dataLabel) {
+ dataLabel[method]();
+ }
+ if (connector) {
+ connector[method]();
+ }
+ if (point.legendItem) {
+ chart.legend.colorizeItem(point, vis);
+ }
+ },
+
+ /**
+ * Set or toggle whether the slice is cut out from the pie
+ * @param {Boolean} sliced When undefined, the slice state is toggled
+ * @param {Boolean} redraw Whether to redraw the chart. True by default.
+ */
+ slice: function(sliced, redraw, animation) {
+ var point = this,
+ series = point.series,
+ chart = series.chart,
+ slicedTranslation = point.slicedTranslation;
+
+ setAnimation(animation, chart);
+
+ // redraw is true by default
+ redraw = pick(redraw, true);
+
+ // if called without an argument, toggle
+ sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
+
+ point.group.animate({
+ translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
+ translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
+ });
+
+ }
+});
+
+/**
+ * The Pie series class
+ */
+var PieSeries = extendClass(Series, {
+ type: 'pie',
+ isCartesian: false,
+ pointClass: PiePoint,
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
+ stroke: 'borderColor',
+ 'stroke-width': 'borderWidth',
+ fill: 'color'
+ },
+
+ /**
+ * Pies have one color each point
+ */
+ getColor: function() {
+ // record first color for use in setData
+ this.initialColor = colorCounter;
+ },
+
+ /**
+ * Animate the column heights one by one from zero
+ * @param {Boolean} init Whether to initialize the animation or run it
+ */
+ animate: function(init) {
+ var series = this,
+ data = series.data;
+
+ each(data, function(point) {
+ var graphic = point.graphic,
+ args = point.shapeArgs,
+ up = -mathPI / 2;
+
+ if (graphic) {
+ // start values
+ graphic.attr({
+ r: 0,
+ start: up,
+ end: up
+ });
+
+ // animate
+ graphic.animate({
+ r: args.r,
+ start: args.start,
+ end: args.end
+ }, series.options.animation);
+ }
+ });
+
+ // delete this function to allow it only once
+ series.animate = null;
+
+ },
+ /**
+ * Do translation for pie slices
+ */
+ translate: function() {
+ var total = 0,
+ series = this,
+ cumulative = -0.25, // start at top
+ precision = 1000, // issue #172
+ options = series.options,
+ slicedOffset = options.slicedOffset,
+ connectorOffset = slicedOffset + options.borderWidth,
+ positions = options.center,
+ chart = series.chart,
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ start,
+ end,
+ angle,
+ data = series.data,
+ circ = 2 * mathPI,
+ fraction,
+ smallestSize = mathMin(plotWidth, plotHeight),
+ isPercent,
+ radiusX, // the x component of the radius vector for a given point
+ radiusY,
+ labelDistance = options.dataLabels.distance;
+
+ // get positions - either an integer or a percentage string must be given
+ positions.push(options.size, options.innerSize || 0);
+ positions = map(positions, function(length, i) {
+
+ isPercent = /%$/.test(length);
+ return isPercent ?
+ // i == 0: centerX, relative to width
+ // i == 1: centerY, relative to height
+ // i == 2: size, relative to smallestSize
+ [plotWidth, plotHeight, smallestSize, smallestSize][i] *
+ pInt(length) / 100:
+ length;
+ });
+
+ // utility for getting the x value from a given y, used for anticollision logic in data labels
+ series.getX = function(y, left) {
+
+ angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
+
+ return positions[0] +
+ (left ? -1 : 1) *
+ (mathCos(angle) * (positions[2] / 2 + labelDistance));
+ };
+
+ // set center for later use
+ series.center = positions;
+
+ // get the total sum
+ each(data, function(point) {
+ total += point.y;
+ });
+
+ each(data, function(point) {
+ // set start and end angle
+ fraction = total ? point.y / total : 0;
+ start = mathRound(cumulative * circ * precision) / precision;
+ cumulative += fraction;
+ end = mathRound(cumulative * circ * precision) / precision;
+
+ // set the shape
+ point.shapeType = 'arc';
+ point.shapeArgs = {
+ x: positions[0],
+ y: positions[1],
+ r: positions[2] / 2,
+ innerR: positions[3] / 2,
+ start: start,
+ end: end
+ };
+
+ // center for the sliced out slice
+ angle = (end + start) / 2;
+ point.slicedTranslation = map([
+ mathCos(angle) * slicedOffset + chart.plotLeft,
+ mathSin(angle) * slicedOffset + chart.plotTop
+ ], mathRound);
+
+ // set the anchor point for tooltips
+ radiusX = mathCos(angle) * positions[2] / 2;
+ radiusY = mathSin(angle) * positions[2] / 2;
+ point.tooltipPos = [
+ positions[0] + radiusX * 0.7,
+ positions[1] + radiusY * 0.7
+ ];
+
+ // set the anchor point for data labels
+ point.labelPos = [
+ positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
+ positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
+ positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
+ positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
+ positions[0] + radiusX, // landing point for connector
+ positions[1] + radiusY, // a/a
+ labelDistance < 0 ? // alignment
+ 'center' :
+ angle < circ / 4 ? 'left' : 'right', // alignment
+ angle // center angle
+ ];
+
+
+ // API properties
+ point.percentage = fraction * 100;
+ point.total = total;
+
+ });
+
+ this.setTooltipPoints();
+ },
+
+ /**
+ * Render the slices
+ */
+ render: function() {
+ var series = this;
+
+ // cache attributes for shapes
+ series.getAttribs();
+
+ this.drawPoints();
+
+ // draw the mouse tracking area
+ if (series.options.enableMouseTracking !== false) {
+ series.drawTracker();
+ }
+
+ this.drawDataLabels();
+
+ if (series.options.animation && series.animate) {
+ series.animate();
+ }
+
+ series.isDirty = false; // means data is in accordance with what you see
+ },
+
+ /**
+ * Draw the data points
+ */
+ drawPoints: function() {
+ var series = this,
+ chart = series.chart,
+ renderer = chart.renderer,
+ groupTranslation,
+ //center,
+ graphic,
+ group,
+ shapeArgs;
+
+ // draw the slices
+ each(series.data, function(point) {
+ graphic = point.graphic;
+ shapeArgs = point.shapeArgs;
+ group = point.group;
+
+ // create the group the first time
+ if (!group) {
+ group = point.group = renderer.g('point')
+ .attr({ zIndex: 5 })
+ .add();
+ }
+
+ // if the point is sliced, use special translation, else use plot area traslation
+ groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
+ group.translate(groupTranslation[0], groupTranslation[1])
+
+
+ // draw the slice
+ if (graphic) {
+ graphic.animate(shapeArgs);
+ } else {
+ point.graphic =
+ renderer.arc(shapeArgs)
+ .attr(extend(
+ point.pointAttr[NORMAL_STATE],
+ { 'stroke-linejoin': 'round' }
+ ))
+ .add(point.group);
+ }
+
+ // detect point specific visibility
+ if (point.visible === false) {
+ point.setVisible(false);
+ }
+
+ });
+
+ },
+
+ /**
+ * Override the base drawDataLabels method by pie specific functionality
+ */
+ drawDataLabels: function() {
+ var series = this,
+ data = series.data,
+ point,
+ chart = series.chart,
+ options = series.options.dataLabels,
+ connectorPadding = pick(options.connectorPadding, 10),
+ connectorWidth = pick(options.connectorWidth, 1),
+ connector,
+ connectorPath,
+ outside = options.distance > 0,
+ dataLabel,
+ labelPos,
+ labelHeight,
+ lastY,
+ centerY = series.center[1],
+ quarters = [// divide the points into quarters for anti collision
+ [], // top right
+ [], // bottom right
+ [], // bottom left
+ [] // top left
+ ],
+ x,
+ y,
+ visibility,
+ overlapping,
+ rankArr,
+ secondPass,
+ sign,
+ lowerHalf,
+ sort,
+ i = 4,
+ j;
+
+ // run parent method
+ Series.prototype.drawDataLabels.apply(series);
+
+ // arrange points for detection collision
+ each(data, function(point) {
+ var angle = point.labelPos[7],
+ quarter;
+ if (angle < 0) {
+ quarter = 0;
+ } else if (angle < mathPI / 2) {
+ quarter = 1;
+ } else if (angle < mathPI) {
+ quarter = 2;
+ } else {
+ quarter = 3;
+ }
+ quarters[quarter].push(point);
+ });
+ quarters[1].reverse();
+ quarters[3].reverse();
+
+ // define the sorting algorithm
+ sort = function(a,b) {
+ return a.y > b.y;
+ };
+ /* Loop over the points in each quartile, starting from the top and bottom
+ * of the pie to detect overlapping labels.
+ */
+ while (i--) {
+ overlapping = 0;
+
+ // create an array for sorting and ranking the points within each quarter
+ rankArr = [].concat(quarters[i]);
+ rankArr.sort(sort);
+ j = rankArr.length;
+ while (j--) {
+ rankArr[j].rank = j;
+ }
+
+ /* In the first pass, count the number of overlapping labels. In the second
+ * pass, remove the labels with lowest rank/values.
+ */
+ for (secondPass = 0; secondPass < 2; secondPass++) {
+ lowerHalf = i % 3;
+ lastY = lowerHalf ? 9999 : -9999;
+ sign = lowerHalf ? -1 : 1;
+
+ for (j = 0; j < quarters[i].length; j++) {
+ point = quarters[i][j];
+
+ if ((dataLabel = point.dataLabel)) {
+ labelPos = point.labelPos;
+ visibility = VISIBLE;
+ x = labelPos[0];
+ y = labelPos[1];
+
+
+ // assume all labels have equal height
+ if (!labelHeight) {
+ labelHeight = dataLabel && dataLabel.getBBox().height;
+ }
+
+ // anticollision
+ if (outside) {
+ if (secondPass && point.rank < overlapping) {
+ visibility = HIDDEN;
+ } else if ((!lowerHalf && y < lastY + labelHeight) ||
+ (lowerHalf && y > lastY - labelHeight)) {
+ y = lastY + sign * labelHeight;
+ x = series.getX(y, i > 1);
+ if ((!lowerHalf && y + labelHeight > centerY) ||
+ (lowerHalf && y -labelHeight < centerY)) {
+ if (secondPass) {
+ visibility = HIDDEN;
+ } else {
+ overlapping++;
+ }
+ }
+ }
+ }
+
+ if (point.visible === false) {
+ visibility = HIDDEN;
+ }
+
+ if (visibility == VISIBLE) {
+ lastY = y;
+ }
+
+ if (secondPass) {
+
+ // move or place the data label
+ dataLabel
+ .attr({
+ visibility: visibility,
+ align: labelPos[6]
+ })
+ [dataLabel.moved ? 'animate' : 'attr']({
+ x: x + options.x +
+ ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
+ y: y + options.y
+ });
+ dataLabel.moved = true;
+
+ // draw the connector
+ if (outside && connectorWidth) {
+ connector = point.connector;
+
+ connectorPath = [
+ M,
+ x + (labelPos[6] == 'left' ? 5 : -5), y, // end of the string at the label
+ L,
+ x, y, // first break, next to the label
+ L,
+ labelPos[2], labelPos[3], // second break
+ L,
+ labelPos[4], labelPos[5] // base
+ ];
+
+ if (connector) {
+ connector.animate({ d: connectorPath });
+ connector.attr('visibility', visibility);
+
+ } else {
+ point.connector = connector = series.chart.renderer.path(connectorPath).attr({
+ 'stroke-width': connectorWidth,
+ stroke: options.connectorColor || '#606060',
+ visibility: visibility,
+ zIndex: 3
+ })
+ .translate(chart.plotLeft, chart.plotTop)
+ .add();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Draw point specific tracker objects. Inherit directly from column series.
+ */
+ drawTracker: ColumnSeries.prototype.drawTracker,
+
+ /**
+ * Pies don't have point marker symbols
+ */
+ getSymbol: function() {}
+
+});
+seriesTypes.pie = PieSeries;
+
+
+// global variables
+win.Highcharts = {
+ Chart: Chart,
+ dateFormat: dateFormat,
+ pathAnim: pathAnim,
+ getOptions: getOptions,
+ numberFormat: numberFormat,
+ Point: Point,
+ Color: Color,
+ Renderer: Renderer,
+ seriesTypes: seriesTypes,
+ setOptions: setOptions,
+ Series: Series,
+
+ // Expose utility funcitons for modules
+ addEvent: addEvent,
+ createElement: createElement,
+ discardElement: discardElement,
+ css: css,
+ each: each,
+ extend: extend,
+ map: map,
+ merge: merge,
+ pick: pick,
+ extendClass: extendClass,
+ version: '2.1.4'
+};
+})();
+
diff --git a/js/jquery/jquery.cookie.js b/js/jquery/jquery.cookie.js
new file mode 100644
index 0000000..61d3bce
--- /dev/null
+++ b/js/jquery/jquery.cookie.js
@@ -0,0 +1,91 @@
+/*jslint browser: true */ /*global jQuery: true */
+
+/**
+ * jQuery Cookie plugin
+ *
+ * Copyright (c) 2010 Klaus Hartl (stilbuero.de)
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+
+// TODO JsDoc
+
+/**
+ * Create a cookie with the given key and value and other optional parameters.
+ *
+ * @example $.cookie('the_cookie', 'the_value');
+ * @desc Set the value of a cookie.
+ * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
+ * @desc Create a cookie with all available options.
+ * @example $.cookie('the_cookie', 'the_value');
+ * @desc Create a session cookie.
+ * @example $.cookie('the_cookie', null);
+ * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
+ * used when the cookie was set.
+ *
+ * @param String key The key of the cookie.
+ * @param String value The value of the cookie.
+ * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
+ * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
+ * If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
+ * If set to null or omitted, the cookie will be a session cookie and will not be retained
+ * when the the browser exits.
+ * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
+ * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
+ * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
+ * require a secure protocol (like HTTPS).
+ * @type undefined
+ *
+ * @name $.cookie
+ * @cat Plugins/Cookie
+ * @author Klaus Hartl/klaus.hartl(a)stilbuero.de
+ */
+
+/**
+ * Get the value of a cookie with the given key.
+ *
+ * @example $.cookie('the_cookie');
+ * @desc Get the value of a cookie.
+ *
+ * @param String key The key of the cookie.
+ * @return The value of the cookie.
+ * @type String
+ *
+ * @name $.cookie
+ * @cat Plugins/Cookie
+ * @author Klaus Hartl/klaus.hartl(a)stilbuero.de
+ */
+jQuery.cookie = function (key, value, options) {
+
+ // key and at least value given, set cookie...
+ if (arguments.length > 1 && String(value) !== "[object Object]") {
+ options = jQuery.extend({}, options);
+
+ if (value === null || value === undefined) {
+ options.expires = -1;
+ }
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setDate(t.getDate() + days);
+ }
+
+ value = String(value);
+
+ return (document.cookie = [
+ encodeURIComponent(key), '=',
+ options.raw ? value : encodeURIComponent(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // key and possibly options given, get cookie...
+ options = value || {};
+ var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
+ return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
+};
diff --git a/js/jquery/jquery.tablesorter.js b/js/jquery/jquery.tablesorter.js
new file mode 100644
index 0000000..e8e2323
--- /dev/null
+++ b/js/jquery/jquery.tablesorter.js
@@ -0,0 +1,1031 @@
+/*
+ *
+ * TableSorter 2.0 - Client-side table sorting with ease!
+ * Version 2.0.5b
+ * @requires jQuery v1.2.3
+ *
+ * Copyright (c) 2007 Christian Bach
+ * Examples and docs at: http://tablesorter.com
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+/**
+ *
+ * @description Create a sortable table with multi-column sorting capabilitys
+ *
+ * @example $('table').tablesorter();
+ * @desc Create a simple tablesorter interface.
+ *
+ * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] });
+ * @desc Create a tablesorter interface and sort on the first and secound column column headers.
+ *
+ * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
+ *
+ * @desc Create a tablesorter interface and disableing the first and second column headers.
+ *
+ *
+ * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } });
+ *
+ * @desc Create a tablesorter interface and set a column parser for the first
+ * and second column.
+ *
+ *
+ * @param Object
+ * settings An object literal containing key/value pairs to provide
+ * optional settings.
+ *
+ *
+ * @option String cssHeader (optional) A string of the class name to be appended
+ * to sortable tr elements in the thead of the table. Default value:
+ * "header"
+ *
+ * @option String cssAsc (optional) A string of the class name to be appended to
+ * sortable tr elements in the thead on a ascending sort. Default value:
+ * "headerSortUp"
+ *
+ * @option String cssDesc (optional) A string of the class name to be appended
+ * to sortable tr elements in the thead on a descending sort. Default
+ * value: "headerSortDown"
+ *
+ * @option String sortInitialOrder (optional) A string of the inital sorting
+ * order can be asc or desc. Default value: "asc"
+ *
+ * @option String sortMultisortKey (optional) A string of the multi-column sort
+ * key. Default value: "shiftKey"
+ *
+ * @option String textExtraction (optional) A string of the text-extraction
+ * method to use. For complex html structures inside td cell set this
+ * option to "complex", on large tables the complex option can be slow.
+ * Default value: "simple"
+ *
+ * @option Object headers (optional) An array containing the forces sorting
+ * rules. This option let's you specify a default sorting rule. Default
+ * value: null
+ *
+ * @option Array sortList (optional) An array containing the forces sorting
+ * rules. This option let's you specify a default sorting rule. Default
+ * value: null
+ *
+ * @option Array sortForce (optional) An array containing forced sorting rules.
+ * This option let's you specify a default sorting rule, which is
+ * prepended to user-selected rules. Default value: null
+ *
+ * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever
+ * to use String.localeCampare method or not. Default set to true.
+ *
+ *
+ * @option Array sortAppend (optional) An array containing forced sorting rules.
+ * This option let's you specify a default sorting rule, which is
+ * appended to user-selected rules. Default value: null
+ *
+ * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter
+ * should apply fixed widths to the table columns. This is usefull when
+ * using the pager companion plugin. This options requires the dimension
+ * jquery plugin. Default value: false
+ *
+ * @option Boolean cancelSelection (optional) Boolean flag indicating if
+ * tablesorter should cancel selection of the table headers text.
+ * Default value: true
+ *
+ * @option Boolean debug (optional) Boolean flag indicating if tablesorter
+ * should display debuging information usefull for development.
+ *
+ * @type jQuery
+ *
+ * @name tablesorter
+ *
+ * @cat Plugins/Tablesorter
+ *
+ * @author Christian Bach/christian.bach(a)polyester.se
+ */
+
+(function ($) {
+ $.extend({
+ tablesorter: new
+ function () {
+
+ var parsers = [],
+ widgets = [];
+
+ this.defaults = {
+ cssHeader: "header",
+ cssAsc: "headerSortUp",
+ cssDesc: "headerSortDown",
+ cssChildRow: "expand-child",
+ sortInitialOrder: "asc",
+ sortMultiSortKey: "shiftKey",
+ sortForce: null,
+ sortAppend: null,
+ sortLocaleCompare: true,
+ textExtraction: "simple",
+ parsers: {}, widgets: [],
+ widgetZebra: {
+ css: ["even", "odd"]
+ }, headers: {}, widthFixed: false,
+ cancelSelection: true,
+ sortList: [],
+ headerList: [],
+ dateFormat: "us",
+ decimal: '/\.|\,/g',
+ onRenderHeader: null,
+ selectorHeaders: 'thead th',
+ debug: false
+ };
+
+ /* debuging utils */
+
+ function benchmark(s, d) {
+ log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
+ }
+
+ this.benchmark = benchmark;
+
+ function log(s) {
+ if (typeof console != "undefined" && typeof console.debug != "undefined") {
+ console.log(s);
+ } else {
+ alert(s);
+ }
+ }
+
+ /* parsers utils */
+
+ function buildParserCache(table, $headers) {
+
+ if (table.config.debug) {
+ var parsersDebug = "";
+ }
+
+ if (table.tBodies.length == 0) return; // In the case of empty tables
+ var rows = table.tBodies[0].rows;
+
+ if (rows[0]) {
+
+ var list = [],
+ cells = rows[0].cells,
+ l = cells.length;
+
+ for (var i = 0; i < l; i++) {
+
+ var p = false;
+
+ if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {
+
+ p = getParserById($($headers[i]).metadata().sorter);
+
+ } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {
+
+ p = getParserById(table.config.headers[i].sorter);
+ }
+ if (!p) {
+
+ p = detectParserForColumn(table, rows, -1, i);
+ }
+
+ if (table.config.debug) {
+ parsersDebug += "column:" + i + " parser:" + p.id + "\n";
+ }
+
+ list.push(p);
+ }
+ }
+
+ if (table.config.debug) {
+ log(parsersDebug);
+ }
+
+ return list;
+ };
+
+ function detectParserForColumn(table, rows, rowIndex, cellIndex) {
+ var l = parsers.length,
+ node = false,
+ nodeValue = false,
+ keepLooking = true;
+ while (nodeValue == '' && keepLooking) {
+ rowIndex++;
+ if (rows[rowIndex]) {
+ node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex);
+ nodeValue = trimAndGetNodeText(table.config, node);
+ if (table.config.debug) {
+ log('Checking if value was empty on row:' + rowIndex);
+ }
+ } else {
+ keepLooking = false;
+ }
+ }
+ for (var i = 1; i < l; i++) {
+ if (parsers[i].is(nodeValue, table, node)) {
+ return parsers[i];
+ }
+ }
+ // 0 is always the generic parser (text)
+ return parsers[0];
+ }
+
+ function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) {
+ return rows[rowIndex].cells[cellIndex];
+ }
+
+ function trimAndGetNodeText(config, node) {
+ return $.trim(getElementText(config, node));
+ }
+
+ function getParserById(name) {
+ var l = parsers.length;
+ for (var i = 0; i < l; i++) {
+ if (parsers[i].id.toLowerCase() == name.toLowerCase()) {
+ return parsers[i];
+ }
+ }
+ return false;
+ }
+
+ /* utils */
+
+ function buildCache(table) {
+
+ if (table.config.debug) {
+ var cacheTime = new Date();
+ }
+
+ var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
+ totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
+ parsers = table.config.parsers,
+ cache = {
+ row: [],
+ normalized: []
+ };
+
+ for (var i = 0; i < totalRows; ++i) {
+
+ /** Add the table data to main data array */
+ var c = $(table.tBodies[0].rows[i]),
+ cols = [];
+
+ // if this is a child row, add it to the last row's children and
+ // continue to the next row
+ if (c.hasClass(table.config.cssChildRow)) {
+ cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
+ // go to the next for loop
+ continue;
+ }
+
+ cache.row.push(c);
+
+ for (var j = 0; j < totalCells; ++j) {
+ cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));
+ }
+
+ cols.push(cache.normalized.length); // add position for rowCache
+ cache.normalized.push(cols);
+ cols = null;
+ };
+
+ if (table.config.debug) {
+ benchmark("Building cache for " + totalRows + " rows:", cacheTime);
+ }
+
+ return cache;
+ };
+
+ function getElementText(config, node) {
+
+ var text = "";
+
+ if (!node) return "";
+
+ if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false;
+
+ if (config.textExtraction == "simple") {
+ if (config.supportsTextContent) {
+ text = node.textContent;
+ } else {
+ if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
+ text = node.childNodes[0].innerHTML;
+ } else {
+ text = node.innerHTML;
+ }
+ }
+ } else {
+ if (typeof(config.textExtraction) == "function") {
+ text = config.textExtraction(node);
+ } else {
+ text = $(node).text();
+ }
+ }
+ return text;
+ }
+
+ function appendToTable(table, cache) {
+
+ if (table.config.debug) {
+ var appendTime = new Date()
+ }
+
+ var c = cache,
+ r = c.row,
+ n = c.normalized,
+ totalRows = n.length,
+ checkCell = (n[0].length - 1),
+ tableBody = $(table.tBodies[0]),
+ rows = [];
+
+
+ for (var i = 0; i < totalRows; i++) {
+ var pos = n[i][checkCell];
+
+ rows.push(r[pos]);
+
+ if (!table.config.appender) {
+
+ //var o = ;
+ var l = r[pos].length;
+ for (var j = 0; j < l; j++) {
+ tableBody[0].appendChild(r[pos][j]);
+ }
+
+ //
+ }
+ }
+
+
+
+ if (table.config.appender) {
+
+ table.config.appender(table, rows);
+ }
+
+ rows = null;
+
+ if (table.config.debug) {
+ benchmark("Rebuilt table:", appendTime);
+ }
+
+ // apply table widgets
+ applyWidget(table);
+
+ // trigger sortend
+ setTimeout(function () {
+ $(table).trigger("sortEnd");
+ }, 0);
+
+ };
+
+ function buildHeaders(table) {
+
+ if (table.config.debug) {
+ var time = new Date();
+ }
+
+ var meta = ($.metadata) ? true : false;
+
+ var header_index = computeTableHeaderCellIndexes(table);
+
+ $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) {
+
+ this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
+ // this.column = index;
+ this.order = formatSortingOrder(table.config.sortInitialOrder);
+
+
+ this.count = this.order;
+
+ if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;
+ if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index);
+
+ if (!this.sortDisabled) {
+ var $th = $(this).addClass(table.config.cssHeader);
+ if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th);
+ }
+
+ // add cell to headerList
+ table.config.headerList[index] = this;
+ });
+
+ if (table.config.debug) {
+ benchmark("Built headers:", time);
+ log($tableHeaders);
+ }
+
+ return $tableHeaders;
+
+ };
+
+ // from:
+ // http://www.javascripttoolbox.com/lib/table/examples.php
+ // http://www.javascripttoolbox.com/temp/table_cellindex.html
+
+
+ function computeTableHeaderCellIndexes(t) {
+ var matrix = [];
+ var lookup = {};
+ var thead = t.getElementsByTagName('THEAD')[0];
+ var trs = thead.getElementsByTagName('TR');
+
+ for (var i = 0; i < trs.length; i++) {
+ var cells = trs[i].cells;
+ for (var j = 0; j < cells.length; j++) {
+ var c = cells[j];
+
+ var rowIndex = c.parentNode.rowIndex;
+ var cellId = rowIndex + "-" + c.cellIndex;
+ var rowSpan = c.rowSpan || 1;
+ var colSpan = c.colSpan || 1
+ var firstAvailCol;
+ if (typeof(matrix[rowIndex]) == "undefined") {
+ matrix[rowIndex] = [];
+ }
+ // Find first available column in the first row
+ for (var k = 0; k < matrix[rowIndex].length + 1; k++) {
+ if (typeof(matrix[rowIndex][k]) == "undefined") {
+ firstAvailCol = k;
+ break;
+ }
+ }
+ lookup[cellId] = firstAvailCol;
+ for (var k = rowIndex; k < rowIndex + rowSpan; k++) {
+ if (typeof(matrix[k]) == "undefined") {
+ matrix[k] = [];
+ }
+ var matrixrow = matrix[k];
+ for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
+ matrixrow[l] = "x";
+ }
+ }
+ }
+ }
+ return lookup;
+ }
+
+ function checkCellColSpan(table, rows, row) {
+ var arr = [],
+ r = table.tHead.rows,
+ c = r[row].cells;
+
+ for (var i = 0; i < c.length; i++) {
+ var cell = c[i];
+
+ if (cell.colSpan > 1) {
+ arr = arr.concat(checkCellColSpan(table, headerArr, row++));
+ } else {
+ if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {
+ arr.push(cell);
+ }
+ // headerArr[row] = (i+row);
+ }
+ }
+ return arr;
+ };
+
+ function checkHeaderMetadata(cell) {
+ if (($.metadata) && ($(cell).metadata().sorter === false)) {
+ return true;
+ };
+ return false;
+ }
+
+ function checkHeaderOptions(table, i) {
+ if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {
+ return true;
+ };
+ return false;
+ }
+
+ function checkHeaderOptionsSortingLocked(table, i) {
+ if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder;
+ return false;
+ }
+
+ function applyWidget(table) {
+ var c = table.config.widgets;
+ var l = c.length;
+ for (var i = 0; i < l; i++) {
+
+ getWidgetById(c[i]).format(table);
+ }
+
+ }
+
+ function getWidgetById(name) {
+ var l = widgets.length;
+ for (var i = 0; i < l; i++) {
+ if (widgets[i].id.toLowerCase() == name.toLowerCase()) {
+ return widgets[i];
+ }
+ }
+ };
+
+ function formatSortingOrder(v) {
+ if (typeof(v) != "Number") {
+ return (v.toLowerCase() == "desc") ? 1 : 0;
+ } else {
+ return (v == 1) ? 1 : 0;
+ }
+ }
+
+ function isValueInArray(v, a) {
+ var l = a.length;
+ for (var i = 0; i < l; i++) {
+ if (a[i][0] == v) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function setHeadersCss(table, $headers, list, css) {
+ // remove all header information
+ $headers.removeClass(css[0]).removeClass(css[1]);
+
+ var h = [];
+ $headers.each(function (offset) {
+ if (!this.sortDisabled) {
+ h[this.column] = $(this);
+ }
+ });
+
+ var l = list.length;
+ for (var i = 0; i < l; i++) {
+ h[list[i][0]].addClass(css[list[i][1]]);
+ }
+ }
+
+ function fixColumnWidth(table, $headers) {
+ var c = table.config;
+ if (c.widthFixed) {
+ var colgroup = $('<colgroup>');
+ $("tr:first td", table.tBodies[0]).each(function () {
+ colgroup.append($('<col>').css('width', $(this).width()));
+ });
+ $(table).prepend(colgroup);
+ };
+ }
+
+ function updateHeaderSortCount(table, sortList) {
+ var c = table.config,
+ l = sortList.length;
+ for (var i = 0; i < l; i++) {
+ var s = sortList[i],
+ o = c.headerList[s[0]];
+ o.count = s[1];
+ o.count++;
+ }
+ }
+
+ /* sorting methods */
+
+ function multisort(table, sortList, cache) {
+
+ if (table.config.debug) {
+ var sortTime = new Date();
+ }
+
+ var dynamicExp = "var sortWrapper = function(a,b) {",
+ l = sortList.length;
+
+ // TODO: inline functions.
+ for (var i = 0; i < l; i++) {
+
+ var c = sortList[i][0];
+ var order = sortList[i][1];
+ // var s = (getCachedSortType(table.config.parsers,c) == "text") ?
+ // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ?
+ // "sortNumeric" : "sortNumericDesc");
+ // var s = (table.config.parsers[c].type == "text") ? ((order == 0)
+ // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ?
+ // makeSortNumeric(c) : makeSortNumericDesc(c));
+ var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c));
+ var e = "e" + i;
+
+ dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c
+ // + "]); ";
+ dynamicExp += "if(" + e + ") { return " + e + "; } ";
+ dynamicExp += "else { ";
+
+ }
+
+ // if value is the same keep orignal order
+ var orgOrderCol = cache.normalized[0].length - 1;
+ dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
+
+ for (var i = 0; i < l; i++) {
+ dynamicExp += "}; ";
+ }
+
+ dynamicExp += "return 0; ";
+ dynamicExp += "}; ";
+
+ if (table.config.debug) {
+ benchmark("Evaling expression:" + dynamicExp, new Date());
+ }
+
+ eval(dynamicExp);
+
+ cache.normalized.sort(sortWrapper);
+
+ if (table.config.debug) {
+ benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime);
+ }
+
+ return cache;
+ };
+
+ function makeSortFunction(type, direction, index) {
+ var a = "a[" + index + "]",
+ b = "b[" + index + "]";
+ if (type == 'text' && direction == 'asc') {
+ return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));";
+ } else if (type == 'text' && direction == 'desc') {
+ return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));";
+ } else if (type == 'numeric' && direction == 'asc') {
+ return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));";
+ } else if (type == 'numeric' && direction == 'desc') {
+ return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));";
+ }
+ };
+
+ function makeSortText(i) {
+ return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));";
+ };
+
+ function makeSortTextDesc(i) {
+ return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));";
+ };
+
+ function makeSortNumeric(i) {
+ return "a[" + i + "]-b[" + i + "];";
+ };
+
+ function makeSortNumericDesc(i) {
+ return "b[" + i + "]-a[" + i + "];";
+ };
+
+ function sortText(a, b) {
+ if (table.config.sortLocaleCompare) return a.localeCompare(b);
+ return ((a < b) ? -1 : ((a > b) ? 1 : 0));
+ };
+
+ function sortTextDesc(a, b) {
+ if (table.config.sortLocaleCompare) return b.localeCompare(a);
+ return ((b < a) ? -1 : ((b > a) ? 1 : 0));
+ };
+
+ function sortNumeric(a, b) {
+ return a - b;
+ };
+
+ function sortNumericDesc(a, b) {
+ return b - a;
+ };
+
+ function getCachedSortType(parsers, i) {
+ return parsers[i].type;
+ }; /* public methods */
+ this.construct = function (settings) {
+ return this.each(function () {
+ // if no thead or tbody quit.
+ if (!this.tHead || !this.tBodies) return;
+ // declare
+ var $this, $document, $headers, cache, config, shiftDown = 0,
+ sortOrder;
+ // new blank config object
+ this.config = {};
+ // merge and extend.
+ config = $.extend(this.config, $.tablesorter.defaults, settings);
+ // store common expression for speed
+ $this = $(this);
+ // save the settings where they read
+ $.data(this, "tablesorter", config);
+ // build headers
+ $headers = buildHeaders(this);
+ // try to auto detect column type, and store in tables config
+ this.config.parsers = buildParserCache(this, $headers);
+ // build the cache for the tbody cells
+ cache = buildCache(this);
+ // get the css class names, could be done else where.
+ var sortCSS = [config.cssDesc, config.cssAsc];
+ // fixate columns if the users supplies the fixedWidth option
+ fixColumnWidth(this);
+ // apply event handling to headers
+ // this is to big, perhaps break it out?
+ $headers.click(
+
+ function (e) {
+ var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
+ if (!this.sortDisabled && totalRows > 0) {
+ // Only call sortStart if sorting is
+ // enabled.
+ $this.trigger("sortStart");
+ // store exp, for speed
+ var $cell = $(this);
+ // get current column index
+ var i = this.column;
+ // get current column sort order
+ this.order = this.count++ % 2;
+ // always sort on the locked order.
+ if(this.lockedOrder) this.order = this.lockedOrder;
+
+ // user only whants to sort on one
+ // column
+ if (!e[config.sortMultiSortKey]) {
+ // flush the sort list
+ config.sortList = [];
+ if (config.sortForce != null) {
+ var a = config.sortForce;
+ for (var j = 0; j < a.length; j++) {
+ if (a[j][0] != i) {
+ config.sortList.push(a[j]);
+ }
+ }
+ }
+ // add column to sort list
+ config.sortList.push([i, this.order]);
+ // multi column sorting
+ } else {
+ // the user has clicked on an all
+ // ready sortet column.
+ if (isValueInArray(i, config.sortList)) {
+ // revers the sorting direction
+ // for all tables.
+ for (var j = 0; j < config.sortList.length; j++) {
+ var s = config.sortList[j],
+ o = config.headerList[s[0]];
+ if (s[0] == i) {
+ o.count = s[1];
+ o.count++;
+ s[1] = o.count % 2;
+ }
+ }
+ } else {
+ // add column to sort list array
+ config.sortList.push([i, this.order]);
+ }
+ };
+ setTimeout(function () {
+ // set css for headers
+ setHeadersCss($this[0], $headers, config.sortList, sortCSS);
+ appendToTable(
+ $this[0], multisort(
+ $this[0], config.sortList, cache)
+ );
+ }, 1);
+ // stop normal event by returning false
+ return false;
+ }
+ // cancel selection
+ }).mousedown(function () {
+ if (config.cancelSelection) {
+ this.onselectstart = function () {
+ return false
+ };
+ return false;
+ }
+ });
+ // apply easy methods that trigger binded events
+ $this.bind("update", function () {
+ var me = this;
+ setTimeout(function () {
+ // rebuild parsers.
+ me.config.parsers = buildParserCache(
+ me, $headers);
+ // rebuild the cache map
+ cache = buildCache(me);
+ }, 1);
+ }).bind("updateCell", function (e, cell) {
+ var config = this.config;
+ // get position from the dom.
+ var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];
+ // update cache
+ cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(
+ getElementText(config, cell), cell);
+ }).bind("sorton", function (e, list) {
+ $(this).trigger("sortStart");
+ config.sortList = list;
+ // update and store the sortlist
+ var sortList = config.sortList;
+ // update header count index
+ updateHeaderSortCount(this, sortList);
+ // set css for headers
+ setHeadersCss(this, $headers, sortList, sortCSS);
+ // sort the table and append it to the dom
+ appendToTable(this, multisort(this, sortList, cache));
+ }).bind("appendCache", function () {
+ appendToTable(this, cache);
+ }).bind("applyWidgetId", function (e, id) {
+ getWidgetById(id).format(this);
+ }).bind("applyWidgets", function () {
+ // apply widgets
+ applyWidget(this);
+ });
+ if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
+ config.sortList = $(this).metadata().sortlist;
+ }
+ // if user has supplied a sort list to constructor.
+ if (config.sortList.length > 0) {
+ $this.trigger("sorton", [config.sortList]);
+ }
+ // apply widgets
+ applyWidget(this);
+ });
+ };
+ this.addParser = function (parser) {
+ var l = parsers.length,
+ a = true;
+ for (var i = 0; i < l; i++) {
+ if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
+ a = false;
+ }
+ }
+ if (a) {
+ parsers.push(parser);
+ };
+ };
+ this.addWidget = function (widget) {
+ widgets.push(widget);
+ };
+ this.formatFloat = function (s) {
+ var i = parseFloat(s);
+ return (isNaN(i)) ? 0 : i;
+ };
+ this.formatInt = function (s) {
+ var i = parseInt(s);
+ return (isNaN(i)) ? 0 : i;
+ };
+ this.isDigit = function (s, config) {
+ // replace all an wanted chars and match.
+ return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, '')));
+ };
+ this.clearTableBody = function (table) {
+ if ($.browser.msie) {
+ function empty() {
+ while (this.firstChild)
+ this.removeChild(this.firstChild);
+ }
+ empty.apply(table.tBodies[0]);
+ } else {
+ table.tBodies[0].innerHTML = "";
+ }
+ };
+ }
+ });
+
+ // extend plugin scope
+ $.fn.extend({
+ tablesorter: $.tablesorter.construct
+ });
+
+ // make shortcut
+ var ts = $.tablesorter;
+
+ // add default parsers
+ ts.addParser({
+ id: "text",
+ is: function (s) {
+ return true;
+ }, format: function (s) {
+ return $.trim(s.toLocaleLowerCase());
+ }, type: "text"
+ });
+
+ ts.addParser({
+ id: "digit",
+ is: function (s, table) {
+ var c = table.config;
+ return $.tablesorter.isDigit(s, c);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(s);
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "currency",
+ is: function (s) {
+ return /^[£$€?.]/.test(s);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), ""));
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "ipAddress",
+ is: function (s) {
+ return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);
+ }, format: function (s) {
+ var a = s.split("."),
+ r = "",
+ l = a.length;
+ for (var i = 0; i < l; i++) {
+ var item = a[i];
+ if (item.length == 2) {
+ r += "0" + item;
+ } else {
+ r += item;
+ }
+ }
+ return $.tablesorter.formatFloat(r);
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "url",
+ is: function (s) {
+ return /^(https?|ftp|file):\/\/$/.test(s);
+ }, format: function (s) {
+ return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), ''));
+ }, type: "text"
+ });
+
+ ts.addParser({
+ id: "isoDate",
+ is: function (s) {
+ return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(
+ new RegExp(/-/g), "/")).getTime() : "0");
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "percent",
+ is: function (s) {
+ return /\%$/.test($.trim(s));
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), ""));
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "usLongDate",
+ is: function (s) {
+ return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(new Date(s).getTime());
+ }, type: "numeric"
+ });
+
+ ts.addParser({
+ id: "shortDate",
+ is: function (s) {
+ return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);
+ }, format: function (s, table) {
+ var c = table.config;
+ s = s.replace(/\-/g, "/");
+ if (c.dateFormat == "us") {
+ // reformat the string in ISO format
+ s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2");
+ } else if (c.dateFormat == "uk") {
+ // reformat the string in ISO format
+ s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1");
+ } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") {
+ s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3");
+ }
+ return $.tablesorter.formatFloat(new Date(s).getTime());
+ }, type: "numeric"
+ });
+ ts.addParser({
+ id: "time",
+ is: function (s) {
+ return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
+ }, format: function (s) {
+ return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
+ }, type: "numeric"
+ });
+ ts.addParser({
+ id: "metadata",
+ is: function (s) {
+ return false;
+ }, format: function (s, table, cell) {
+ var c = table.config,
+ p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
+ return $(cell).metadata()[p];
+ }, type: "numeric"
+ });
+ // add default widgets
+ ts.addWidget({
+ id: "zebra",
+ format: function (table) {
+ if (table.config.debug) {
+ var time = new Date();
+ }
+ var $tr, row = -1,
+ odd;
+ // loop through the visible rows
+ $("tr:visible", table.tBodies[0]).each(function (i) {
+ $tr = $(this);
+ // style children rows the same way the parent
+ // row was styled
+ if (!$tr.hasClass(table.config.cssChildRow)) row++;
+ odd = (row % 2 == 0);
+ $tr.removeClass(
+ table.config.widgetZebra.css[odd ? 0 : 1]).addClass(
+ table.config.widgetZebra.css[odd ? 1 : 0])
+ });
+ if (table.config.debug) {
+ $.tablesorter.benchmark("Applying Zebra widget", time);
+ }
+ }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/js/messages.php b/js/messages.php
index b2209cf..a09078e 100644
--- a/js/messages.php
+++ b/js/messages.php
@@ -46,6 +46,10 @@ $js_messages['strBLOBRepositoryDisableAreYouSure'] = sprintf(__('Are you sure yo
$js_messages['strFormEmpty'] = __('Missing value in the form!');
$js_messages['strNotNumber'] = __('This is not a number!');
+/* Charts */
+/* l10n: Default description for the y-Axis of Charts */
+$js_messages['strTotalCount'] = __('Total count');
+
/* For server_privileges.js */
$js_messages['strHostEmpty'] = __('The host name is empty!');
$js_messages['strUserEmpty'] = __('The user name is empty!');
@@ -56,6 +60,18 @@ $js_messages['strReloadingPrivileges'] = __('Reloading Privileges');
$js_messages['strRemovingSelectedUsers'] = __('Removing Selected Users');
$js_messages['strClose'] = __('Close');
+/* for server_status.js */
+$js_messages['strRealtimeChart'] = __('Realtime chart');
+$js_messages['strStaticData'] = __('Static data');
+/* l10n: Total number of queries */
+$js_messages['strTotal'] = __('Total');
+/* l10n: Other, small valued, queries */
+$js_messages['strOther'] = __('Other');
+/* l10n: Thousands separator */
+$js_messages['strThousandsSeperator'] = __(',');
+/* l10n: Decimal separator */
+$js_messages['strDecimalSeperator'] = __('.');
+
/* For inline query editing */
$js_messages['strGo'] = __('Go');
$js_messages['strCancel'] = __('Cancel');
diff --git a/js/pMap.js b/js/pMap.js
deleted file mode 100644
index b63221c..0000000
--- a/js/pMap.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * Holds the definition and the creation of the imageMap object
- * @author Martynas Mickevicius <mmartynas(a)gmail.com>
- * @package phpMyAdmin
- */
-
-/**
- * responsible for showing tooltips above the image chart
- */
-var imageMap = {
- 'mouseMoved': function(event, cont) {
- // return if no imageMap set
- // this can happen if server has no json
- if (!this.imageMap) {
- return;
- }
-
- // get mouse coordinated relative to image
- var mouseX = event.pageX - cont.offsetLeft;
- var mouseY = event.pageY - cont.offsetTop;
-
- //console.log("X: " + mouseX + ", Y: " + mouseY);
-
- /* Check if we are flying over a map zone
- * Lets use the following method to check if a given
- * point is in any convex polygon.
- * http://www.programmingforums.org/post168124-3.html
- */
- var found = false;
- for (var key = 0; key < this.imageMap.length; key++)
- {
- var seriesName = this.imageMap[key]['n'];
- var seriesValue = this.imageMap[key]['v'];
-
- var signSum = 0;
- for (var i = 0; i < this.imageMap[key]['p'].length; i++)
- {
- var index1;
- var index2;
-
- if (i == this.imageMap[key]['p'].length - 1)
- {
- index1 = i;
- index2 = 0;
- }
- else
- {
- index1 = i;
- index2 = i+1;
- }
- var result = this.getDeterminant(
- this.imageMap[key]['p'][index1][0],
- this.imageMap[key]['p'][index1][1],
- this.imageMap[key]['p'][index2][0],
- this.imageMap[key]['p'][index2][1],
- mouseX,
- mouseY
- );
- if (result > 0) { signSum += 1; } else { signSum += -1; }
- }
-
- if (Math.abs(signSum) == this.imageMap[key]['p'].length)
- {
- found = true;
- if (this.currentKey != key)
- {
- this.tooltip.show();
- this.tooltip.title(seriesName);
- this.tooltip.text(seriesValue);
- this.currentKey = key;
- }
- this.tooltip.move(mouseX + 20, mouseY + 20);
- }
- }
- if (!found && this.currentKey != -1 )
- {
- this.tooltip.hide();
- this.currentKey = -1;
- }
- },
-
- 'getDeterminant': function (X1, Y1, X2, Y2, X3, Y3) {
- return (X2*Y3 - X3*Y2) - (X1*Y3 - X3*Y1) + (X1*Y2 - X2*Y1);
- },
-
- 'loadImageMap': function(map) {
- this.imageMap = JSON.parse(map);
- for (key in this.imageMap)
- {
- // FIXME
- // without this loop image map does not work
- // on IE8 in the status page
- }
- },
-
- 'init': function() {
- this.tooltip.init();
-
- $("div#chart").bind('mousemove',function(e) {
- imageMap.mouseMoved(e, this);
- });
-
- this.tooltip.attach("div#chart");
-
- this.currentKey = -1;
- },
-
- 'tooltip': {
- 'init': function () {
- this.el = $('<div></div>');
- this.el.css('position', 'absolute');
- this.el.css('font-family', 'tahoma');
- this.el.css('background-color', '#373737');
- this.el.css('color', '#BEBEBE');
- this.el.css('padding', '3px');
-
- var title = $('<p></p>');
- title.attr('id', 'title');
- title.css('margin', '0px');
- title.css('padding', '3px');
- title.css('background-color', '#606060');
- title.css('text-align', 'center');
- title.html('Title');
- this.el.append(title);
-
- var text = $('<p></p>');
- text.attr('id', 'text');
- text.css('margin', '0');
- text.html('Text');
- this.el.append(text);
-
- this.hide();
- },
-
- 'attach': function (element) {
- $(element).prepend(this.el);
- },
-
- 'move': function (x, y) {
- this.el.css('margin-left', x);
- this.el.css('margin-top', y);
- },
-
- 'hide': function () {
- this.el.css('display', 'none');
- },
-
- 'show': function () {
- this.el.css('display', 'block');
- },
-
- 'title': function (title) {
- this.el.find("p#title").html(title);
- },
-
- 'text': function (text) {
- this.el.find("p#text").html(text.replace(/;/g, "<br />"));
- }
- }
-};
-
-$(document).ready(function() {
- imageMap.init();
-});
diff --git a/js/server_status.js b/js/server_status.js
new file mode 100644
index 0000000..75894ad
--- /dev/null
+++ b/js/server_status.js
@@ -0,0 +1,373 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview functions used in server status pages
+ * @name Server Status
+ *
+ * @requires jQuery
+ * @requires jQueryUI
+ * @requires jQueryCookie
+ * @requires jQueryTablesorter
+ * @requires Highcharts
+ * @requires canvg
+ * @requires js/functions.js
+ *
+ */
+
+// Add a tablesorter parser to properly handle thousands seperated numbers and SI prefixes
+$(function() {
+ jQuery.tablesorter.addParser({
+ id: "fancyNumber",
+ is: function(s) {
+ return /^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/.test(s);
+ },
+ format: function(s) {
+ var num = jQuery.tablesorter.formatFloat( s.replace(PMA_messages['strThousandsSeperator'],'').replace(PMA_messages['strDecimalSeperator'],'.') );
+ var factor = 1;
+ switch (s.charAt(s.length-1)) {
+ case '%': factor = -2; break;
+ // Todo: Complete this list (as well as in the regexp a few lines up)
+ case 'k': factor = 3; break;
+ case 'M': factor = 6; break;
+ case 'G': factor = 9; break;
+ case 'T': factor = 12; break;
+ }
+ return num*Math.pow(10,factor);
+ },
+ type: "numeric"
+ });
+});
+
+$(function() {
+ // Filters for status variables
+ var textFilter=null;
+ var alertFilter = false;
+ var categoryFilter='';
+ var odd_row=false;
+ var text=''; // Holds filter text
+ var queryPieChart = null;
+ /* Chart configuration */
+
+ // Defines what the tabs are currently displaying (realtime or data)
+ var tabStatus = new Object();
+ // Holds the current chart instances for each tab
+ var tabChart = new Object();
+
+ // Add tabs
+ $('#serverStatusTabs').tabs({
+ // Tab persistence
+ cookie: { name: 'pma_serverStatusTabs', expires: 1 },
+ // Fixes line break in the menu bar when the page overflows and scrollbar appears
+ show: function() { menuResize(); }
+ });
+
+ // Fixes wrong tab height with floated elements. See also http://bugs.jqueryui.com/ticket/5601
+ $(".ui-widget-content:not(.ui-tabs):not(.ui-helper-clearfix)").addClass("ui-helper-clearfix");
+
+ // Initialize each tab
+ $('div.ui-tabs-panel').each(function() { initTab($(this),null); });
+
+ $('.statuslinks select').change(function() {
+ var chart=tabChart[$(this).parents('div.ui-tabs-panel').attr('id')];
+ chart.options.realtime.refreshRate = 1000*parseInt(this.value);
+ chart.xAxis[0].setExtremes(new Date().getTime() - chart.options.realtime.numMaxPoints * chart.options.realtime.refreshRate, chart.xAxis[0].getExtremes().max, true);
+ clearTimeout(chart_activeTimeouts[chart.options.chart.renderTo]);
+ chart_activeTimeouts[chart.options.chart.renderTo] = setTimeout(chart.options.realtime.timeoutCallBack, chart.options.realtime.refreshRate);
+ });
+
+ // Ajax refresh of variables (always the first element in each tab)
+ $('.statuslinks a.tabRefresh').click(function() {
+ // ui-tabs-panel class is added by the jquery tabs feature
+ var tab=$(this).parents('div.ui-tabs-panel');
+ var that = this;
+
+ // Show ajax load icon
+ $(this).find('img').show();
+
+ $.get($(this).attr('href'),{ajax_request:1},function(data) {
+ $(that).find('img').hide();
+ initTab(tab,data);
+ });
+
+ tabStatus[tab.attr('id')]='data';
+
+ return false;
+ });
+
+ /** Realtime charting of variables (always the third element) **/
+ $('.statuslinks a.tabChart').click(function() {
+ // ui-tabs-panel class is added by the jquery tabs feature
+ var tab=$(this).parents('div.ui-tabs-panel');
+
+ if(tabStatus[tab.attr('id')]!='realtime') {
+ var series, title;
+ var settings = new Object();
+
+ switch(tab.attr('id')) {
+ case 'statustabs_traffic':
+ settings = {
+ series: [{name:'Connections since last refresh', data:[]},{name:'Processes', data:[]}],
+ title: {text:'Connections / Processes'},
+ realtime:{ url:'server_status.php?'+url_query,
+ type: 'proc',
+ callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
+ if(lastVal==null) return;
+ chartObj.series[0].addPoint(
+ { x:curVal.x, y:curVal.y_conn-lastVal.y_conn },
+ false, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ chartObj.series[1].addPoint(
+ { x:curVal.x, y:curVal.y_proc },
+ true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ }
+ }
+ };
+ break;
+ case 'statustabs_queries':
+ settings = {
+ series: [{name:'Issued queries since last refresh', data:[]}],
+ title: {text:'Issued queries'},
+ tooltip: { formatter:function() { return this.point.name; } },
+ realtime:{ url:'server_status.php?'+url_query,
+ type: 'queries',
+ callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
+ if(lastVal==null) return;
+ chartObj.series[0].addPoint(
+ { x:curVal.x, y:curVal.y-lastVal.y, name:sortedQueriesPointInfo(curVal,lastVal) },
+ true, numLoadedPoints >= chartObj.options.realtime.numMaxPoints
+ );
+ }
+ }
+ };
+ break;
+
+ default:
+ return;
+ }
+
+ if(!settings.chart) settings.chart = {};
+ settings.chart.renderTo=tab.attr('id')+"_chart_cnt";
+
+ tab.find('.tabInnerContent')
+ .hide()
+ .after('<div style="clear:both; min-width:500px; height:400px; padding-bottom:80px;" id="'+tab.attr('id')+'_chart_cnt"></div>');
+ tabStatus[tab.attr('id')]='realtime';
+ tabChart[tab.attr('id')]=PMA_createChart(settings);
+ $(this).html(PMA_messages['strStaticData']);
+ tab.find('.statuslinks a.tabRefresh').hide();
+ tab.find('.statuslinks select').show();
+ } else {
+ clearTimeout(chart_activeTimeouts[tab.attr('id')+"_chart_cnt"]);
+ chart_activeTimeouts[tab.attr('id')+"_chart_cnt"]=null;
+ tab.find('.tabInnerContent').show();
+ tab.find('div#'+tab.attr('id')+'_chart_cnt').remove();
+ tabStatus[tab.attr('id')]='data';
+ tabChart[tab.attr('id')].destroy();
+ $(this).html(PMA_messages['strRealtimeChart']);
+ tab.find('.statuslinks a.tabRefresh').show();
+ tab.find('.statuslinks select').hide();
+ }
+ return false;
+ });
+
+
+ /* 3 Filtering functions */
+ $('#filterAlert').change(function() {
+ alertFilter = this.checked;
+ filterVariables();
+ });
+
+ $('#filterText').keyup(function(e) {
+ if($(this).val().length==0) textFilter=null;
+ else textFilter = new RegExp("(^|_)"+$(this).val(),'i');
+ text=$(this).val();
+ filterVariables();
+ });
+
+ $('#filterCategory').change(function() {
+ categoryFilter = $(this).val();
+ filterVariables();
+ });
+
+ /* Adjust DOM / Add handlers to the tabs */
+ function initTab(tab,data) {
+ switch(tab.attr('id')) {
+ case 'statustabs_traffic':
+ if(data!=null) tab.find('.tabInnerContent').html(data);
+ initTooltips();
+ break;
+ case 'statustabs_queries':
+ if(data!=null) {
+ queryPieChart.destroy();
+ tab.find('.tabInnerContent').html(data);
+ }
+
+ // Build query statistics chart
+ var cdata = new Array();
+ $.each(jQuery.parseJSON($('#serverstatusquerieschart').html()),function(key,value) {
+ cdata.push([key,parseInt(value)]);
+ });
+
+ queryPieChart=PMA_createChart({
+ chart: {
+ renderTo: 'serverstatusquerieschart'
+
+ },
+ title: {
+ text:'',
+ margin:0
+ },
+ series: [{
+ type:'pie',
+ name: 'Query statistics',
+ data: cdata
+ }],
+ plotOptions: {
+ pie: {
+ allowPointSelect: true,
+ cursor: 'pointer',
+ dataLabels: {
+ enabled: true,
+ formatter: function() {
+ return '<b>'+ this.point.name +'</b><br> '+ Highcharts.numberFormat(this.percentage, 2) +' %';
+ }
+ }
+ }
+ },
+ tooltip: {
+ formatter: function() { return '<b>'+ this.point.name +'</b><br/>'+Highcharts.numberFormat(this.y, 2)+'<br/>('+Highcharts.numberFormat(this.percentage, 2) +' %)'; }
+ }
+ });
+ break;
+
+ case 'statustabs_allvars':
+ if(data!=null) {
+ tab.find('.tabInnerContent').html(data);
+ filterVariables();
+ }
+ break;
+ }
+
+ initTableSorter(tab.attr('id'));
+ }
+
+ function initTableSorter(tabid) {
+ switch(tabid) {
+ case 'statustabs_queries':
+ $('#serverstatusqueriesdetails').tablesorter({
+ sortList: [[3,1]],
+ widgets: ['zebra'],
+ headers: {
+ 1: { sorter: 'fancyNumber' },
+ 2: { sorter: 'fancyNumber' }
+ }
+ });
+
+ $('#serverstatusqueriesdetails tr:first th')
+ .append('<img class="sortableIcon" src="'+pma_theme_image+'cleardot.gif" alt="">');
+
+ break;
+
+ case 'statustabs_allvars':
+ $('#serverstatusvariables').tablesorter({
+ sortList: [[0,0]],
+ widgets: ['zebra'],
+ headers: {
+ 1: { sorter: 'fancyNumber' }
+ }
+ });
+
+ $('#serverstatusvariables tr:first th')
+ .append('<img class="sortableIcon" src="'+pma_theme_image+'cleardot.gif" alt="">');
+
+ break;
+ }
+ }
+
+ /* Filters the status variables by name/category/alert in the variables tab */
+ function filterVariables() {
+ var useful_links=0;
+ var section = text;
+
+ if(categoryFilter.length>0) section = categoryFilter;
+
+ if(section.length>1) {
+ $('#linkSuggestions span').each(function() {
+ if($(this).attr('class').indexOf('status_'+section)!=-1) {
+ useful_links++;
+ $(this).css('display','');
+ } else {
+ $(this).css('display','none');
+ }
+
+
+ });
+ }
+
+ if(useful_links>0)
+ $('#linkSuggestions').css('display','');
+ else $('#linkSuggestions').css('display','none');
+
+ odd_row=false;
+ $('#serverstatusvariables th.name').each(function() {
+ if((textFilter==null || textFilter.exec($(this).text()))
+ && (!alertFilter || $(this).next().find('span.attention').length>0)
+ && (categoryFilter.length==0 || $(this).parent().hasClass('s_'+categoryFilter))) {
+ odd_row = !odd_row;
+ $(this).parent().css('display','');
+ if(odd_row) {
+ $(this).parent().addClass('odd');
+ $(this).parent().removeClass('even');
+ } else {
+ $(this).parent().addClass('even');
+ $(this).parent().removeClass('odd');
+ }
+ } else {
+ $(this).parent().css('display','none');
+ }
+ });
+ }
+
+ // Provides a nicely formatted and sorted tooltip of each datapoint of the query statistics
+ function sortedQueriesPointInfo(queries, lastQueries){
+ var max, maxIdx, num=0;
+ var queryKeys = new Array();
+ var queryValues = new Array();
+ var sumOther=0;
+ var sumTotal=0;
+
+ // Separate keys and values, then sort them
+ $.each(queries.pointInfo, function(key,value) {
+ if(value-lastQueries.pointInfo[key] > 0) {
+ queryKeys.push(key);
+ queryValues.push(value-lastQueries.pointInfo[key]);
+ sumTotal+=value-lastQueries.pointInfo[key];
+ }
+ });
+ var numQueries = queryKeys.length;
+ var pointInfo = '<b>' + PMA_messages['strTotal'] + ': ' + sumTotal + '</b><br>';
+
+ while(queryKeys.length > 0) {
+ max=0;
+ for(var i=0; i<queryKeys.length; i++) {
+ if(queryValues[i] > max) {
+ max = queryValues[i];
+ maxIdx = i;
+ }
+ }
+ if(numQueries > 8 && num>=6)
+ sumOther+=queryValues[maxIdx];
+ else pointInfo += queryKeys[maxIdx].substr(4).replace('_',' ') + ': ' + queryValues[maxIdx] + '<br>';
+
+ queryKeys.splice(maxIdx,1);
+ queryValues.splice(maxIdx,1);
+ num++;
+ }
+
+ if(sumOther>0)
+ pointInfo += PMA_messages['strOther'] + ': ' + sumOther;
+
+ return pointInfo;
+ }
+
+});
\ No newline at end of file
diff --git a/js/server_variables.js b/js/server_variables.js
new file mode 100644
index 0000000..1877ce7
--- /dev/null
+++ b/js/server_variables.js
@@ -0,0 +1,42 @@
+$(function() {
+ var textFilter=null;
+ var odd_row=false;
+
+ // Filter options are invisible for disabled js users
+ $('fieldset#tableFilter').css('display','');
+
+ $('#filterText').keyup(function(e) {
+ if($(this).val().length==0) textFilter=null;
+ else textFilter = new RegExp("(^| )"+$(this).val(),'i');
+ filterVariables();
+ });
+
+ function filterVariables() {
+ odd_row=false;
+ var mark_next=false;
+ var firstCell;
+
+ $('table.filteredData tbody tr').each(function() {
+ firstCell = $(this).children(':first');
+
+ if(mark_next || textFilter==null || textFilter.exec(firstCell.text())) {
+ // If current row is 'marked', also display next row
+ if($(this).hasClass('marked') && !mark_next)
+ mark_next=true;
+ else mark_next=false;
+
+ odd_row = !odd_row;
+ $(this).css('display','');
+ if(odd_row) {
+ $(this).addClass('odd');
+ $(this).removeClass('even');
+ } else {
+ $(this).addClass('even');
+ $(this).removeClass('odd');
+ }
+ } else {
+ $(this).css('display','none');
+ }
+ });
+ }
+});
\ No newline at end of file
diff --git a/js/sql.js b/js/sql.js
index 3bc4f81..65d209a 100644
--- a/js/sql.js
+++ b/js/sql.js
@@ -1155,6 +1155,51 @@ $(document).ready(function() {
$('.column_heading.marker').live('click', function() {
PMA_changeClassForColumn($(this), 'marked');
});
-})
+});
+
+/*
+ * Profiling Chart
+ */
+function createProfilingChart() {
+ if($('#profilingchart').length==0) return;
+
+ var cdata = new Array();
+ $.each(jQuery.parseJSON($('#profilingchart').html()),function(key,value) {
+ cdata.push([key,parseFloat(value)]);
+ });
+
+ // Prevent the user from seeing the JSON code
+ $('div#profilingchart').html('').show();
+
+ PMA_createChart({
+ chart: {
+ renderTo: 'profilingchart',
+ backgroundColor: $('#sqlqueryresults fieldset').css('background-color')
+ },
+ title: { text:'', margin:0 },
+ series: [{
+ type:'pie',
+ name: 'Query execution time',
+ data: cdata
+ }],
+ plotOptions: {
+ pie: {
+ allowPointSelect: true,
+ cursor: 'pointer',
+ dataLabels: {
+ enabled: true,
+ distance: 35,
+ formatter: function() {
+ return '<b>'+ this.point.name +'</b><br/>'+ Highcharts.numberFormat(this.percentage, 2) +' %';
+ }
+ }
+ }
+ },
+ tooltip: {
+ formatter: function() { return '<b>'+ this.point.name +'</b><br/>'+this.y+'s<br/>('+Highcharts.numberFormat(this.percentage, 2) +' %)'; }
+ }
+ });
+}
+
/**#@- */
diff --git a/js/tbl_chart.js b/js/tbl_chart.js
new file mode 100644
index 0000000..7c870d0
--- /dev/null
+++ b/js/tbl_chart.js
@@ -0,0 +1,236 @@
+var chart_xaxis_idx = -1;
+var chart_series;
+var chart_series_index = -1;
+
+$(document).ready(function() {
+ var currentChart=null;
+ var chart_data = jQuery.parseJSON($('#querychart').html());
+ chart_series = 'columns';
+ chart_xaxis_idx = $('select[name="chartXAxis"]').attr('value');
+
+ $('#resizer').resizable({
+ minHeight:240,
+ minWidth:300,
+ // On resize, set the chart size to that of the
+ // resizer minus padding. If your chart has a lot of data or other
+ // content, the redrawing might be slow. In that case, we recommend
+ // that you use the 'stop' event instead of 'resize'.
+ resize: function() {
+ currentChart.setSize(
+ this.offsetWidth - 20,
+ this.offsetHeight - 20,
+ false
+ );
+ }
+ });
+
+ var currentSettings = {
+ chart: {
+ type:'line',
+ width:$('#resizer').width()-20,
+ height:$('#resizer').height()-20
+ },
+ xAxis: {
+ title: { text: $('input[name="xaxis_label"]').attr('value') }
+ },
+ yAxis: {
+ title: { text: $('input[name="yaxis_label"]').attr('value') }
+ },
+ title: { text: $('input[name="chartTitle"]').attr('value'), margin:20 },
+ plotOptions: {
+ series: {}
+ }
+ }
+
+ $('#querychart').html('');
+
+ $('input[name="chartType"]').click(function() {
+ currentSettings.chart.type=$(this).attr('value');
+
+ drawChart();
+
+ if($(this).attr('value')=='bar' || $(this).attr('value')=='column')
+ $('span.barStacked').show();
+ else
+ $('span.barStacked').hide();
+ });
+
+ $('input[name="barStacked"]').click(function() {
+ if(this.checked)
+ $.extend(true,currentSettings,{ plotOptions: { series: { stacking:'normal' } } });
+ else
+ $.extend(true,currentSettings,{ plotOptions: { series: { stacking:null } } });
+ drawChart();
+ });
+
+ $('input[name="chartTitle"]').keyup(function() {
+ var title=$(this).attr('value');
+ if(title.length==0) title=' ';
+ currentChart.setTitle({text: title});
+ });
+
+ $('select[name="chartXAxis"]').change(function() {
+ chart_xaxis_idx = this.value;
+ drawChart();
+ });
+ $('select[name="chartSeries"]').change(function() {
+ chart_series = this.value;
+ chart_series_index = this.selectedIndex;
+ drawChart();
+ });
+
+ /* Sucks, we cannot just set axis labels, we have to redraw the chart completely */
+ $('input[name="xaxis_label"]').keyup(function() {
+ currentSettings.xAxis.title.text = $(this).attr('value');
+ drawChart(true);
+ });
+ $('input[name="yaxis_label"]').keyup(function() {
+ currentSettings.yAxis.title.text = $(this).attr('value');
+ drawChart(true);
+ });
+
+ function drawChart(noAnimation) {
+ currentSettings.chart.width=$('#resizer').width()-20;
+ currentSettings.chart.height=$('#resizer').height()-20;
+
+ if(currentChart!=null) currentChart.destroy();
+
+ if(noAnimation) currentSettings.plotOptions.series.animation = false;
+ currentChart = PMA_queryChart(chart_data,currentSettings);
+ if(noAnimation) currentSettings.plotOptions.series.animation = true;
+ }
+
+ drawChart();
+ $('#querychart').show();
+});
+
+function in_array(element,array) {
+ for(var i=0; i<array.length; i++)
+ if(array[i]==element) return true;
+ return false;
+}
+
+function PMA_queryChart(data,passedSettings) {
+ if($('#querychart').length==0) return;
+
+ var columnNames = Array();
+
+ var series = new Array();
+ var xaxis = new Object();
+ var yaxis = new Object();
+
+ $.each(data[0],function(index,element) {
+ columnNames.push(index);
+ });
+
+ switch(passedSettings.chart.type) {
+ case 'column':
+ case 'spline':
+ case 'line':
+ case 'bar':
+ xaxis.categories = new Array();
+
+ if(chart_series=='columns') {
+ var j=0;
+ for(var i=0; i<columnNames.length; i++)
+ if(i!=chart_xaxis_idx) {
+ series[j] = new Object();
+ series[j].data = new Array();
+ series[j].name = columnNames[i];
+ $.each(data,function(key,value) {
+ series[j].data.push(parseFloat(value[columnNames[i]]));
+ if(j==0 && chart_xaxis_idx!=-1 && !xaxis.categories[value[columnNames[chart_xaxis_idx]]])
+ xaxis.categories.push(value[columnNames[chart_xaxis_idx]]);
+ });
+ j++;
+ }
+ } else {
+ var j=0;
+ var seriesIndex = new Object();
+ // Get series types and build series object from the query data
+ $.each(data,function(index,element) {
+ var contains=false;
+ for(var i=0; i<series.length; i++)
+ if(series[i].name == element[chart_series]) contains=true;
+
+ if(!contains) {
+ seriesIndex[element[chart_series]] = j;
+ series[j] = new Object();
+ series[j].data = new Array();
+ series[j].name = element[chart_series]; // columnNames[i];
+ j++;
+ }
+ });
+
+ var type;
+ // Get series points from query data
+ $.each(data,function(key,value) {
+ type = value[chart_series];
+ series[seriesIndex[type]].data.push(parseFloat(value[columnNames[0]]));
+ if(!in_array(value[columnNames[chart_xaxis_idx]],xaxis.categories))
+ xaxis.categories.push(value[columnNames[chart_xaxis_idx]]);
+ });
+ }
+
+
+
+ if(columnNames.length==2)
+ yaxis.title = { text: columnNames[0] };
+ break;
+
+ case 'pie':
+ series[0] = new Object();
+ series[0].data = new Array();
+ $.each(data,function(key,value) {
+ series[0].data.push({name:value[columnNames[chart_xaxis_idx]],y:parseFloat(value[columnNames[0]])});
+ });
+ break;
+ }
+
+ // Prevent the user from seeing the JSON code
+ $('div#profilingchart').html('').show();
+
+ var settings = {
+ chart: {
+ renderTo: 'querychart',
+ backgroundColor: $('fieldset').css('background-color')
+ },
+ title: { text:'', margin:0 },
+ series: series,
+ xAxis: xaxis,
+ yAxis: yaxis,
+ plotOptions: {
+ pie: {
+ allowPointSelect: true,
+ cursor: 'pointer',
+ dataLabels: {
+ enabled: true,
+ distance: 35,
+ formatter: function() {
+ return '<b>'+ this.point.name +'</b><br/>'+ Highcharts.numberFormat(this.percentage, 2) +' %';
+ }
+ }
+ }
+ },
+ credits: {
+ enabled:false
+ },
+ exporting: {
+ enabled: true
+ },
+ tooltip: {
+ formatter: function() {
+ if(this.point.name) return '<b>'+this.series.name+'</b><br/>'+this.point.name+'<br/>'+this.y;
+ return '<b>'+this.series.name+'</b><br/>'+this.y;
+ }
+ }
+ };
+
+ if(passedSettings.chart.type=='pie')
+ settings.tooltip.formatter = function() { return '<b>'+columnNames[0]+'</b><br/>'+this.y; }
+
+ // Overwrite/Merge default settings with passedsettings
+ $.extend(true,settings,passedSettings);
+
+ return new Highcharts.Chart(settings);
+}
diff --git a/libraries/chart.lib.php b/libraries/chart.lib.php
deleted file mode 100644
index 83791fb..0000000
--- a/libraries/chart.lib.php
+++ /dev/null
@@ -1,256 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * Chart functions used to generate various types of charts.
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-define('ERR_NO_GD', 0);
-define('ERR_NO_JSON', 1);
-
-require_once './libraries/chart/pma_pchart_pie.php';
-require_once './libraries/chart/pma_pchart_single_bar.php';
-require_once './libraries/chart/pma_pchart_multi_bar.php';
-require_once './libraries/chart/pma_pchart_stacked_bar.php';
-require_once './libraries/chart/pma_pchart_single_line.php';
-require_once './libraries/chart/pma_pchart_multi_line.php';
-require_once './libraries/chart/pma_pchart_single_radar.php';
-require_once './libraries/chart/pma_pchart_multi_radar.php';
-
-/**
- * Formats a chart for the status page.
- * @param array $data data for the status chart
- * @return string HTML and JS code for the chart
- */
-function PMA_chart_status($data)
-{
- // format keys which will be shown in the chart
- $chartData = array();
- foreach($data as $dataKey => $dataValue) {
- $key = ucwords(str_replace(array('Com_', '_'), array('', ' '), $dataKey));
- $value = (int)$dataValue;
- $chartData[$key] = $value;
- }
-
- $chart = new PMA_pChart_Pie(
- $chartData,
- array('titleText' => __('Query statistics'))
- );
- $chartCode = $chart->toString();
- PMA_handle_chart_err($chart->getErrors());
- echo $chartCode;
-}
-
-/**
- * Formats a chart for the profiling page.
- * @param array $data data for the status chart
- * @return string HTML and JS code for the chart
- */
-function PMA_chart_profiling($data)
-{
- $chartData = array();
- foreach($data as $dataValue) {
- $value = (int)($dataValue['Duration'] * 1000000);
- $key = ucwords($dataValue['Status']);
- $chartData[$key] = $value;
- }
-
- $chart = new PMA_pChart_Pie(
- $chartData,
- array('titleText' => __('Query execution time comparison (in microseconds)'))
- );
- $chartCode = $chart->toString();
- PMA_handle_chart_err($chart->getErrors());
- echo $chartCode;
-}
-
-/**
- * Formats a chart for the query results page.
- * @param array $data data for the status chart
- * @param array $chartSettings settings used to generate the chart
- * @return string HTML and JS code for the chart
- */
-function PMA_chart_results($data, &$chartSettings)
-{
- $chartData = array();
- $chart = null;
-
- // set default title if not already set
- if (empty($chartSettings['titleText'])) {
- $chartSettings['titleText'] = __('Query results');
- }
-
- // set default type if not already set
- if (empty($chartSettings['type'])) {
- $chartSettings['type'] = 'bar';
- }
-
- // set default type if not already set
- if (empty($chartSettings['continuous'])) {
- $chartSettings['continuous'] = 'off';
- }
-
- // set default bar type if needed
- if ($chartSettings['type'] == 'bar' && empty($chartSettings['barType'])) {
- $chartSettings['barType'] = 'stacked';
- }
-
- // default for legend
- $chartSettings['legend'] = false;
-
- // default for muti series
- $chartSettings['multi'] = false;
-
- if (! isset($data[0])) {
- // empty data
- return __('No data found for the chart.');
- }
-
- if (count($data[0]) == 1 || count($data[0]) == 2) {
- // One or two columns in every row.
- // This data is suitable for a simple bar chart.
-
- if ($chartSettings['type'] == 'pie') {
- // loop through the rows, data for pie chart has to be formated
- // in a different way then in other charts.
- foreach ($data as $rowKey => $row) {
- $values = array_values($row);
-
- if (count($row) == 1) {
- $chartData[$rowKey] = $values[0];
- }
- else {
- $chartData[$values[1]] = $values[0];
- }
- }
-
- $chartSettings['legend'] = true;
- $chart = new PMA_pChart_pie($chartData, $chartSettings);
- }
- else {
- // loop through the rows
- foreach ($data as $rowKey => $row) {
-
- // loop through the columns in the row
- foreach ($row as $valueKey => $value) {
- $chartData[$valueKey][] = $value;
- }
-
- // if only one column, we need to add
- // placeholder data for x axis
- if (count($row) == 1) {
- $chartData[''][] = $rowKey;
- }
- }
-
- switch ($chartSettings['type']) {
- case 'bar':
- default:
- $chart = new PMA_pChart_single_bar($chartData, $chartSettings);
- break;
- case 'line':
- $chart = new PMA_pChart_single_line($chartData, $chartSettings);
- break;
- case 'radar':
- $chart = new PMA_pChart_single_radar($chartData, $chartSettings);
- break;
- }
- }
- }
- else if (count($data[0]) == 3) {
- // Three columns (x axis, y axis, series) in every row.
- // This data is suitable for a stacked bar chart.
- $chartSettings['multi'] = true;
-
- $keys = array_keys($data[0]);
- $yAxisKey = $keys[0];
- $xAxisKey = $keys[1];
- $seriesKey = $keys[2];
-
- // get all the series labels
- $seriesLabels = array();
- foreach ($data as $row) {
- $seriesLabels[] = $row[$seriesKey];
- }
- $seriesLabels = array_unique($seriesLabels);
-
- // loop through the rows
- $currentXLabel = $data[0][$xAxisKey];
- foreach ($data as $row) {
-
- // save the label
- // use the same value as the key and the value to get rid of duplicate results
- $chartData[$xAxisKey][$row[$xAxisKey]] = $row[$xAxisKey];
-
- // make sure to set value to every serie
- $currentSeriesLabel = (string)$row[$seriesKey];
- foreach ($seriesLabels as $seriesLabelsValue) {
- if ($currentSeriesLabel == $seriesLabelsValue) {
- // the value os for this serie
- $chartData[$yAxisKey][$seriesLabelsValue][$row[$xAxisKey]] = (int)$row[$yAxisKey];
- }
- else if (! isset($chartData[$yAxisKey][$seriesLabelsValue][$row[$xAxisKey]])) {
- // if the value for this serie is not set, set it to 0
- $chartData[$yAxisKey][$seriesLabelsValue][$row[$xAxisKey]] = 0;
- }
- }
- }
-
- $chartSettings['legend'] = true;
-
- // determine the chart type
- switch ($chartSettings['type']) {
- case 'bar':
- default:
-
- // determine the bar chart type
- switch ($chartSettings['barType']) {
- case 'stacked':
- default:
- $chart = new PMA_pChart_stacked_bar($chartData, $chartSettings);
- break;
- case 'multi':
- $chart = new PMA_pChart_multi_bar($chartData, $chartSettings);
- break;
- }
- break;
-
- case 'line':
- $chart = new PMA_pChart_multi_line($chartData, $chartSettings);
- break;
- case 'radar':
- $chart = new PMA_pChart_multi_radar($chartData, $chartSettings);
- break;
- }
- }
- else {
- // unknown data format
- return '';
- }
-
- $chartCode = $chart->toString();
- $chartSettings = $chart->getSettings();
- $chartErrors = $chart->getErrors();
- PMA_handle_chart_err($chartErrors);
-
- return $chartCode;
-}
-
-/**
- * Simple handler of chart errors.
- * @param array $errors all occured errors
- */
-function PMA_handle_chart_err($errors)
-{
- if (in_array(ERR_NO_GD, $errors)) {
- PMA_warnMissingExtension('GD', false, __('GD extension is needed for charts.'));
- }
- else if (in_array(ERR_NO_JSON, $errors)) {
- PMA_warnMissingExtension('JSON', false, __('JSON encoder is needed for chart tooltips.'));
- }
-}
-
-?>
diff --git a/libraries/chart/pChart/fonts/DejaVuSans.ttf b/libraries/chart/pChart/fonts/DejaVuSans.ttf
deleted file mode 100644
index a99969e..0000000
Binary files a/libraries/chart/pChart/fonts/DejaVuSans.ttf and /dev/null differ
diff --git a/libraries/chart/pChart/fonts/LICENSE b/libraries/chart/pChart/fonts/LICENSE
deleted file mode 100644
index 254e2cc..0000000
--- a/libraries/chart/pChart/fonts/LICENSE
+++ /dev/null
@@ -1,99 +0,0 @@
-Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
-Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
-
-Bitstream Vera Fonts Copyright
-------------------------------
-
-Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
-a trademark of Bitstream, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of the fonts accompanying this license ("Fonts") and associated
-documentation files (the "Font Software"), to reproduce and distribute the
-Font Software, including without limitation the rights to use, copy, merge,
-publish, distribute, and/or sell copies of the Font Software, and to permit
-persons to whom the Font Software is furnished to do so, subject to the
-following conditions:
-
-The above copyright and trademark notices and this permission notice shall
-be included in all copies of one or more of the Font Software typefaces.
-
-The Font Software may be modified, altered, or added to, and in particular
-the designs of glyphs or characters in the Fonts may be modified and
-additional glyphs or characters may be added to the Fonts, only if the fonts
-are renamed to names not containing either the words "Bitstream" or the word
-"Vera".
-
-This License becomes null and void to the extent applicable to Fonts or Font
-Software that has been modified and is distributed under the "Bitstream
-Vera" names.
-
-The Font Software may be sold as part of a larger software package but no
-copy of one or more of the Font Software typefaces may be sold by itself.
-
-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 BITSTREAM OR THE GNOME
-FOUNDATION 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.
-
-Except as contained in this notice, the names of Gnome, the Gnome
-Foundation, and Bitstream Inc., shall not be used in advertising or
-otherwise to promote the sale, use or other dealings in this Font Software
-without prior written authorization from the Gnome Foundation or Bitstream
-Inc., respectively. For further information, contact: fonts at gnome dot
-org.
-
-Arev Fonts Copyright
-------------------------------
-
-Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the fonts accompanying this license ("Fonts") and
-associated documentation files (the "Font Software"), to reproduce
-and distribute the modifications to the Bitstream Vera Font Software,
-including without limitation the rights to use, copy, merge, publish,
-distribute, and/or sell copies of the Font Software, and to permit
-persons to whom the Font Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright and trademark notices and this permission notice
-shall be included in all copies of one or more of the Font Software
-typefaces.
-
-The Font Software may be modified, altered, or added to, and in
-particular the designs of glyphs or characters in the Fonts may be
-modified and additional glyphs or characters may be added to the
-Fonts, only if the fonts are renamed to names not containing either
-the words "Tavmjong Bah" or the word "Arev".
-
-This License becomes null and void to the extent applicable to Fonts
-or Font Software that has been modified and is distributed under the
-"Tavmjong Bah Arev" names.
-
-The Font Software may be sold as part of a larger software package but
-no copy of one or more of the Font Software typefaces may be sold by
-itself.
-
-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
-TAVMJONG BAH 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.
-
-Except as contained in this notice, the name of Tavmjong Bah shall not
-be used in advertising or otherwise to promote the sale, use or other
-dealings in this Font Software without prior written authorization
-from Tavmjong Bah. For further information, contact: tavmjong @ free
-. fr.
-
-$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/libraries/chart/pChart/fonts/README b/libraries/chart/pChart/fonts/README
deleted file mode 100644
index 554d017..0000000
--- a/libraries/chart/pChart/fonts/README
+++ /dev/null
@@ -1,6 +0,0 @@
-
-The tahoma.ttf file which is bundled with pChart 1.27d was replaced due to uncertainty with it's licence.
-
-As a replacement the DejaVuSans.ttf font file was extracted from the DejaVu fonts 2.32 package available from dejavu-fonts.org
-
-For license information see LICENSE.
diff --git a/libraries/chart/pChart/pCache.class b/libraries/chart/pChart/pCache.class
deleted file mode 100644
index 2bcd6b0..0000000
--- a/libraries/chart/pChart/pCache.class
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
- /*
- pCache - Faster renderding using data cache
- Copyright (C) 2008 Jean-Damien POGOLOTTI
- Version 1.1.2 last updated on 06/17/08
-
- http://pchart.sourceforge.net
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 1,2,3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- Class initialisation :
- pCache($CacheFolder="Cache/")
- Cache management :
- IsInCache($Data)
- GetFromCache($ID,$Data)
- WriteToCache($ID,$Data,$Picture)
- DeleteFromCache($ID,$Data)
- ClearCache()
- Inner functions :
- GetHash($ID,$Data)
- */
-
- /* pCache class definition */
- class pCache
- {
- var $HashKey = "";
- var $CacheFolder = "Cache/";
-
- /* Create the pCache object */
- function pCache($CacheFolder="Cache/")
- {
- $this->CacheFolder = $CacheFolder;
- }
-
- /* This function is clearing the cache folder */
- function ClearCache()
- {
- if ($handle = opendir($this->CacheFolder))
- {
- while (false !== ($file = readdir($handle)))
- {
- if ( $file != "." && $file != ".." )
- unlink($this->CacheFolder.$file);
- }
- closedir($handle);
- }
- }
-
- /* This function is checking if we have an offline version of this chart */
- function IsInCache($ID,$Data,$Hash="")
- {
- if ( $Hash == "" )
- $Hash = $this->GetHash($ID,$Data);
-
- if ( file_exists($this->CacheFolder.$Hash) )
- return(TRUE);
- else
- return(FALSE);
- }
-
- /* This function is making a copy of drawn chart in the cache folder */
- function WriteToCache($ID,$Data,$Picture)
- {
- $Hash = $this->GetHash($ID,$Data);
- $FileName = $this->CacheFolder.$Hash;
-
- imagepng($Picture->Picture,$FileName);
- }
-
- /* This function is removing any cached copy of this chart */
- function DeleteFromCache($ID,$Data)
- {
- $Hash = $this->GetHash($ID,$Data);
- $FileName = $this->CacheFolder.$Hash;
-
- if ( file_exists($FileName ) )
- unlink($FileName);
- }
-
- /* This function is retrieving the cached picture if applicable */
- function GetFromCache($ID,$Data)
- {
- $Hash = $this->GetHash($ID,$Data);
- if ( $this->IsInCache("","",$Hash ) )
- {
- $FileName = $this->CacheFolder.$Hash;
-
- header('Content-type: image/png');
- @readfile($FileName);
- exit();
- }
- }
-
- /* This function is building the graph unique hash key */
- function GetHash($ID,$Data)
- {
- $mKey = "$ID";
- foreach($Data as $key => $Values)
- {
- $tKey = "";
- foreach($Values as $Serie => $Value)
- $tKey = $tKey.$Serie.$Value;
- $mKey = $mKey.md5($tKey);
- }
- return(md5($mKey));
- }
- }
-?>
\ No newline at end of file
diff --git a/libraries/chart/pChart/pChart.class b/libraries/chart/pChart/pChart.class
deleted file mode 100644
index 2b5a077..0000000
--- a/libraries/chart/pChart/pChart.class
+++ /dev/null
@@ -1,3626 +0,0 @@
-<?php
- /*
- pChart - a PHP class to build charts!
- Copyright (C) 2008 Jean-Damien POGOLOTTI
- Version 1.27d last updated on 09/30/08
-
- http://pchart.sourceforge.net
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 1,2,3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- Class initialisation :
- pChart($XSize,$YSize)
- Draw methods :
- drawBackground($R,$G,$B)
- drawRectangle($X1,$Y1,$X2,$Y2,$R,$G,$B)
- drawFilledRectangle($X1,$Y1,$X2,$Y2,$R,$G,$B,$DrawBorder=TRUE,$Alpha=100)
- drawRoundedRectangle($X1,$Y1,$X2,$Y2,$Radius,$R,$G,$B)
- drawFilledRoundedRectangle($X1,$Y1,$X2,$Y2,$Radius,$R,$G,$B)
- drawCircle($Xc,$Yc,$Height,$R,$G,$B,$Width=0)
- drawFilledCircle($Xc,$Yc,$Height,$R,$G,$B,$Width=0)
- drawEllipse($Xc,$Yc,$Height,$Width,$R,$G,$B)
- drawFilledEllipse($Xc,$Yc,$Height,$Width,$R,$G,$B)
- drawLine($X1,$Y1,$X2,$Y2,$R,$G,$B,$GraphFunction=FALSE)
- drawDottedLine($X1,$Y1,$X2,$Y2,$DotSize,$R,$G,$B)
- drawAlphaPixel($X,$Y,$Alpha,$R,$G,$B)
- drawFromPNG($FileName,$X,$Y,$Alpha=100)
- drawFromGIF($FileName,$X,$Y,$Alpha=100)
- drawFromJPG($FileName,$X,$Y,$Alpha=100)
- Graph setup methods :
- addBorder($Width=3,$R=0,$G=0,$B=0)
- clearScale()
- clearShadow()
- createColorGradientPalette($R1,$G1,$B1,$R2,$G2,$B2,$Shades)
- drawGraphArea($R,$G,$B,$Stripe=FALSE)
- drawScale($Data,$DataDescription,$ScaleMode,$R,$G,$B,$DrawTicks=TRUE,$Angle=0,$Decimals=1,$WithMargin=FALSE,$SkipLabels=1,$RightScale=FALSE)
- drawRightScale($Data,$DataDescription,$ScaleMode,$R,$G,$B,$DrawTicks=TRUE,$Angle=0,$Decimals=1,$WithMargin=FALSE,$SkipLabels=1)
- drawXYScale($Data,$DataDescription,$YSerieName,$XSerieName,$R,$G,$B,$WithMargin=0,$Angle=0,$Decimals=1)
- drawGrid($LineWidth,$Mosaic=TRUE,$R=220,$G=220,$B=220,$Alpha=100)
- drawLegend($XPos,$YPos,$DataDescription,$R,$G,$B,$Rs=-1,$Gs=-1,$Bs=-1,$Rt=0,$Gt=0,$Bt=0,$Border=FALSE)
- drawPieLegend($XPos,$YPos,$Data,$DataDescription,$R,$G,$B)
- drawTitle($XPos,$YPos,$Value,$R,$G,$B,$XPos2=-1,$YPos2=-1,$Shadow=FALSE)
- drawTreshold($Value,$R,$G,$B,$ShowLabel=FALSE,$ShowOnRight=FALSE,$TickWidth=4,$FreeText=NULL)
- drawArea($Data,$Serie1,$Serie2,$R,$G,$B,$Alpha = 50)
- drawRadarAxis($Data,$DataDescription,$Mosaic=TRUE,$BorderOffset=10,$A_R=60,$A_G=60,$A_B=60,$S_R=200,$S_G=200,$S_B=200,$MaxValue=-1)
- drawGraphAreaGradient($R,$G,$B,$Decay,$Target=TARGET_GRAPHAREA)
- drawTextBox($X1,$Y1,$X2,$Y2,$Text,$Angle=0,$R=255,$G=255,$B=255,$Align=ALIGN_LEFT,$Shadow=TRUE,$BgR=-1,$BgG=-1,$BgB=-1,$Alpha=100)
- getLegendBoxSize($DataDescription)
- loadColorPalette($FileName,$Delimiter=",")
- reportWarnings($Interface="CLI")
- setGraphArea($X1,$Y1,$X2,$Y2)
- setLabel($Data,$DataDescription,$SerieName,$ValueName,$Caption,$R=210,$G=210,$B=210)
- setColorPalette($ID,$R,$G,$B)
- setCurrency($Currency)
- setDateFormat($Format)
- setFontProperties($FontName,$FontSize)
- setLineStyle($Width=1,$DotSize=0)
- setFixedScale($VMin,$VMax,$Divisions=5,$VXMin=0,$VXMin=0,$XDivisions=5)
- setShadowProperties($XDistance=1,$YDistance=1,$R=60,$G=60,$B=60,$Alpha)
- writeValues($Data,$DataDescription,$Series)
- Graphs methods :
- drawPlotGraph($Data,$DataDescription,$BigRadius=5,$SmallRadius=2,$R2=-1,$G2=-1,$B2=-1,$Shadow=FALSE)
- drawXYPlotGraph($Data,$DataDescription,$YSerieName,$XSerieName,$PaletteID=0,$BigRadius=5,$SmallRadius=2,$R2=-1,$G2=-1,$B2=-1)
- drawLineGraph($Data,$DataDescription,$SerieName="")
- drawXYGraph($Data,$DataDescription,$YSerieName,$XSerieName,$PaletteID=0)
- drawFilledLineGraph($Data,$DataDescription,$Alpha=100,$AroundZero=FALSE)
- drawCubicCurve($Data,$DataDescription,$Accuracy=.1,$SerieName="")
- drawFilledCubicCurve($Data,$DataDescription,$Accuracy=.1,$Alpha=100,$AroundZero=FALSE)
- drawOverlayBarGraph($Data,$DataDescription,$Alpha=50)
- drawBarGraph($Data,$DataDescription,$Shadow=FALSE)
- drawStackedBarGraph($Data,$DataDescription,$Alpha=50,$Contiguous=FALSE)
- drawLimitsGraph($Data,$DataDescription,$R=0,$G=0,$B=0)
- drawRadar($Data,$DataDescription,$BorderOffset=10,$MaxValue=-1)
- drawFilledRadar($Data,$DataDescription,$Alpha=50,$BorderOffset=10,$MaxValue=-1)
- drawBasicPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$R=255,$G=255,$B=255,$Decimals=0)
- drawFlatPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$SpliceDistance=0,$Decimals = 0)
- drawFlatPieGraphWithShadow($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$SpliceDistance=0,$Decimals = 0)
- drawPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$EnhanceColors=TRUE,$Skew=60,$SpliceHeight=20,$SpliceDistance=0,$Decimals=0)
- Other methods :
- setImageMap($Mode=TRUE,$GraphID="MyGraph")
- getImageMap()
- getSavedImageMap($MapName,$Flush=TRUE)
- Render($FileName)
- Stroke()
- */
-
- /* Declare some script wide constants */
- define("SCALE_NORMAL",1);
- define("SCALE_ADDALL",2);
- define("SCALE_START0",3);
- define("SCALE_ADDALLSTART0",4);
- define("PIE_PERCENTAGE", 1);
- define("PIE_LABELS",2);
- define("PIE_NOLABEL",3);
- define("PIE_PERCENTAGE_LABEL", 4);
- define("TARGET_GRAPHAREA",1);
- define("TARGET_BACKGROUND",2);
- define("ALIGN_TOP_LEFT",1);
- define("ALIGN_TOP_CENTER",2);
- define("ALIGN_TOP_RIGHT",3);
- define("ALIGN_LEFT",4);
- define("ALIGN_CENTER",5);
- define("ALIGN_RIGHT",6);
- define("ALIGN_BOTTOM_LEFT",7);
- define("ALIGN_BOTTOM_CENTER",8);
- define("ALIGN_BOTTOM_RIGHT",9);
-
- /* pChart class definition */
- class pChart
- {
- /* Palettes definition */
- var $Palette = array("0"=>array("R"=>188,"G"=>224,"B"=>46),
- "1"=>array("R"=>224,"G"=>100,"B"=>46),
- "2"=>array("R"=>224,"G"=>214,"B"=>46),
- "3"=>array("R"=>46,"G"=>151,"B"=>224),
- "4"=>array("R"=>176,"G"=>46,"B"=>224),
- "5"=>array("R"=>224,"G"=>46,"B"=>117),
- "6"=>array("R"=>92,"G"=>224,"B"=>46),
- "7"=>array("R"=>224,"G"=>176,"B"=>46));
-
- /* Some static vars used in the class */
- var $XSize = NULL;
- var $YSize = NULL;
- var $Picture = NULL;
- var $ImageMap = NULL;
-
- /* Error management */
- var $ErrorReporting = FALSE;
- var $ErrorInterface = "CLI";
- var $Errors = NULL;
- var $ErrorFontName = "Fonts/pf_arma_five.ttf";
- var $ErrorFontSize = 6;
-
- /* vars related to the graphing area */
- var $GArea_X1 = NULL;
- var $GArea_Y1 = NULL;
- var $GArea_X2 = NULL;
- var $GArea_Y2 = NULL;
- var $GAreaXOffset = NULL;
- var $VMax = NULL;
- var $VMin = NULL;
- var $VXMax = NULL;
- var $VXMin = NULL;
- var $Divisions = NULL;
- var $XDivisions = NULL;
- var $DivisionHeight = NULL;
- var $XDivisionHeight = NULL;
- var $DivisionCount = NULL;
- var $XDivisionCount = NULL;
- var $DivisionRatio = NULL;
- var $XDivisionRatio = NULL;
- var $DivisionWidth = NULL;
- var $DataCount = NULL;
- var $Currency = "\$";
-
- /* Text format related vars */
- var $FontName = NULL;
- var $FontSize = NULL;
- var $DateFormat = "d/m/Y";
-
- /* Lines format related vars */
- var $LineWidth = 1;
- var $LineDotSize = 0;
-
- /* Layer related vars */
- var $Layers = NULL;
-
- /* Set antialias quality : 0 is maximum, 100 minimum*/
- var $AntialiasQuality = 0;
-
- /* Shadow settings */
- var $ShadowActive = FALSE;
- var $ShadowXDistance = 1;
- var $ShadowYDistance = 1;
- var $ShadowRColor = 60;
- var $ShadowGColor = 60;
- var $ShadowBColor = 60;
- var $ShadowAlpha = 50;
- var $ShadowBlur = 0;
-
- /* Image Map settings */
- var $BuildMap = FALSE;
- var $MapFunction = NULL;
- var $tmpFolder = "tmp/";
- var $MapID = NULL;
-
- /* This function create the background picture */
- function pChart($XSize,$YSize)
- {
- $this->XSize = $XSize;
- $this->YSize = $YSize;
- $this->Picture = imagecreatetruecolor($XSize,$YSize);
- $C_White =$this->AllocateColor($this->Picture,255,255,255);
- imagefilledrectangle($this->Picture,0,0,$XSize,$YSize,$C_White);
- imagecolortransparent($this->Picture,$C_White);
- $this->setFontProperties("tahoma.ttf",8);
- }
-
- /* Set if warnings should be reported */
- function reportWarnings($Interface="CLI")
- {
- $this->ErrorReporting = TRUE;
- $this->ErrorInterface = $Interface;
- }
-
- /* Set the font properties */
- function setFontProperties($FontName,$FontSize)
- {
- $this->FontName = $FontName;
- $this->FontSize = $FontSize;
- }
-
- /* Set the shadow properties */
- function setShadowProperties($XDistance=1,$YDistance=1,$R=60,$G=60,$B=60,$Alpha=50,$Blur=0)
- {
- $this->ShadowActive = TRUE;
- $this->ShadowXDistance = $XDistance;
- $this->ShadowYDistance = $YDistance;
- $this->ShadowRColor = $R;
- $this->ShadowGColor = $G;
- $this->ShadowBColor = $B;
- $this->ShadowAlpha = $Alpha;
- $this->ShadowBlur = $Blur;
- }
-
- /* Remove shadow option */
- function clearShadow()
- {
- $this->ShadowActive = FALSE;
- }
-
- /* Set Palette color */
- function setColorPalette($ID,$R,$G,$B)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $this->Palette[$ID]["R"] = $R;
- $this->Palette[$ID]["G"] = $G;
- $this->Palette[$ID]["B"] = $B;
- }
-
- /* Create a color palette shading from one color to another */
- function createColorGradientPalette($R1,$G1,$B1,$R2,$G2,$B2,$Shades)
- {
- $RFactor = ($R2-$R1)/$Shades;
- $GFactor = ($G2-$G1)/$Shades;
- $BFactor = ($B2-$B1)/$Shades;
-
- for($i=0;$i<=$Shades-1;$i++)
- {
- $this->Palette[$i]["R"] = $R1+$RFactor*$i;
- $this->Palette[$i]["G"] = $G1+$GFactor*$i;
- $this->Palette[$i]["B"] = $B1+$BFactor*$i;
- }
- }
-
- /* Load Color Palette from file */
- function loadColorPalette($FileName,$Delimiter=",")
- {
- $handle = @fopen($FileName,"r");
- $ColorID = 0;
- if ($handle)
- {
- while (!feof($handle))
- {
- $buffer = fgets($handle, 4096);
- $buffer = str_replace(chr(10),"",$buffer);
- $buffer = str_replace(chr(13),"",$buffer);
- $Values = split($Delimiter,$buffer);
- if ( count($Values) == 3 )
- {
- $this->Palette[$ColorID]["R"] = $Values[0];
- $this->Palette[$ColorID]["G"] = $Values[1];
- $this->Palette[$ColorID]["B"] = $Values[2];
- $ColorID++;
- }
- }
- }
- }
-
- /* Set line style */
- function setLineStyle($Width=1,$DotSize=0)
- {
- $this->LineWidth = $Width;
- $this->LineDotSize = $DotSize;
- }
-
- /* Set currency symbol */
- function setCurrency($Currency)
- {
- $this->Currency = $Currency;
- }
-
- /* Set the graph area location */
- function setGraphArea($X1,$Y1,$X2,$Y2)
- {
- $this->GArea_X1 = $X1;
- $this->GArea_Y1 = $Y1;
- $this->GArea_X2 = $X2;
- $this->GArea_Y2 = $Y2;
- }
-
- /* Prepare the graph area */
- function drawGraphArea($R,$G,$B,$Stripe=FALSE)
- {
- $this->drawFilledRectangle($this->GArea_X1,$this->GArea_Y1,$this->GArea_X2,$this->GArea_Y2,$R,$G,$B,FALSE);
- $this->drawRectangle($this->GArea_X1,$this->GArea_Y1,$this->GArea_X2,$this->GArea_Y2,$R-40,$G-40,$B-40);
-
- if ( $Stripe )
- {
- $R2 = $R-15; if ( $R2 < 0 ) { $R2 = 0; }
- $G2 = $R-15; if ( $G2 < 0 ) { $G2 = 0; }
- $B2 = $R-15; if ( $B2 < 0 ) { $B2 = 0; }
-
- $LineColor =$this->AllocateColor($this->Picture,$R2,$G2,$B2);
- $SkewWidth = $this->GArea_Y2-$this->GArea_Y1-1;
-
- for($i=$this->GArea_X1-$SkewWidth;$i<=$this->GArea_X2;$i=$i+4)
- {
- $X1 = $i; $Y1 = $this->GArea_Y2;
- $X2 = $i+$SkewWidth; $Y2 = $this->GArea_Y1;
-
-
- if ( $X1 < $this->GArea_X1 )
- { $X1 = $this->GArea_X1; $Y1 = $this->GArea_Y1 + $X2 - $this->GArea_X1 + 1; }
-
- if ( $X2 >= $this->GArea_X2 )
- { $Y2 = $this->GArea_Y1 + $X2 - $this->GArea_X2 +1; $X2 = $this->GArea_X2 - 1; }
-// * Fixed in 1.27 * { $X2 = $this->GArea_X2 - 1; $Y2 = $this->GArea_Y2 - ($this->GArea_X2 - $X1); }
-
- imageline($this->Picture,$X1,$Y1,$X2,$Y2+1,$LineColor);
- }
- }
- }
-
- /* Allow you to clear the scale : used if drawing multiple charts */
- function clearScale()
- {
- $this->VMin = NULL;
- $this->VMax = NULL;
- $this->VXMin = NULL;
- $this->VXMax = NULL;
- $this->Divisions = NULL;
- $this->XDivisions = NULL; }
-
- /* Allow you to fix the scale, use this to bypass the automatic scaling */
- function setFixedScale($VMin,$VMax,$Divisions=5,$VXMin=0,$VXMax=0,$XDivisions=5)
- {
- $this->VMin = $VMin;
- $this->VMax = $VMax;
- $this->Divisions = $Divisions;
-
- if ( !$VXMin == 0 )
- {
- $this->VXMin = $VXMin;
- $this->VXMax = $VXMax;
- $this->XDivisions = $XDivisions;
- }
- }
-
- /* Wrapper to the drawScale() function allowing a second scale to be drawn */
- function drawRightScale($Data,$DataDescription,$ScaleMode,$R,$G,$B,$DrawTicks=TRUE,$Angle=0,$Decimals=1,$WithMargin=FALSE,$SkipLabels=1)
- {
- $this->drawScale($Data,$DataDescription,$ScaleMode,$R,$G,$B,$DrawTicks,$Angle,$Decimals,$WithMargin,$SkipLabels,TRUE);
- }
-
- /* Compute and draw the scale */
- function drawScale($Data,$DataDescription,$ScaleMode,$R,$G,$B,$DrawTicks=TRUE,$Angle=0,$Decimals=1,$WithMargin=FALSE,$SkipLabels=1,$RightScale=FALSE)
- {
- /* Validate the Data and DataDescription array */
- $this->validateData("drawScale",$Data);
-
- $C_TextColor =$this->AllocateColor($this->Picture,$R,$G,$B);
-
- $this->drawLine($this->GArea_X1,$this->GArea_Y1,$this->GArea_X1,$this->GArea_Y2,$R,$G,$B);
- $this->drawLine($this->GArea_X1,$this->GArea_Y2,$this->GArea_X2,$this->GArea_Y2,$R,$G,$B);
-
- if ( $this->VMin == NULL && $this->VMax == NULL)
- {
- if (isset($DataDescription["Values"][0]))
- {
- $this->VMin = $Data[0][$DataDescription["Values"][0]];
- $this->VMax = $Data[0][$DataDescription["Values"][0]];
- }
- else { $this->VMin = 2147483647; $this->VMax = -2147483647; }
-
- /* Compute Min and Max values */
- if ( $ScaleMode == SCALE_NORMAL || $ScaleMode == SCALE_START0 )
- {
- if ( $ScaleMode == SCALE_START0 ) { $this->VMin = 0; }
-
- foreach ( $Data as $Key => $Values )
- {
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if (isset($Data[$Key][$ColName]))
- {
- $Value = $Data[$Key][$ColName];
-
- if ( is_numeric($Value) )
- {
- if ( $this->VMax < $Value) { $this->VMax = $Value; }
- if ( $this->VMin > $Value) { $this->VMin = $Value; }
- }
- }
- }
- }
- }
- elseif ( $ScaleMode == SCALE_ADDALL || $ScaleMode == SCALE_ADDALLSTART0 ) /* Experimental */
- {
- if ( $ScaleMode == SCALE_ADDALLSTART0 ) { $this->VMin = 0; }
-
- foreach ( $Data as $Key => $Values )
- {
- $Sum = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if (isset($Data[$Key][$ColName]))
- {
- $Value = $Data[$Key][$ColName];
- if ( is_numeric($Value) )
- $Sum += $Value;
- }
- }
- if ( $this->VMax < $Sum) { $this->VMax = $Sum; }
- if ( $this->VMin > $Sum) { $this->VMin = $Sum; }
- }
- }
-
- if ( $this->VMax > preg_replace('/\.[0-9]+/','',$this->VMax) )
- $this->VMax = preg_replace('/\.[0-9]+/','',$this->VMax)+1;
-
- /* If all values are the same */
- if ( $this->VMax == $this->VMin )
- {
- if ( $this->VMax >= 0 ) { $this->VMax++; }
- else { $this->VMin--; }
- }
-
- $DataRange = $this->VMax - $this->VMin;
- if ( $DataRange == 0 ) { $DataRange = .1; }
-
- /* Compute automatic scaling */
- $ScaleOk = FALSE; $Factor = 1;
- $MinDivHeight = 25; $MaxDivs = ($this->GArea_Y2 - $this->GArea_Y1) / $MinDivHeight;
-
- if ( $this->VMin == 0 && $this->VMax == 0 )
- { $this->VMin = 0; $this->VMax = 2; $Scale = 1; $Divisions = 2;}
- elseif ($MaxDivs > 1)
- {
- while(!$ScaleOk)
- {
- $Scale1 = ( $this->VMax - $this->VMin ) / $Factor;
- $Scale2 = ( $this->VMax - $this->VMin ) / $Factor / 2;
- $Scale4 = ( $this->VMax - $this->VMin ) / $Factor / 4;
-
- if ( $Scale1 > 1 && $Scale1 <= $MaxDivs && !$ScaleOk) { $ScaleOk = TRUE; $Divisions = floor($Scale1); $Scale = 1;}
- if ( $Scale2 > 1 && $Scale2 <= $MaxDivs && !$ScaleOk) { $ScaleOk = TRUE; $Divisions = floor($Scale2); $Scale = 2;}
- if (!$ScaleOk)
- {
- if ( $Scale2 > 1 ) { $Factor = $Factor * 10; }
- if ( $Scale2 < 1 ) { $Factor = $Factor / 10; }
- }
- }
-
- if ( floor($this->VMax / $Scale / $Factor) != $this->VMax / $Scale / $Factor)
- {
- $GridID = floor ( $this->VMax / $Scale / $Factor) + 1;
- $this->VMax = $GridID * $Scale * $Factor;
- $Divisions++;
- }
-
- if ( floor($this->VMin / $Scale / $Factor) != $this->VMin / $Scale / $Factor)
- {
- $GridID = floor( $this->VMin / $Scale / $Factor);
- $this->VMin = $GridID * $Scale * $Factor;
- $Divisions++;
- }
- }
- else /* Can occurs for small graphs */
- $Scale = 1;
-
- if ( !isset($Divisions) )
- $Divisions = 2;
-
- if ($Scale == 1 && $Divisions%2 == 1)
- $Divisions--;
- }
- else
- $Divisions = $this->Divisions;
-
- $this->DivisionCount = $Divisions;
-
- $DataRange = $this->VMax - $this->VMin;
- if ( $DataRange == 0 ) { $DataRange = .1; }
-
- $this->DivisionHeight = ( $this->GArea_Y2 - $this->GArea_Y1 ) / $Divisions;
- $this->DivisionRatio = ( $this->GArea_Y2 - $this->GArea_Y1 ) / $DataRange;
-
- $this->GAreaXOffset = 0;
- if ( count($Data) > 1 )
- {
- if ( $WithMargin == FALSE )
- $this->DivisionWidth = ( $this->GArea_X2 - $this->GArea_X1 ) / (count($Data)-1);
- else
- {
- $this->DivisionWidth = ( $this->GArea_X2 - $this->GArea_X1 ) / (count($Data));
- $this->GAreaXOffset = $this->DivisionWidth / 2;
- }
- }
- else
- {
- $this->DivisionWidth = $this->GArea_X2 - $this->GArea_X1;
- $this->GAreaXOffset = $this->DivisionWidth / 2;
- }
-
- $this->DataCount = count($Data);
-
- if ( $DrawTicks == FALSE )
- return(0);
-
- $YPos = $this->GArea_Y2; $XMin = NULL;
- for($i=1;$i<=$Divisions+1;$i++)
- {
- if ( $RightScale )
- $this->drawLine($this->GArea_X2,$YPos,$this->GArea_X2+5,$YPos,$R,$G,$B);
- else
- $this->drawLine($this->GArea_X1,$YPos,$this->GArea_X1-5,$YPos,$R,$G,$B);
-
- $Value = $this->VMin + ($i-1) * (( $this->VMax - $this->VMin ) / $Divisions);
- $Value = round($Value * pow(10,$Decimals)) / pow(10,$Decimals);
- if ( $DataDescription["Format"]["Y"] == "number" )
- $Value = $Value.$DataDescription["Unit"]["Y"];
- if ( $DataDescription["Format"]["Y"] == "time" )
- $Value = $this->ToTime($Value);
- if ( $DataDescription["Format"]["Y"] == "date" )
- $Value = $this->ToDate($Value);
- if ( $DataDescription["Format"]["Y"] == "metric" )
- $Value = $this->ToMetric($Value);
- if ( $DataDescription["Format"]["Y"] == "currency" )
- $Value = $this->ToCurrency($Value);
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextWidth = $Position[2]-$Position[0];
-
- if ( $RightScale )
- {
- imagettftext($this->Picture,$this->FontSize,0,$this->GArea_X2+10,$YPos+($this->FontSize/2),$C_TextColor,$this->FontName,$Value);
- if ( $XMin < $this->GArea_X2+15+$TextWidth || $XMin == NULL ) { $XMin = $this->GArea_X2+15+$TextWidth; }
- }
- else
- {
- imagettftext($this->Picture,$this->FontSize,0,$this->GArea_X1-10-$TextWidth,$YPos+($this->FontSize/2),$C_TextColor,$this->FontName,$Value);
- if ( $XMin > $this->GArea_X1-10-$TextWidth || $XMin == NULL ) { $XMin = $this->GArea_X1-10-$TextWidth; }
- }
-
- $YPos = $YPos - $this->DivisionHeight;
- }
-
- /* Write the Y Axis caption if set */
- if ( isset($DataDescription["Axis"]["Y"]) )
- {
- $Position = imageftbbox($this->FontSize,90,$this->FontName,$DataDescription["Axis"]["Y"]);
- $TextHeight = abs($Position[1])+abs($Position[3]);
- $TextTop = (($this->GArea_Y2 - $this->GArea_Y1) / 2) + $this->GArea_Y1 + ($TextHeight/2);
-
- if ( $RightScale )
- imagettftext($this->Picture,$this->FontSize,90,$XMin+$this->FontSize,$TextTop,$C_TextColor,$this->FontName,$DataDescription["Axis"]["Y"]);
- else
- imagettftext($this->Picture,$this->FontSize,90,$XMin-$this->FontSize,$TextTop,$C_TextColor,$this->FontName,$DataDescription["Axis"]["Y"]);
- }
-
- /* Horizontal Axis */
- $XPos = $this->GArea_X1 + $this->GAreaXOffset;
- $ID = 1; $YMax = NULL;
- foreach ( $Data as $Key => $Values )
- {
- if ( $ID % $SkipLabels == 0 )
- {
- $this->drawLine(floor($XPos),$this->GArea_Y2,floor($XPos),$this->GArea_Y2+5,$R,$G,$B);
- $Value = $Data[$Key][$DataDescription["Position"]];
- if ( $DataDescription["Format"]["X"] == "number" )
- $Value = $Value.$DataDescription["Unit"]["X"];
- if ( $DataDescription["Format"]["X"] == "time" )
- $Value = $this->ToTime($Value);
- if ( $DataDescription["Format"]["X"] == "date" )
- $Value = $this->ToDate($Value);
- if ( $DataDescription["Format"]["X"] == "metric" )
- $Value = $this->ToMetric($Value);
- if ( $DataDescription["Format"]["X"] == "currency" )
- $Value = $this->ToCurrency($Value);
-
- $Position = imageftbbox($this->FontSize,$Angle,$this->FontName,$Value);
- $TextWidth = abs($Position[2])+abs($Position[0]);
- $TextHeight = abs($Position[1])+abs($Position[3]);
-
- if ( $Angle == 0 )
- {
- $YPos = $this->GArea_Y2+18;
- imagettftext($this->Picture,$this->FontSize,$Angle,floor($XPos)-floor($TextWidth/2),$YPos,$C_TextColor,$this->FontName,$Value);
- }
- else
- {
- $YPos = $this->GArea_Y2+10+$TextHeight;
- if ( $Angle <= 90 )
- imagettftext($this->Picture,$this->FontSize,$Angle,floor($XPos)-$TextWidth+5,$YPos,$C_TextColor,$this->FontName,$Value);
- else
- imagettftext($this->Picture,$this->FontSize,$Angle,floor($XPos)+$TextWidth+5,$YPos,$C_TextColor,$this->FontName,$Value);
- }
- if ( $YMax < $YPos || $YMax == NULL ) { $YMax = $YPos; }
- }
-
- $XPos = $XPos + $this->DivisionWidth;
- $ID++;
- }
-
- /* Write the X Axis caption if set */
- if ( isset($DataDescription["Axis"]["X"]) )
- {
- $Position = imageftbbox($this->FontSize,90,$this->FontName,$DataDescription["Axis"]["X"]);
- $TextWidth = abs($Position[2])+abs($Position[0]);
- $TextLeft = (($this->GArea_X2 - $this->GArea_X1) / 2) + $this->GArea_X1 + ($TextWidth/2);
- imagettftext($this->Picture,$this->FontSize,0,$TextLeft,$YMax+$this->FontSize+5,$C_TextColor,$this->FontName,$DataDescription["Axis"]["X"]);
- }
- }
-
- /* Compute and draw the scale for X/Y charts */
- function drawXYScale($Data,$DataDescription,$YSerieName,$XSerieName,$R,$G,$B,$WithMargin=0,$Angle=0,$Decimals=1)
- {
- /* Validate the Data and DataDescription array */
- $this->validateData("drawScale",$Data);
-
- $C_TextColor =$this->AllocateColor($this->Picture,$R,$G,$B);
-
- $this->drawLine($this->GArea_X1,$this->GArea_Y1,$this->GArea_X1,$this->GArea_Y2,$R,$G,$B);
- $this->drawLine($this->GArea_X1,$this->GArea_Y2,$this->GArea_X2,$this->GArea_Y2,$R,$G,$B);
-
- /* Process Y scale */
- if ( $this->VMin == NULL && $this->VMax == NULL)
- {
- $this->VMin = $Data[0][$YSerieName];
- $this->VMax = $Data[0][$YSerieName];
-
- foreach ( $Data as $Key => $Values )
- {
- if (isset($Data[$Key][$YSerieName]))
- {
- $Value = $Data[$Key][$YSerieName];
- if ( $this->VMax < $Value) { $this->VMax = $Value; }
- if ( $this->VMin > $Value) { $this->VMin = $Value; }
- }
- }
-
- if ( $this->VMax > preg_replace('/\.[0-9]+/','',$this->VMax) )
- $this->VMax = preg_replace('/\.[0-9]+/','',$this->VMax)+1;
-
- $DataRange = $this->VMax - $this->VMin;
- if ( $DataRange == 0 ) { $DataRange = .1; }
-
- /* Compute automatic scaling */
- $ScaleOk = FALSE; $Factor = 1;
- $MinDivHeight = 25; $MaxDivs = ($this->GArea_Y2 - $this->GArea_Y1) / $MinDivHeight;
-
- if ( $this->VMin == 0 && $this->VMax == 0 )
- { $this->VMin = 0; $this->VMax = 2; $Scale = 1; $Divisions = 2;}
- elseif ($MaxDivs > 1)
- {
- while(!$ScaleOk)
- {
- $Scale1 = ( $this->VMax - $this->VMin ) / $Factor;
- $Scale2 = ( $this->VMax - $this->VMin ) / $Factor / 2;
- $Scale4 = ( $this->VMax - $this->VMin ) / $Factor / 4;
-
- if ( $Scale1 > 1 && $Scale1 <= $MaxDivs && !$ScaleOk) { $ScaleOk = TRUE; $Divisions = floor($Scale1); $Scale = 1;}
- if ( $Scale2 > 1 && $Scale2 <= $MaxDivs && !$ScaleOk) { $ScaleOk = TRUE; $Divisions = floor($Scale2); $Scale = 2;}
- if (!$ScaleOk)
- {
- if ( $Scale2 > 1 ) { $Factor = $Factor * 10; }
- if ( $Scale2 < 1 ) { $Factor = $Factor / 10; }
- }
- }
-
- if ( floor($this->VMax / $Scale / $Factor) != $this->VMax / $Scale / $Factor)
- {
- $GridID = floor ( $this->VMax / $Scale / $Factor) + 1;
- $this->VMax = $GridID * $Scale * $Factor;
- $Divisions++;
- }
-
- if ( floor($this->VMin / $Scale / $Factor) != $this->VMin / $Scale / $Factor)
- {
- $GridID = floor( $this->VMin / $Scale / $Factor);
- $this->VMin = $GridID * $Scale * $Factor;
- $Divisions++;
- }
- }
- else /* Can occurs for small graphs */
- $Scale = 1;
-
- if ( !isset($Divisions) )
- $Divisions = 2;
-
- if ( $this->isRealInt(($this->VMax-$this->VMin)/($Divisions-1)))
- $Divisions--;
- elseif ( $this->isRealInt(($this->VMax-$this->VMin)/($Divisions+1)))
- $Divisions++;
- }
- else
- $Divisions = $this->Divisions;
-
- $this->DivisionCount = $Divisions;
-
- $DataRange = $this->VMax - $this->VMin;
- if ( $DataRange == 0 ) { $DataRange = .1; }
-
- $this->DivisionHeight = ( $this->GArea_Y2 - $this->GArea_Y1 ) / $Divisions;
- $this->DivisionRatio = ( $this->GArea_Y2 - $this->GArea_Y1 ) / $DataRange;
-
- $YPos = $this->GArea_Y2; $XMin = NULL;
- for($i=1;$i<=$Divisions+1;$i++)
- {
- $this->drawLine($this->GArea_X1,$YPos,$this->GArea_X1-5,$YPos,$R,$G,$B);
- $Value = $this->VMin + ($i-1) * (( $this->VMax - $this->VMin ) / $Divisions);
- $Value = round($Value * pow(10,$Decimals)) / pow(10,$Decimals);
- if ( $DataDescription["Format"]["Y"] == "number" )
- $Value = $Value.$DataDescription["Unit"]["Y"];
- if ( $DataDescription["Format"]["Y"] == "time" )
- $Value = $this->ToTime($Value);
- if ( $DataDescription["Format"]["Y"] == "date" )
- $Value = $this->ToDate($Value);
- if ( $DataDescription["Format"]["Y"] == "metric" )
- $Value = $this->ToMetric($Value);
- if ( $DataDescription["Format"]["Y"] == "currency" )
- $Value = $this->ToCurrency($Value);
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextWidth = $Position[2]-$Position[0];
- imagettftext($this->Picture,$this->FontSize,0,$this->GArea_X1-10-$TextWidth,$YPos+($this->FontSize/2),$C_TextColor,$this->FontName,$Value);
-
- if ( $XMin > $this->GArea_X1-10-$TextWidth || $XMin == NULL ) { $XMin = $this->GArea_X1-10-$TextWidth; }
-
- $YPos = $YPos - $this->DivisionHeight;
- }
-
- /* Process X scale */
- if ( $this->VXMin == NULL && $this->VXMax == NULL)
- {
- $this->VXMin = $Data[0][$XSerieName];
- $this->VXMax = $Data[0][$XSerieName];
-
- foreach ( $Data as $Key => $Values )
- {
- if (isset($Data[$Key][$XSerieName]))
- {
- $Value = $Data[$Key][$XSerieName];
- if ( $this->VXMax < $Value) { $this->VXMax = $Value; }
- if ( $this->VXMin > $Value) { $this->VXMin = $Value; }
- }
- }
-
- if ( $this->VXMax > preg_replace('/\.[0-9]+/','',$this->VXMax) )
- $this->VXMax = preg_replace('/\.[0-9]+/','',$this->VXMax)+1;
-
- $DataRange = $this->VMax - $this->VMin;
- if ( $DataRange == 0 ) { $DataRange = .1; }
-
- /* Compute automatic scaling */
- $ScaleOk = FALSE; $Factor = 1;
- $MinDivWidth = 25; $MaxDivs = ($this->GArea_X2 - $this->GArea_X1) / $MinDivWidth;
-
- if ( $this->VXMin == 0 && $this->VXMax == 0 )
- { $this->VXMin = 0; $this->VXMax = 2; $Scale = 1; $XDivisions = 2;}
- elseif ($MaxDivs > 1)
- {
- while(!$ScaleOk)
- {
- $Scale1 = ( $this->VXMax - $this->VXMin ) / $Factor;
- $Scale2 = ( $this->VXMax - $this->VXMin ) / $Factor / 2;
- $Scale4 = ( $this->VXMax - $this->VXMin ) / $Factor / 4;
-
- if ( $Scale1 > 1 && $Scale1 <= $MaxDivs && !$ScaleOk) { $ScaleOk = TRUE; $XDivisions = floor($Scale1); $Scale = 1;}
- if ( $Scale2 > 1 && $Scale2 <= $MaxDivs && !$ScaleOk) { $ScaleOk = TRUE; $XDivisions = floor($Scale2); $Scale = 2;}
- if (!$ScaleOk)
- {
- if ( $Scale2 > 1 ) { $Factor = $Factor * 10; }
- if ( $Scale2 < 1 ) { $Factor = $Factor / 10; }
- }
- }
-
- if ( floor($this->VXMax / $Scale / $Factor) != $this->VXMax / $Scale / $Factor)
- {
- $GridID = floor ( $this->VXMax / $Scale / $Factor) + 1;
- $this->VXMax = $GridID * $Scale * $Factor;
- $XDivisions++;
- }
-
- if ( floor($this->VXMin / $Scale / $Factor) != $this->VXMin / $Scale / $Factor)
- {
- $GridID = floor( $this->VXMin / $Scale / $Factor);
- $this->VXMin = $GridID * $Scale * $Factor;
- $XDivisions++;
- }
- }
- else /* Can occurs for small graphs */
- $Scale = 1;
-
- if ( !isset($XDivisions) )
- $XDivisions = 2;
-
- if ( $this->isRealInt(($this->VXMax-$this->VXMin)/($XDivisions-1)))
- $XDivisions--;
- elseif ( $this->isRealInt(($this->VXMax-$this->VXMin)/($XDivisions+1)))
- $XDivisions++;
- }
- else
- $XDivisions = $this->XDivisions;
-
- $this->XDivisionCount = $Divisions;
- $this->DataCount = $Divisions + 2;
-
- $XDataRange = $this->VXMax - $this->VXMin;
- if ( $XDataRange == 0 ) { $XDataRange = .1; }
-
- $this->DivisionWidth = ( $this->GArea_X2 - $this->GArea_X1 ) / $XDivisions;
- $this->XDivisionRatio = ( $this->GArea_X2 - $this->GArea_X1 ) / $XDataRange;
-
- $XPos = $this->GArea_X1; $YMax = NULL;
- for($i=1;$i<=$XDivisions+1;$i++)
- {
- $this->drawLine($XPos,$this->GArea_Y2,$XPos,$this->GArea_Y2+5,$R,$G,$B);
-
- $Value = $this->VXMin + ($i-1) * (( $this->VXMax - $this->VXMin ) / $XDivisions);
- $Value = round($Value * pow(10,$Decimals)) / pow(10,$Decimals);
- if ( $DataDescription["Format"]["Y"] == "number" )
- $Value = $Value.$DataDescription["Unit"]["Y"];
- if ( $DataDescription["Format"]["Y"] == "time" )
- $Value = $this->ToTime($Value);
- if ( $DataDescription["Format"]["Y"] == "date" )
- $Value = $this->ToDate($Value);
- if ( $DataDescription["Format"]["Y"] == "metric" )
- $Value = $this->ToMetric($Value);
- if ( $DataDescription["Format"]["Y"] == "currency" )
- $Value = $this->ToCurrency($Value);
-
- $Position = imageftbbox($this->FontSize,$Angle,$this->FontName,$Value);
- $TextWidth = abs($Position[2])+abs($Position[0]);
- $TextHeight = abs($Position[1])+abs($Position[3]);
-
- if ( $Angle == 0 )
- {
- $YPos = $this->GArea_Y2+18;
- imagettftext($this->Picture,$this->FontSize,$Angle,floor($XPos)-floor($TextWidth/2),$YPos,$C_TextColor,$this->FontName,$Value);
- }
- else
- {
- $YPos = $this->GArea_Y2+10+$TextHeight;
- if ( $Angle <= 90 )
- imagettftext($this->Picture,$this->FontSize,$Angle,floor($XPos)-$TextWidth+5,$YPos,$C_TextColor,$this->FontName,$Value);
- else
- imagettftext($this->Picture,$this->FontSize,$Angle,floor($XPos)+$TextWidth+5,$YPos,$C_TextColor,$this->FontName,$Value);
- }
-
- if ( $YMax < $YPos || $YMax == NULL ) { $YMax = $YPos; }
-
- $XPos = $XPos + $this->DivisionWidth;
- }
-
- /* Write the Y Axis caption if set */
- if ( isset($DataDescription["Axis"]["Y"]) )
- {
- $Position = imageftbbox($this->FontSize,90,$this->FontName,$DataDescription["Axis"]["Y"]);
- $TextHeight = abs($Position[1])+abs($Position[3]);
- $TextTop = (($this->GArea_Y2 - $this->GArea_Y1) / 2) + $this->GArea_Y1 + ($TextHeight/2);
- imagettftext($this->Picture,$this->FontSize,90,$XMin-$this->FontSize,$TextTop,$C_TextColor,$this->FontName,$DataDescription["Axis"]["Y"]);
- }
-
- /* Write the X Axis caption if set */
- if ( isset($DataDescription["Axis"]["X"]) )
- {
- $Position = imageftbbox($this->FontSize,90,$this->FontName,$DataDescription["Axis"]["X"]);
- $TextWidth = abs($Position[2])+abs($Position[0]);
- $TextLeft = (($this->GArea_X2 - $this->GArea_X1) / 2) + $this->GArea_X1 + ($TextWidth/2);
- imagettftext($this->Picture,$this->FontSize,0,$TextLeft,$YMax+$this->FontSize+5,$C_TextColor,$this->FontName,$DataDescription["Axis"]["X"]);
- }
- }
-
- /* Compute and draw the scale */
- function drawGrid($LineWidth,$Mosaic=TRUE,$R=220,$G=220,$B=220,$Alpha=100)
- {
- /* Draw mosaic */
- if ( $Mosaic )
- {
- $LayerWidth = $this->GArea_X2-$this->GArea_X1;
- $LayerHeight = $this->GArea_Y2-$this->GArea_Y1;
-
- $this->Layers[0] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White =$this->AllocateColor($this->Layers[0],255,255,255);
- imagefilledrectangle($this->Layers[0],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[0],$C_White);
-
- $C_Rectangle =$this->AllocateColor($this->Layers[0],250,250,250);
-
- $YPos = $LayerHeight; //$this->GArea_Y2-1;
- $LastY = $YPos;
- for($i=0;$i<=$this->DivisionCount;$i++)
- {
- $LastY = $YPos;
- $YPos = $YPos - $this->DivisionHeight;
-
- if ( $YPos <= 0 ) { $YPos = 1; }
-
- if ( $i % 2 == 0 )
- {
- imagefilledrectangle($this->Layers[0],1,$YPos,$LayerWidth-1,$LastY,$C_Rectangle);
- }
- }
- imagecopymerge($this->Picture,$this->Layers[0],$this->GArea_X1,$this->GArea_Y1,0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[0]);
- }
-
- /* Horizontal lines */
- $YPos = $this->GArea_Y2 - $this->DivisionHeight;
- for($i=1;$i<=$this->DivisionCount;$i++)
- {
- if ( $YPos > $this->GArea_Y1 && $YPos < $this->GArea_Y2 )
- $this->drawDottedLine($this->GArea_X1,$YPos,$this->GArea_X2,$YPos,$LineWidth,$R,$G,$B);
-
- $YPos = $YPos - $this->DivisionHeight;
- }
-
- /* Vertical lines */
- if ( $this->GAreaXOffset == 0 )
- { $XPos = $this->GArea_X1 + $this->DivisionWidth + $this->GAreaXOffset; $ColCount = $this->DataCount-2; }
- else
- { $XPos = $this->GArea_X1 + $this->GAreaXOffset; $ColCount = floor( ($this->GArea_X2 - $this->GArea_X1) / $this->DivisionWidth ); }
-
- for($i=1;$i<=$ColCount;$i++)
- {
- if ( $XPos > $this->GArea_X1 && $XPos < $this->GArea_X2 )
- $this->drawDottedLine(floor($XPos),$this->GArea_Y1,floor($XPos),$this->GArea_Y2,$LineWidth,$R,$G,$B);
- $XPos = $XPos + $this->DivisionWidth;
- }
- }
-
- /* retrieve the legends size */
- function getLegendBoxSize($DataDescription)
- {
- if ( !isset($DataDescription["Description"]) )
- return(-1);
-
- /* <-10->[8]<-4->Text<-10-> */
- $MaxWidth = 0; $MaxHeight = 8;
- foreach($DataDescription["Description"] as $Key => $Value)
- {
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = $Position[1]-$Position[7];
- if ( $TextWidth > $MaxWidth) { $MaxWidth = $TextWidth; }
- $MaxHeight = $MaxHeight + $TextHeight + 4;
- }
- $MaxHeight = $MaxHeight - 3;
- $MaxWidth = $MaxWidth + 32;
-
- return(array($MaxWidth,$MaxHeight));
- }
-
- function getPieLegendBoxSize($Data)
- {
- $MaxWidth = 0; $MaxHeight = 8;
- foreach($Data as $Value)
- {
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value['Keys']);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = $Position[1]-$Position[7];
- if ( $TextWidth > $MaxWidth) { $MaxWidth = $TextWidth; }
- $MaxHeight = $MaxHeight + $TextHeight + 4;
- }
- $MaxHeight = $MaxHeight - 3;
- $MaxWidth = $MaxWidth + 32;
-
- return(array($MaxWidth,$MaxHeight));
- }
-
- /* Draw the data legends */
- function drawLegend($XPos,$YPos,$DataDescription,$R,$G,$B,$Rs=-1,$Gs=-1,$Bs=-1,$Rt=0,$Gt=0,$Bt=0,$Border=TRUE)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawLegend",$DataDescription);
-
- if ( !isset($DataDescription["Description"]) )
- return(-1);
-
- $C_TextColor =$this->AllocateColor($this->Picture,$Rt,$Gt,$Bt);
-
- /* <-10->[8]<-4->Text<-10-> */
- $MaxWidth = 0; $MaxHeight = 8;
- foreach($DataDescription["Description"] as $Key => $Value)
- {
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = $Position[1]-$Position[7];
- if ( $TextWidth > $MaxWidth) { $MaxWidth = $TextWidth; }
- $MaxHeight = $MaxHeight + $TextHeight + 4;
- }
- $MaxHeight = $MaxHeight - 5;
- $MaxWidth = $MaxWidth + 32;
-
- if ( $Rs == -1 || $Gs == -1 || $Bs == -1 )
- { $Rs = $R-30; $Gs = $G-30; $Bs = $B-30; }
-
- if ( $Border )
- {
- $this->drawFilledRoundedRectangle($XPos+1,$YPos+1,$XPos+$MaxWidth+1,$YPos+$MaxHeight+1,5,$Rs,$Gs,$Bs);
- $this->drawFilledRoundedRectangle($XPos,$YPos,$XPos+$MaxWidth,$YPos+$MaxHeight,5,$R,$G,$B);
- }
-
- $YOffset = 4 + $this->FontSize; $ID = 0;
- foreach($DataDescription["Description"] as $Key => $Value)
- {
- $this->drawFilledRoundedRectangle($XPos+10,$YPos+$YOffset-4,$XPos+14,$YPos+$YOffset-4,2,$this->Palette[$ID]["R"],$this->Palette[$ID]["G"],$this->Palette[$ID]["B"]);
- imagettftext($this->Picture,$this->FontSize,0,$XPos+22,$YPos+$YOffset,$C_TextColor,$this->FontName,$Value);
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextHeight = $Position[1]-$Position[7];
-
- $YOffset = $YOffset + $TextHeight + 4;
- $ID++;
- }
- }
-
- /* Draw the data legends */
- function drawPieLegend($XPos,$YPos,$Data,$DataDescription,$R,$G,$B)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawPieLegend",$DataDescription,FALSE);
- $this->validateData("drawPieLegend",$Data);
-
- if ( !isset($DataDescription["Position"]) )
- return(-1);
-
- $C_TextColor =$this->AllocateColor($this->Picture,0,0,0);
-
- /* <-10->[8]<-4->Text<-10-> */
- $MaxWidth = 0; $MaxHeight = 8;
- foreach($Data as $Key => $Value)
- {
- $Value2 = $Value[$DataDescription["Position"]];
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value2);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = $Position[1]-$Position[7];
- if ( $TextWidth > $MaxWidth) { $MaxWidth = $TextWidth; }
-
- $MaxHeight = $MaxHeight + $TextHeight + 4;
- }
- $MaxHeight = $MaxHeight - 3;
- $MaxWidth = $MaxWidth + 32;
-
- $this->drawFilledRoundedRectangle($XPos+1,$YPos+1,$XPos+$MaxWidth+1,$YPos+$MaxHeight+1,5,$R-30,$G-30,$B-30);
- $this->drawFilledRoundedRectangle($XPos,$YPos,$XPos+$MaxWidth,$YPos+$MaxHeight,5,$R,$G,$B);
-
- $YOffset = 4 + $this->FontSize; $ID = 0;
- foreach($Data as $Key => $Value)
- {
- $Value2 = $Value[$DataDescription["Position"]];
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value2);
- $TextHeight = $Position[1]-$Position[7];
- $this->drawFilledRectangle($XPos+10,$YPos+$YOffset-6,$XPos+14,$YPos+$YOffset-2,$this->Palette[$ID]["R"],$this->Palette[$ID]["G"],$this->Palette[$ID]["B"]);
-
- imagettftext($this->Picture,$this->FontSize,0,$XPos+22,$YPos+$YOffset,$C_TextColor,$this->FontName,$Value2);
- $YOffset = $YOffset + $TextHeight + 4;
- $ID++;
- }
- }
-
- /* Draw the graph title */
- function drawTitle($XPos,$YPos,$Value,$R,$G,$B,$XPos2=-1,$YPos2=-1,$Shadow=FALSE)
- {
- $C_TextColor = $this->AllocateColor($this->Picture,$R,$G,$B);
-
- if ( $XPos2 != -1 )
- {
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextWidth = $Position[2]-$Position[0];
- $XPos = floor(( $XPos2 - $XPos - $TextWidth ) / 2 ) + $XPos;
- }
-
- if ( $YPos2 != -1 )
- {
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Value);
- $TextHeight = $Position[5]-$Position[3];
- $YPos = floor(( $YPos2 - $YPos - $TextHeight ) / 2 ) + $YPos;
- }
-
- if ( $Shadow )
- {
- $C_ShadowColor = $this->AllocateColor($this->Picture,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor);
- imagettftext($this->Picture,$this->FontSize,0,$XPos+$this->ShadowXDistance,$YPos+$this->ShadowYDistance,$C_ShadowColor,$this->FontName,$Value);
- }
-
- imagettftext($this->Picture,$this->FontSize,0,$XPos,$YPos,$C_TextColor,$this->FontName,$Value);
- }
-
- /* Draw a text box with text align & alpha properties */
- function drawTextBox($X1,$Y1,$X2,$Y2,$Text,$Angle=0,$R=255,$G=255,$B=255,$Align=ALIGN_LEFT,$Shadow=TRUE,$BgR=-1,$BgG=-1,$BgB=-1,$Alpha=100)
- {
- $Position = imageftbbox($this->FontSize,$Angle,$this->FontName,$Text);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = $Position[5]-$Position[3];
- $AreaWidth = $X2 - $X1;
- $AreaHeight = $Y2 - $Y1;
-
- if ( $BgR != -1 && $BgG != -1 && $BgB != -1 )
- $this->drawFilledRectangle($X1,$Y1,$X2,$Y2,$BgR,$BgG,$BgB,FALSE,$Alpha);
-
- if ( $Align == ALIGN_TOP_LEFT ) { $X = $X1+1; $Y = $Y1+$this->FontSize+1; }
- if ( $Align == ALIGN_TOP_CENTER ) { $X = $X1+($AreaWidth/2)-($TextWidth/2); $Y = $Y1+$this->FontSize+1; }
- if ( $Align == ALIGN_TOP_RIGHT ) { $X = $X2-$TextWidth-1; $Y = $Y1+$this->FontSize+1; }
- if ( $Align == ALIGN_LEFT ) { $X = $X1+1; $Y = $Y1+($AreaHeight/2)-($TextHeight/2); }
- if ( $Align == ALIGN_CENTER ) { $X = $X1+($AreaWidth/2)-($TextWidth/2); $Y = $Y1+($AreaHeight/2)-($TextHeight/2); }
- if ( $Align == ALIGN_RIGHT ) { $X = $X2-$TextWidth-1; $Y = $Y1+($AreaHeight/2)-($TextHeight/2); }
- if ( $Align == ALIGN_BOTTOM_LEFT ) { $X = $X1+1; $Y = $Y2-1; }
- if ( $Align == ALIGN_BOTTOM_CENTER ) { $X = $X1+($AreaWidth/2)-($TextWidth/2); $Y = $Y2-1; }
- if ( $Align == ALIGN_BOTTOM_RIGHT ) { $X = $X2-$TextWidth-1; $Y = $Y2-1; }
-
- $C_TextColor =$this->AllocateColor($this->Picture,$R,$G,$B);
- $C_ShadowColor =$this->AllocateColor($this->Picture,0,0,0);
- if ( $Shadow )
- imagettftext($this->Picture,$this->FontSize,$Angle,$X+1,$Y+1,$C_ShadowColor,$this->FontName,$Text);
-
- imagettftext($this->Picture,$this->FontSize,$Angle,$X,$Y,$C_TextColor,$this->FontName,$Text);
- }
-
- /* Compute and draw the scale */
- function drawTreshold($Value,$R,$G,$B,$ShowLabel=FALSE,$ShowOnRight=FALSE,$TickWidth=4,$FreeText=NULL)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_TextColor =$this->AllocateColor($this->Picture,$R,$G,$B);
- $Y = $this->GArea_Y2 - ($Value - $this->VMin) * $this->DivisionRatio;
-
- if ( $Y <= $this->GArea_Y1 || $Y >= $this->GArea_Y2 )
- return(-1);
-
- if ( $TickWidth == 0 )
- $this->drawLine($this->GArea_X1,$Y,$this->GArea_X2,$Y,$R,$G,$B);
- else
- $this->drawDottedLine($this->GArea_X1,$Y,$this->GArea_X2,$Y,$TickWidth,$R,$G,$B);
-
- if ( $ShowLabel )
- {
- if ( $FreeText == NULL )
- { $Label = $Value; } else { $Label = $FreeText; }
-
- if ( $ShowOnRight )
- imagettftext($this->Picture,$this->FontSize,0,$this->GArea_X2+2,$Y+($this->FontSize/2),$C_TextColor,$this->FontName,$Label);
- else
- imagettftext($this->Picture,$this->FontSize,0,$this->GArea_X1+2,$Y-($this->FontSize/2),$C_TextColor,$this->FontName,$Label);
- }
- }
-
- /* This function put a label on a specific point */
- function setLabel($Data,$DataDescription,$SerieName,$ValueName,$Caption,$R=210,$G=210,$B=210)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("setLabel",$DataDescription);
- $this->validateData("setLabel",$Data);
- $ShadowFactor = 100;
- $C_Label =$this->AllocateColor($this->Picture,$R,$G,$B);
- $C_Shadow =$this->AllocateColor($this->Picture,$R-$ShadowFactor,$G-$ShadowFactor,$B-$ShadowFactor);
- $C_TextColor =$this->AllocateColor($this->Picture,0,0,0);
-
- $Cp = 0; $Found = FALSE;
- foreach ( $Data as $Key => $Value )
- {
- if ( $Data[$Key][$DataDescription["Position"]] == $ValueName )
- { $NumericalValue = $Data[$Key][$SerieName]; $Found = TRUE; }
- if ( !$Found )
- $Cp++;
- }
-
- $XPos = $this->GArea_X1 + $this->GAreaXOffset + ( $this->DivisionWidth * $Cp ) + 2;
- $YPos = $this->GArea_Y2 - ($NumericalValue - $this->VMin) * $this->DivisionRatio;
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Caption);
- $TextHeight = $Position[3] - $Position[5];
- $TextWidth = $Position[2]-$Position[0] + 2;
- $TextOffset = floor($TextHeight/2);
-
- // Shadow
- $Poly = array($XPos+1,$YPos+1,$XPos + 9,$YPos - $TextOffset,$XPos + 8,$YPos + $TextOffset + 2);
- imagefilledpolygon($this->Picture,$Poly,3,$C_Shadow);
- $this->drawLine($XPos,$YPos+1,$XPos + 9,$YPos - $TextOffset - .2,$R-$ShadowFactor,$G-$ShadowFactor,$B-$ShadowFactor);
- $this->drawLine($XPos,$YPos+1,$XPos + 9,$YPos + $TextOffset + 2.2,$R-$ShadowFactor,$G-$ShadowFactor,$B-$ShadowFactor);
- $this->drawFilledRectangle($XPos + 9,$YPos - $TextOffset-.2,$XPos + 13 + $TextWidth,$YPos + $TextOffset + 2.2,$R-$ShadowFactor,$G-$ShadowFactor,$B-$ShadowFactor);
-
- // Label background
- $Poly = array($XPos,$YPos,$XPos + 8,$YPos - $TextOffset - 1,$XPos + 8,$YPos + $TextOffset + 1);
- imagefilledpolygon($this->Picture,$Poly,3,$C_Label);
- $this->drawLine($XPos-1,$YPos,$XPos + 8,$YPos - $TextOffset - 1.2,$R,$G,$B);
- $this->drawLine($XPos-1,$YPos,$XPos + 8,$YPos + $TextOffset + 1.2,$R,$G,$B);
- $this->drawFilledRectangle($XPos + 8,$YPos - $TextOffset - 1.2,$XPos + 12 + $TextWidth,$YPos + $TextOffset + 1.2,$R,$G,$B);
-
- imagettftext($this->Picture,$this->FontSize,0,$XPos + 10,$YPos + $TextOffset,$C_TextColor,$this->FontName,$Caption);
- }
-
- /* This function draw a plot graph */
- function drawPlotGraph($Data,$DataDescription,$BigRadius=5,$SmallRadius=2,$R2=-1,$G2=-1,$B2=-1,$Shadow=FALSE)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawPlotGraph",$DataDescription);
- $this->validateData("drawPlotGraph",$Data);
-
- $GraphID = 0;
- $Ro = $R2; $Go = $G2; $Bo = $B2;
-
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $R = $this->Palette[$ColorID]["R"];
- $G = $this->Palette[$ColorID]["G"];
- $B = $this->Palette[$ColorID]["B"];
- $R2 = $Ro; $G2 = $Go; $B2 = $Bo;
-
- if ( isset($DataDescription["Symbol"][$ColName]) )
- {
- $Is_Alpha = ((ord ( file_get_contents ($DataDescription["Symbol"][$ColName], false, null, 25, 1)) & 6) & 4) == 4;
-
- $Infos = getimagesize($DataDescription["Symbol"][$ColName]);
- $ImageWidth = $Infos[0];
- $ImageHeight = $Infos[1];
- $Symbol = imagecreatefromgif($DataDescription["Symbol"][$ColName]);
- }
-
- $XPos = $this->GArea_X1 + $this->GAreaXOffset;
- $Hsize = round($BigRadius/2);
- $R3 = -1; $G3 = -1; $B3 = -1;
- foreach ( $Data as $Key => $Values )
- {
- $Value = $Data[$Key][$ColName];
- $YPos = $this->GArea_Y2 - (($Value-$this->VMin) * $this->DivisionRatio);
-
- /* Save point into the image map if option activated */
- if ( $this->BuildMap )
- $this->addToImageMap($XPos-$Hsize,$YPos-$Hsize,$XPos+1+$Hsize,$YPos+$Hsize+1,$DataDescription["Description"][$ColName],$Data[$Key][$ColName].$DataDescription["Unit"]["Y"],"Plot");
-
- if ( is_numeric($Value) )
- {
- if ( !isset($DataDescription["Symbol"][$ColName]) )
- {
-
- if ( $Shadow )
- {
- if ( $R3 !=-1 && $G3 !=-1 && $B3 !=-1 )
- $this->drawFilledCircle($XPos+2,$YPos+2,$BigRadius,$R3,$G3,$B3);
- else
- {
- $R3 = $this->Palette[$ColorID]["R"]-20; if ( $R3 < 0 ) { $R3 = 0; }
- $G3 = $this->Palette[$ColorID]["G"]-20; if ( $G3 < 0 ) { $G3 = 0; }
- $B3 = $this->Palette[$ColorID]["B"]-20; if ( $B3 < 0 ) { $B3 = 0; }
- $this->drawFilledCircle($XPos+2,$YPos+2,$BigRadius,$R3,$G3,$B3);
- }
- }
-
- $this->drawFilledCircle($XPos+1,$YPos+1,$BigRadius,$R,$G,$B);
-
- if ( $SmallRadius != 0 )
- {
- if ( $R2 !=-1 && $G2 !=-1 && $B2 !=-1 )
- $this->drawFilledCircle($XPos+1,$YPos+1,$SmallRadius,$R2,$G2,$B2);
- else
- {
- $R2 = $this->Palette[$ColorID]["R"]-15; if ( $R2 < 0 ) { $R2 = 0; }
- $G2 = $this->Palette[$ColorID]["G"]-15; if ( $G2 < 0 ) { $G2 = 0; }
- $B2 = $this->Palette[$ColorID]["B"]-15; if ( $B2 < 0 ) { $B2 = 0; }
-
- $this->drawFilledCircle($XPos+1,$YPos+1,$SmallRadius,$R2,$G2,$B2);
- }
- }
- }
- else
- {
- imagecopymerge($this->Picture,$Symbol,$XPos+1-$ImageWidth/2,$YPos+1-$ImageHeight/2,0,0,$ImageWidth,$ImageHeight,100);
- }
- }
-
- $XPos = $XPos + $this->DivisionWidth;
- }
- $GraphID++;
- }
- }
-
- /* This function draw a plot graph in an X/Y space */
- function drawXYPlotGraph($Data,$DataDescription,$YSerieName,$XSerieName,$PaletteID=0,$BigRadius=5,$SmallRadius=2,$R2=-1,$G2=-1,$B2=-1,$Shadow=TRUE)
- {
- $R = $this->Palette[$PaletteID]["R"];
- $G = $this->Palette[$PaletteID]["G"];
- $B = $this->Palette[$PaletteID]["B"];
- $R3 = -1; $G3 = -1; $B3 = -1;
-
- $YLast = -1; $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$YSerieName]) && isset($Data[$Key][$XSerieName]) )
- {
- $X = $Data[$Key][$XSerieName];
- $Y = $Data[$Key][$YSerieName];
-
- $Y = $this->GArea_Y2 - (($Y-$this->VMin) * $this->DivisionRatio);
- $X = $this->GArea_X1 + (($X-$this->VXMin) * $this->XDivisionRatio);
-
-
- if ( $Shadow )
- {
- if ( $R3 !=-1 && $G3 !=-1 && $B3 !=-1 )
- $this->drawFilledCircle($X+2,$Y+2,$BigRadius,$R3,$G3,$B3);
- else
- {
- $R3 = $this->Palette[$PaletteID]["R"]-20; if ( $R < 0 ) { $R = 0; }
- $G3 = $this->Palette[$PaletteID]["G"]-20; if ( $G < 0 ) { $G = 0; }
- $B3 = $this->Palette[$PaletteID]["B"]-20; if ( $B < 0 ) { $B = 0; }
- $this->drawFilledCircle($X+2,$Y+2,$BigRadius,$R3,$G3,$B3);
- }
- }
-
- $this->drawFilledCircle($X+1,$Y+1,$BigRadius,$R,$G,$B);
-
- if ( $R2 !=-1 && $G2 !=-1 && $B2 !=-1 )
- $this->drawFilledCircle($X+1,$Y+1,$SmallRadius,$R2,$G2,$B2);
- else
- {
- $R2 = $this->Palette[$PaletteID]["R"]+20; if ( $R > 255 ) { $R = 255; }
- $G2 = $this->Palette[$PaletteID]["G"]+20; if ( $G > 255 ) { $G = 255; }
- $B2 = $this->Palette[$PaletteID]["B"]+20; if ( $B > 255 ) { $B = 255; }
- $this->drawFilledCircle($X+1,$Y+1,$SmallRadius,$R2,$G2,$B2);
- }
- }
- }
-
- }
-
- /* This function draw an area between two series */
- function drawArea($Data,$Serie1,$Serie2,$R,$G,$B,$Alpha = 50)
- {
- /* Validate the Data and DataDescription array */
- $this->validateData("drawArea",$Data);
-
- $LayerWidth = $this->GArea_X2-$this->GArea_X1;
- $LayerHeight = $this->GArea_Y2-$this->GArea_Y1;
-
- $this->Layers[0] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White =$this->AllocateColor($this->Layers[0],255,255,255);
- imagefilledrectangle($this->Layers[0],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[0],$C_White);
-
- $C_Graph =$this->AllocateColor($this->Layers[0],$R,$G,$B);
-
- $XPos = $this->GAreaXOffset;
- $LastXPos = -1;
- foreach ( $Data as $Key => $Values )
- {
- $Value1 = $Data[$Key][$Serie1];
- $Value2 = $Data[$Key][$Serie2];
- $YPos1 = $LayerHeight - (($Value1-$this->VMin) * $this->DivisionRatio);
- $YPos2 = $LayerHeight - (($Value2-$this->VMin) * $this->DivisionRatio);
-
- if ( $LastXPos != -1 )
- {
- $Points = "";
- $Points[] = $LastXPos; $Points[] = $LastYPos1;
- $Points[] = $LastXPos; $Points[] = $LastYPos2;
- $Points[] = $XPos; $Points[] = $YPos2;
- $Points[] = $XPos; $Points[] = $YPos1;
-
- imagefilledpolygon($this->Layers[0],$Points,4,$C_Graph);
- }
-
- $LastYPos1 = $YPos1;
- $LastYPos2 = $YPos2;
- $LastXPos = $XPos;
-
- $XPos = $XPos + $this->DivisionWidth;
- }
-
- imagecopymerge($this->Picture,$this->Layers[0],$this->GArea_X1,$this->GArea_Y1,0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[0]);
- }
-
-
- /* This function write the values of the specified series */
- function writeValues($Data,$DataDescription,$Series)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("writeValues",$DataDescription);
- $this->validateData("writeValues",$Data);
-
- if ( !is_array($Series) ) { $Series = array($Series); }
-
- foreach($Series as $Key => $Serie)
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $Serie ) { $ColorID = $ID; }; $ID++; }
-
- $XPos = $this->GArea_X1 + $this->GAreaXOffset;
- $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$Serie]) && is_numeric($Data[$Key][$Serie]))
- {
- $Value = $Data[$Key][$Serie];
- $YPos = $this->GArea_Y2 - (($Value-$this->VMin) * $this->DivisionRatio);
-
- $Positions = imagettfbbox($this->FontSize,0,$this->FontName,$Value);
- $Width = $Positions[2] - $Positions[6]; $XOffset = $XPos - ($Width/2);
- $Height = $Positions[3] - $Positions[7]; $YOffset = $YPos - 4;
-
- $C_TextColor =$this->AllocateColor($this->Picture,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagettftext($this->Picture,$this->FontSize,0,$XOffset,$YOffset,$C_TextColor,$this->FontName,$Value);
- }
- $XPos = $XPos + $this->DivisionWidth;
- }
-
- }
- }
-
- /* This function draw a line graph */
- function drawLineGraph($Data,$DataDescription,$SerieName="")
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawLineGraph",$DataDescription);
- $this->validateData("drawLineGraph",$Data);
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- if ( $SerieName == "" || $SerieName == $ColName )
- {
- $XPos = $this->GArea_X1 + $this->GAreaXOffset;
- $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- {
- $Value = $Data[$Key][$ColName];
- $YPos = $this->GArea_Y2 - (($Value-$this->VMin) * $this->DivisionRatio);
-
- /* Save point into the image map if option activated */
- if ( $this->BuildMap )
- $this->addToImageMap($XPos-3,$YPos-3,$XPos+3,$YPos+3,$DataDescription["Description"][$ColName],$Data[$Key][$ColName].$DataDescription["Unit"]["Y"],"Line");
-
- if (!is_numeric($Value)) { $XLast = -1; }
- if ( $XLast != -1 )
- $this->drawLine($XLast,$YLast,$XPos,$YPos,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"],TRUE);
-
- $XLast = $XPos;
- $YLast = $YPos;
- if (!is_numeric($Value)) { $XLast = -1; }
- }
- $XPos = $XPos + $this->DivisionWidth;
- }
- $GraphID++;
- }
- }
- }
-
- /* This function draw a line graph */
- function drawXYGraph($Data,$DataDescription,$YSerieName,$XSerieName,$PaletteID=0)
- {
- $YLast = -1; $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$YSerieName]) && isset($Data[$Key][$XSerieName]) )
- {
- $X = $Data[$Key][$XSerieName];
- $Y = $Data[$Key][$YSerieName];
-
- $Y = $this->GArea_Y2 - (($Y-$this->VMin) * $this->DivisionRatio);
- $X = $this->GArea_X1 + (($X-$this->VXMin) * $this->XDivisionRatio);
-
- if ($XLast != -1 && $YLast != -1)
- {
- $this->drawLine($XLast,$YLast,$X,$Y,$this->Palette[$PaletteID]["R"],$this->Palette[$PaletteID]["G"],$this->Palette[$PaletteID]["B"],TRUE);
- }
-
- $XLast = $X;
- $YLast = $Y;
- }
- }
- }
-
- /* This function draw a cubic curve */
- function drawCubicCurve($Data,$DataDescription,$Accuracy=.1,$SerieName="")
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawCubicCurve",$DataDescription);
- $this->validateData("drawCubicCurve",$Data);
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if ( $SerieName == "" || $SerieName == $ColName )
- {
- $XIn = ""; $Yin = ""; $Yt = ""; $U = "";
- $XIn[0] = 0; $YIn[0] = 0;
-
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $Index = 1;
- $XLast = -1; $Missing = "";
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]) )
- {
- $Value = $Data[$Key][$ColName];
- $XIn[$Index] = $Index;
- $YIn[$Index] = $Value;
- if ( !is_numeric($Value) ) { $Missing[$Index] = TRUE; }
- $Index++;
- }
- }
- $Index--;
-
- $Yt[0] = 0;
- $Yt[1] = 0;
- $U[1] = 0;
- for($i=2;$i<=$Index-1;$i++)
- {
- $Sig = ($XIn[$i] - $XIn[$i-1]) / ($XIn[$i+1] - $XIn[$i-1]);
- $p = $Sig * $Yt[$i-1] + 2;
- $Yt[$i] = ($Sig - 1) / $p;
- $U[$i] = ($YIn[$i+1] - $YIn[$i]) / ($XIn[$i+1] - $XIn[$i]) - ($YIn[$i] - $YIn[$i-1]) / ($XIn[$i] - $XIn[$i-1]);
- $U[$i] = (6 * $U[$i] / ($XIn[$i+1] - $XIn[$i-1]) - $Sig * $U[$i-1]) / $p;
- }
-
- $qn = 0;
- $un = 0;
- $Yt[$Index] = ($un - $qn * $U[$Index-1]) / ($qn * $Yt[$Index-1] + 1);
-
- for($k=$Index-1;$k>=1;$k--)
- $Yt[$k] = $Yt[$k] * $Yt[$k+1] + $U[$k];
-
- $XPos = $this->GArea_X1 + $this->GAreaXOffset;
- for($X=1;$X<=$Index;$X=$X+$Accuracy)
- {
- $klo = 1;
- $khi = $Index;
- $k = $khi - $klo;
- while($k > 1)
- {
- $k = $khi - $klo;
- If ( $XIn[$k] >= $X )
- $khi = $k;
- else
- $klo = $k;
- }
- $klo = $khi - 1;
-
- $h = $XIn[$khi] - $XIn[$klo];
- $a = ($XIn[$khi] - $X) / $h;
- $b = ($X - $XIn[$klo]) / $h;
- $Value = $a * $YIn[$klo] + $b * $YIn[$khi] + (($a*$a*$a - $a) * $Yt[$klo] + ($b*$b*$b - $b) * $Yt[$khi]) * ($h*$h) / 6;
-
- $YPos = $this->GArea_Y2 - (($Value-$this->VMin) * $this->DivisionRatio);
-
- if ( $XLast != -1 && !isset($Missing[floor($X)]) && !isset($Missing[floor($X+1)]) )
- $this->drawLine($XLast,$YLast,$XPos,$YPos,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"],TRUE);
-
- $XLast = $XPos;
- $YLast = $YPos;
- $XPos = $XPos + $this->DivisionWidth * $Accuracy;
- }
-
- // Add potentialy missing values
- $XPos = $XPos - $this->DivisionWidth * $Accuracy;
- if ( $XPos < ($this->GArea_X2 - $this->GAreaXOffset) )
- {
- $YPos = $this->GArea_Y2 - (($YIn[$Index]-$this->VMin) * $this->DivisionRatio);
- $this->drawLine($XLast,$YLast,$this->GArea_X2-$this->GAreaXOffset,$YPos,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"],TRUE);
- }
-
- $GraphID++;
- }
- }
- }
-
- /* This function draw a filled cubic curve */
- function drawFilledCubicCurve($Data,$DataDescription,$Accuracy=.1,$Alpha=100,$AroundZero=FALSE)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawFilledCubicCurve",$DataDescription);
- $this->validateData("drawFilledCubicCurve",$Data);
-
- $LayerWidth = $this->GArea_X2-$this->GArea_X1;
- $LayerHeight = $this->GArea_Y2-$this->GArea_Y1;
- $YZero = $LayerHeight - ((0-$this->VMin) * $this->DivisionRatio);
- if ( $YZero > $LayerHeight ) { $YZero = $LayerHeight; }
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $XIn = ""; $Yin = ""; $Yt = ""; $U = "";
- $XIn[0] = 0; $YIn[0] = 0;
-
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $Index = 1;
- $XLast = -1; $Missing = "";
- foreach ( $Data as $Key => $Values )
- {
- $Value = $Data[$Key][$ColName];
- $XIn[$Index] = $Index;
- $YIn[$Index] = $Value;
- if ( !is_numeric($Value) ) { $Missing[$Index] = TRUE; }
- $Index++;
- }
- $Index--;
-
- $Yt[0] = 0;
- $Yt[1] = 0;
- $U[1] = 0;
- for($i=2;$i<=$Index-1;$i++)
- {
- $Sig = ($XIn[$i] - $XIn[$i-1]) / ($XIn[$i+1] - $XIn[$i-1]);
- $p = $Sig * $Yt[$i-1] + 2;
- $Yt[$i] = ($Sig - 1) / $p;
- $U[$i] = ($YIn[$i+1] - $YIn[$i]) / ($XIn[$i+1] - $XIn[$i]) - ($YIn[$i] - $YIn[$i-1]) / ($XIn[$i] - $XIn[$i-1]);
- $U[$i] = (6 * $U[$i] / ($XIn[$i+1] - $XIn[$i-1]) - $Sig * $U[$i-1]) / $p;
- }
-
- $qn = 0;
- $un = 0;
- $Yt[$Index] = ($un - $qn * $U[$Index-1]) / ($qn * $Yt[$Index-1] + 1);
-
- for($k=$Index-1;$k>=1;$k--)
- $Yt[$k] = $Yt[$k] * $Yt[$k+1] + $U[$k];
-
- $Points = "";
- $Points[] = $this->GAreaXOffset;
- $Points[] = $LayerHeight;
-
- $this->Layers[0] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White =$this->AllocateColor($this->Layers[0],255,255,255);
- imagefilledrectangle($this->Layers[0],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[0],$C_White);
-
- $YLast = NULL;
- $XPos = $this->GAreaXOffset; $PointsCount = 2;
- for($X=1;$X<=$Index;$X=$X+$Accuracy)
- {
- $klo = 1;
- $khi = $Index;
- $k = $khi - $klo;
- while($k > 1)
- {
- $k = $khi - $klo;
- If ( $XIn[$k] >= $X )
- $khi = $k;
- else
- $klo = $k;
- }
- $klo = $khi - 1;
-
- $h = $XIn[$khi] - $XIn[$klo];
- $a = ($XIn[$khi] - $X) / $h;
- $b = ($X - $XIn[$klo]) / $h;
- $Value = $a * $YIn[$klo] + $b * $YIn[$khi] + (($a*$a*$a - $a) * $Yt[$klo] + ($b*$b*$b - $b) * $Yt[$khi]) * ($h*$h) / 6;
-
- $YPos = $LayerHeight - (($Value-$this->VMin) * $this->DivisionRatio);
-
- if ( $YLast != NULL && $AroundZero && !isset($Missing[floor($X)]) && !isset($Missing[floor($X+1)]))
- {
- $aPoints = "";
- $aPoints[] = $XLast;
- $aPoints[] = $YLast;
- $aPoints[] = $XPos;
- $aPoints[] = $YPos;
- $aPoints[] = $XPos;
- $aPoints[] = $YZero;
- $aPoints[] = $XLast;
- $aPoints[] = $YZero;
-
- $C_Graph =$this->AllocateColor($this->Layers[0],$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagefilledpolygon($this->Layers[0],$aPoints,4,$C_Graph);
- }
-
- if ( !isset($Missing[floor($X)]) || $YLast == NULL )
- {
- $PointsCount++;
- $Points[] = $XPos;
- $Points[] = $YPos;
- }
- else
- {
- $PointsCount++; $Points[] = $XLast; $Points[] = $LayerHeight;
- }
-
- $YLast = $YPos; $XLast = $XPos;
- $XPos = $XPos + $this->DivisionWidth * $Accuracy;
- }
-
- // Add potentialy missing values
- $XPos = $XPos - $this->DivisionWidth * $Accuracy;
- if ( $XPos < ($LayerWidth-$this->GAreaXOffset) )
- {
- $YPos = $LayerHeight - (($YIn[$Index]-$this->VMin) * $this->DivisionRatio);
-
- if ( $YLast != NULL && $AroundZero )
- {
- $aPoints = "";
- $aPoints[] = $XLast;
- $aPoints[] = $YLast;
- $aPoints[] = $LayerWidth-$this->GAreaXOffset;
- $aPoints[] = $YPos;
- $aPoints[] = $LayerWidth-$this->GAreaXOffset;
- $aPoints[] = $YZero;
- $aPoints[] = $XLast;
- $aPoints[] = $YZero;
-
- $C_Graph =$this->AllocateColor($this->Layers[0],$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagefilledpolygon($this->Layers[0],$aPoints,4,$C_Graph);
- }
-
- if ( $YIn[$klo] != "" && $YIn[$khi] != "" || $YLast == NULL )
- {
- $PointsCount++;
- $Points[] = $LayerWidth-$this->GAreaXOffset;
- $Points[] = $YPos;
- }
- }
-
- $Points[] = $LayerWidth-$this->GAreaXOffset;
- $Points[] = $LayerHeight;
-
- if ( !$AroundZero )
- {
- $C_Graph =$this->AllocateColor($this->Layers[0],$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagefilledpolygon($this->Layers[0],$Points,$PointsCount,$C_Graph);
- }
-
- imagecopymerge($this->Picture,$this->Layers[0],$this->GArea_X1,$this->GArea_Y1,0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[0]);
-
- $this->drawCubicCurve($Data,$DataDescription,$Accuracy,$ColName);
-
- $GraphID++;
- }
- }
-
- /* This function draw a filled line graph */
- function drawFilledLineGraph($Data,$DataDescription,$Alpha=100,$AroundZero=FALSE)
- {
- $Empty = -2147483647;
-
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawFilledLineGraph",$DataDescription);
- $this->validateData("drawFilledLineGraph",$Data);
-
- $LayerWidth = $this->GArea_X2-$this->GArea_X1;
- $LayerHeight = $this->GArea_Y2-$this->GArea_Y1;
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $aPoints = "";
- $aPoints[] = $this->GAreaXOffset;
- $aPoints[] = $LayerHeight;
-
- $this->Layers[0] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White = $this->AllocateColor($this->Layers[0],255,255,255);
- imagefilledrectangle($this->Layers[0],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[0],$C_White);
-
- $XPos = $this->GAreaXOffset;
- $XLast = -1; $PointsCount = 2;
- $YZero = $LayerHeight - ((0-$this->VMin) * $this->DivisionRatio);
- if ( $YZero > $LayerHeight ) { $YZero = $LayerHeight; }
-
- $YLast = $Empty;
- foreach ( $Data as $Key => $Values )
- {
- $Value = $Data[$Key][$ColName];
- $YPos = $LayerHeight - (($Value-$this->VMin) * $this->DivisionRatio);
-
- /* Save point into the image map if option activated */
- if ( $this->BuildMap )
- $this->addToImageMap($XPos-3,$YPos-3,$XPos+3,$YPos+3,$DataDescription["Description"][$ColName],$Data[$Key][$ColName].$DataDescription["Unit"]["Y"],"FLine");
-
- if ( !is_numeric($Value) )
- {
- $PointsCount++;
- $aPoints[] = $XLast;
- $aPoints[] = $LayerHeight;
-
- $YLast = $Empty;
- }
- else
- {
- $PointsCount++;
- if ( $YLast <> $Empty )
- { $aPoints[] = $XPos; $aPoints[] = $YPos; }
- else
- { $PointsCount++; $aPoints[] = $XPos; $aPoints[] = $LayerHeight; $aPoints[] = $XPos; $aPoints[] = $YPos; }
-
- if ($YLast <> $Empty && $AroundZero)
- {
- $Points = "";
- $Points[] = $XLast; $Points[] = $YLast;
- $Points[] = $XPos;
- $Points[] = $YPos;
- $Points[] = $XPos;
- $Points[] = $YZero;
- $Points[] = $XLast;
- $Points[] = $YZero;
-
- $C_Graph = $this->AllocateColor($this->Layers[0],$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagefilledpolygon($this->Layers[0],$Points,4,$C_Graph);
- }
- $YLast = $YPos;
- }
-
- $XLast = $XPos;
- $XPos = $XPos + $this->DivisionWidth;
- }
- $aPoints[] = $LayerWidth - $this->GAreaXOffset;
- $aPoints[] = $LayerHeight;
-
- if ( $AroundZero == FALSE )
- {
- $C_Graph = $this->AllocateColor($this->Layers[0],$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagefilledpolygon($this->Layers[0],$aPoints,$PointsCount,$C_Graph);
- }
-
- imagecopymerge($this->Picture,$this->Layers[0],$this->GArea_X1,$this->GArea_Y1,0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[0]);
- $GraphID++;
- $this->drawLineGraph($Data,$DataDescription,$ColName);
- }
- }
-
- /* This function draw a bar graph */
- function drawOverlayBarGraph($Data,$DataDescription,$Alpha=50)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawOverlayBarGraph",$DataDescription);
- $this->validateData("drawOverlayBarGraph",$Data);
-
- $LayerWidth = $this->GArea_X2-$this->GArea_X1;
- $LayerHeight = $this->GArea_Y2-$this->GArea_Y1;
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $this->Layers[$GraphID] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White = $this->AllocateColor($this->Layers[$GraphID],255,255,255);
- $C_Graph = $this->AllocateColor($this->Layers[$GraphID],$this->Palette[$GraphID]["R"],$this->Palette[$GraphID]["G"],$this->Palette[$GraphID]["B"]);
- imagefilledrectangle($this->Layers[$GraphID],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[$GraphID],$C_White);
-
- $XWidth = $this->DivisionWidth / 4;
- $XPos = $this->GAreaXOffset;
- $YZero = $LayerHeight - ((0-$this->VMin) * $this->DivisionRatio);
- $XLast = -1; $PointsCount = 2;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]) )
- {
- $Value = $Data[$Key][$ColName];
- if ( is_numeric($Value) )
- {
- $YPos = $LayerHeight - (($Value-$this->VMin) * $this->DivisionRatio);
-
- imagefilledrectangle($this->Layers[$GraphID],$XPos-$XWidth,$YPos,$XPos+$XWidth,$YZero,$C_Graph);
-
- $X1 = floor($XPos - $XWidth + $this->GArea_X1); $Y1 = floor($YPos+$this->GArea_Y1) + .2;
- $X2 = floor($XPos + $XWidth + $this->GArea_X1); $Y2 = $this->GArea_Y2 - ((0-$this->VMin) * $this->DivisionRatio);
- if ( $X1 <= $this->GArea_X1 ) { $X1 = $this->GArea_X1 + 1; }
- if ( $X2 >= $this->GArea_X2 ) { $X2 = $this->GArea_X2 - 1; }
-
- /* Save point into the image map if option activated */
- if ( $this->BuildMap )
- $this->addToImageMap($X1,min($Y1,$Y2),$X2,max($Y1,$Y2),$DataDescription["Description"][$ColName],$Data[$Key][$ColName].$DataDescription["Unit"]["Y"],"oBar");
-
- $this->drawLine($X1,$Y1,$X2,$Y1,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"],TRUE);
- }
- }
- $XPos = $XPos + $this->DivisionWidth;
- }
-
- $GraphID++;
- }
-
- for($i=0;$i<=($GraphID-1);$i++)
- {
- imagecopymerge($this->Picture,$this->Layers[$i],$this->GArea_X1,$this->GArea_Y1,0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[$i]);
- }
- }
-
- /* This function draw a bar graph */
- function drawBarGraph($Data,$DataDescription,$Shadow=FALSE,$Alpha=100)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawBarGraph",$DataDescription);
- $this->validateData("drawBarGraph",$Data);
-
- $GraphID = 0;
- $Series = count($DataDescription["Values"]);
- $SeriesWidth = $this->DivisionWidth / ($Series+1);
- $SerieXOffset = $this->DivisionWidth / 2 - $SeriesWidth / 2;
-
- $YZero = $this->GArea_Y2 - ((0-$this->VMin) * $this->DivisionRatio);
- if ( $YZero > $this->GArea_Y2 ) { $YZero = $this->GArea_Y2; }
-
- $SerieID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $XPos = $this->GArea_X1 + $this->GAreaXOffset - $SerieXOffset + $SeriesWidth * $SerieID;
- $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- {
- if ( is_numeric($Data[$Key][$ColName]) )
- {
- $Value = $Data[$Key][$ColName];
- $YPos = $this->GArea_Y2 - (($Value-$this->VMin) * $this->DivisionRatio);
-
- /* Save point into the image map if option activated */
- if ( $this->BuildMap )
- {
- $this->addToImageMap($XPos+1,min($YZero,$YPos),$XPos+$SeriesWidth-1,max($YZero,$YPos),$DataDescription["Description"][$ColName],$Data[$Key][$ColName].$DataDescription["Unit"]["Y"],"Bar");
- }
-
- if ( $Shadow && $Alpha == 100 )
- $this->drawRectangle($XPos+1,$YZero,$XPos+$SeriesWidth-1,$YPos,25,25,25,TRUE,$Alpha);
-
- $this->drawFilledRectangle($XPos+1,$YZero,$XPos+$SeriesWidth-1,$YPos,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"],TRUE,$Alpha);
- }
- }
- $XPos = $XPos + $this->DivisionWidth;
- }
- $SerieID++;
- }
- }
-
- /* This function draw a stacked bar graph */
- function drawStackedBarGraph($Data,$DataDescription,$Alpha=50,$Contiguous=FALSE)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawBarGraph",$DataDescription);
- $this->validateData("drawBarGraph",$Data);
-
- $GraphID = 0;
- $Series = count($DataDescription["Values"]);
- if ( $Contiguous )
- $SeriesWidth = $this->DivisionWidth;
- else
- $SeriesWidth = $this->DivisionWidth * .8;
-
- $YZero = $this->GArea_Y2 - ((0-$this->VMin) * $this->DivisionRatio);
- if ( $YZero > $this->GArea_Y2 ) { $YZero = $this->GArea_Y2; }
-
- $SerieID = 0; $LastValue = "";
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $XPos = $this->GArea_X1 + $this->GAreaXOffset - $SeriesWidth / 2;
- $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- {
- if ( is_numeric($Data[$Key][$ColName]) )
- {
- $Value = $Data[$Key][$ColName];
-
- if ( isset($LastValue[$Key]) )
- {
- $YPos = $this->GArea_Y2 - ((($Value+$LastValue[$Key])-$this->VMin) * $this->DivisionRatio);
- $YBottom = $this->GArea_Y2 - (($LastValue[$Key]-$this->VMin) * $this->DivisionRatio);
- $LastValue[$Key] += $Value;
- }
- else
- {
- $YPos = $this->GArea_Y2 - (($Value-$this->VMin) * $this->DivisionRatio);
- $YBottom = $YZero;
- $LastValue[$Key] = $Value;
- }
-
- /* Save point into the image map if option activated */
- if ( $this->BuildMap )
- $this->addToImageMap($XPos+1,min($YBottom,$YPos),$XPos+$SeriesWidth-1,max($YBottom,$YPos),$DataDescription["Description"][$ColName],$Data[$Key][$ColName].$DataDescription["Unit"]["Y"],"sBar");
-
- $this->drawFilledRectangle($XPos+1,$YBottom,$XPos+$SeriesWidth-1,$YPos,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"],TRUE,$Alpha);
- }
- }
- $XPos = $XPos + $this->DivisionWidth;
- }
- $SerieID++;
- }
- }
-
- /* This function draw a limits bar graphs */
- function drawLimitsGraph($Data,$DataDescription,$R=0,$G=0,$B=0)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawLimitsGraph",$DataDescription);
- $this->validateData("drawLimitsGraph",$Data);
-
- $XWidth = $this->DivisionWidth / 4;
- $XPos = $this->GArea_X1 + $this->GAreaXOffset;
-
- foreach ( $Data as $Key => $Values )
- {
- $Min = $Data[$Key][$DataDescription["Values"][0]];
- $Max = $Data[$Key][$DataDescription["Values"][0]];
- $GraphID = 0; $MaxID = 0; $MinID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if ( isset($Data[$Key][$ColName]) )
- {
- if ( $Data[$Key][$ColName] > $Max && is_numeric($Data[$Key][$ColName]))
- { $Max = $Data[$Key][$ColName]; $MaxID = $GraphID; }
- }
- if ( isset($Data[$Key][$ColName]) && is_numeric($Data[$Key][$ColName]))
- {
- if ( $Data[$Key][$ColName] < $Min )
- { $Min = $Data[$Key][$ColName]; $MinID = $GraphID; }
- $GraphID++;
- }
- }
-
- $YPos = $this->GArea_Y2 - (($Max-$this->VMin) * $this->DivisionRatio);
- $X1 = floor($XPos - $XWidth); $Y1 = floor($YPos) - .2;
- $X2 = floor($XPos + $XWidth);
- if ( $X1 <= $this->GArea_X1 ) { $X1 = $this->GArea_X1 + 1; }
- if ( $X2 >= $this->GArea_X2 ) { $X2 = $this->GArea_X2 - 1; }
-
- $YPos = $this->GArea_Y2 - (($Min-$this->VMin) * $this->DivisionRatio);
- $Y2 = floor($YPos) + .2;
-
- $this->drawLine(floor($XPos)-.2,$Y1+1,floor($XPos)-.2,$Y2-1,$R,$G,$B,TRUE);
- $this->drawLine(floor($XPos)+.2,$Y1+1,floor($XPos)+.2,$Y2-1,$R,$G,$B,TRUE);
- $this->drawLine($X1,$Y1,$X2,$Y1,$this->Palette[$MaxID]["R"],$this->Palette[$MaxID]["G"],$this->Palette[$MaxID]["B"],FALSE);
- $this->drawLine($X1,$Y2,$X2,$Y2,$this->Palette[$MinID]["R"],$this->Palette[$MinID]["G"],$this->Palette[$MinID]["B"],FALSE);
-
- $XPos = $XPos + $this->DivisionWidth;
- }
- }
-
- /* This function draw radar axis centered on the graph area */
- function drawRadarAxis($Data,$DataDescription,$Mosaic=TRUE,$BorderOffset=10,$A_R=60,$A_G=60,$A_B=60,$S_R=200,$S_G=200,$S_B=200,$MaxValue=-1,$valueMod=1)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawRadarAxis",$DataDescription);
- $this->validateData("drawRadarAxis",$Data);
-
- $C_TextColor = $this->AllocateColor($this->Picture,$A_R,$A_G,$A_B);
-
- /* Draw radar axis */
- $Points = count($Data);
- $Radius = ( $this->GArea_Y2 - $this->GArea_Y1 ) / 2 - $BorderOffset;
- $XCenter = ( $this->GArea_X2 - $this->GArea_X1 ) / 2 + $this->GArea_X1;
- $YCenter = ( $this->GArea_Y2 - $this->GArea_Y1 ) / 2 + $this->GArea_Y1;
-
- /* Search for the max value */
- if ( $MaxValue == -1 )
- {
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- if ( $Data[$Key][$ColName] > $MaxValue ) { $MaxValue = $Data[$Key][$ColName]; }
- }
- }
- }
-
- /* Draw the mosaic */
- if ( $Mosaic )
- {
- $RadiusScale = $Radius / $MaxValue;
- for ( $t=1; $t<=$MaxValue-1; $t++)
- {
- $TRadius = $RadiusScale * $t;
- $LastX1 = -1;
-
- for ( $i=0; $i<=$Points; $i++)
- {
- $Angle = -90 + $i * 360/$Points;
- $X1 = cos($Angle * 3.1418 / 180 ) * $TRadius + $XCenter;
- $Y1 = sin($Angle * 3.1418 / 180 ) * $TRadius + $YCenter;
- $X2 = cos($Angle * 3.1418 / 180 ) * ($TRadius+$RadiusScale) + $XCenter;
- $Y2 = sin($Angle * 3.1418 / 180 ) * ($TRadius+$RadiusScale) + $YCenter;
-
- if ( $t % 2 == 1 && $LastX1 != -1)
- {
- $Plots = "";
- $Plots[] = $X1; $Plots[] = $Y1;
- $Plots[] = $X2; $Plots[] = $Y2;
- $Plots[] = $LastX2; $Plots[] = $LastY2;
- $Plots[] = $LastX1; $Plots[] = $LastY1;
-
- $C_Graph = $this->AllocateColor($this->Picture,250,250,250);
- imagefilledpolygon($this->Picture,$Plots,(count($Plots)+1)/2,$C_Graph);
- }
-
- $LastX1 = $X1; $LastY1= $Y1;
- $LastX2 = $X2; $LastY2= $Y2;
- }
- }
- }
-
-
- /* Draw the spider web */
- for ( $t=1; $t<=$MaxValue; $t++)
- {
- $TRadius = ( $Radius / $MaxValue ) * $t;
- $LastX = -1;
-
- for ( $i=0; $i<=$Points; $i++)
- {
- $Angle = -90 + $i * 360/$Points;
- $X = cos($Angle * 3.1418 / 180 ) * $TRadius + $XCenter;
- $Y = sin($Angle * 3.1418 / 180 ) * $TRadius + $YCenter;
-
- if ( $LastX != -1 )
- $this->drawDottedLine($LastX,$LastY,$X,$Y,4,$S_R,$S_G,$S_B);
-
- $LastX = $X; $LastY= $Y;
- }
- }
-
- /* Draw the axis */
- for ( $i=0; $i<=$Points; $i++)
- {
- $Angle = -90 + $i * 360/$Points;
- $X = cos($Angle * 3.1418 / 180 ) * $Radius + $XCenter;
- $Y = sin($Angle * 3.1418 / 180 ) * $Radius + $YCenter;
-
- $this->drawLine($XCenter,$YCenter,$X,$Y,$A_R,$A_G,$A_B);
-
- $XOffset = 0; $YOffset = 0;
- if (isset($Data[$i][$DataDescription["Position"]]))
- {
- $Label = $Data[$i][$DataDescription["Position"]];
-
- $Positions = imagettfbbox($this->FontSize,0,$this->FontName,$Label);
- $Width = $Positions[2] - $Positions[6];
- $Height = $Positions[3] - $Positions[7];
-
- if ( $Angle >= 0 && $Angle <= 90 )
- $YOffset = $Height;
-
- if ( $Angle > 90 && $Angle <= 180 )
- { $YOffset = $Height; $XOffset = -$Width; }
-
- if ( $Angle > 180 && $Angle <= 270 )
- { $XOffset = -$Width; }
-
- imagettftext($this->Picture,$this->FontSize,0,$X+$XOffset,$Y+$YOffset,$C_TextColor,$this->FontName,$Label);
-
- if ( $this->BuildMap )
- {
- $vecX = $X - $XCenter;
- $vecY = $Y - $YCenter;
-
- // get a perpendicular vector
- $vecXtemp = $vecX;
- $vecX = -$vecY;
- $vecY = $vecXtemp;
-
- // normalization
- $vecLength = sqrt($vecX * $vecX + $vecY * $vecY);
- $vecX = $vecX / $vecLength;
- $vecY = $vecY / $vecLength;
-
- $tooltipValue = '';
- foreach ($DataDescription['Description'] as $key => $value) {
- $tooltipValue .= $value.' : '.sprintf("%.2f", $Data[$i][$key]).';';
- }
-
- $offset = 10;
- $poly = array(
- array($X+$vecX*-$offset,$Y+$vecY*-$offset),
- array($X+$vecX*+$offset,$Y+$vecY*+$offset),
- array($XCenter+$vecX*+$offset,$YCenter+$vecY*+$offset),
- array($XCenter+$vecX*-$offset,$YCenter+$vecY*-$offset),
- );
- $this->addPolyToImageMap($poly,$Label,$tooltipValue,'Radar');
- }
- }
- }
-
- /* Write the values */
- for ( $t=1; $t<=$MaxValue; $t++)
- {
- if ($t % $valueMod != 0)
- { continue; }
-
- $TRadius = ( $Radius / $MaxValue ) * $t;
-
- $Angle = -90 + 360 / $Points;
- $X1 = $XCenter;
- $Y1 = $YCenter - $TRadius;
- $X2 = cos($Angle * 3.1418 / 180 ) * $TRadius + $XCenter;
- $Y2 = sin($Angle * 3.1418 / 180 ) * $TRadius + $YCenter;
-
- $XPos = floor(($X2-$X1)/2) + $X1;
- $YPos = floor(($Y2-$Y1)/2) + $Y1;
-
- $Positions = imagettfbbox($this->FontSize,0,$this->FontName,$t);
- $X = $XPos - ( $X+$Positions[2] - $X+$Positions[6] ) / 2;
- $Y = $YPos + $this->FontSize;
-
- $this->drawFilledRoundedRectangle($X+$Positions[6]-2,$Y+$Positions[7]-1,$X+$Positions[2]+4,$Y+$Positions[3]+1,2,240,240,240);
- $this->drawRoundedRectangle($X+$Positions[6]-2,$Y+$Positions[7]-1,$X+$Positions[2]+4,$Y+$Positions[3]+1,2,220,220,220);
- imagettftext($this->Picture,$this->FontSize,0,$X,$Y,$C_TextColor,$this->FontName,$t);
- }
- }
-
- /* This function draw a radar graph centered on the graph area */
- function drawRadar($Data,$DataDescription,$BorderOffset=10,$MaxValue=-1)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawRadar",$DataDescription);
- $this->validateData("drawRadar",$Data);
-
- $Points = count($Data);
- $Radius = ( $this->GArea_Y2 - $this->GArea_Y1 ) / 2 - $BorderOffset;
- $XCenter = ( $this->GArea_X2 - $this->GArea_X1 ) / 2 + $this->GArea_X1;
- $YCenter = ( $this->GArea_Y2 - $this->GArea_Y1 ) / 2 + $this->GArea_Y1;
-
- /* Search for the max value */
- if ( $MaxValue == -1 )
- {
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- if ( $Data[$Key][$ColName] > $MaxValue ) { $MaxValue = $Data[$Key][$ColName]; }
- }
- }
- }
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $Angle = -90;
- $XLast = -1;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- {
- $Value = $Data[$Key][$ColName];
- $Strength = ( $Radius / $MaxValue ) * $Value;
-
- $XPos = cos($Angle * 3.1418 / 180 ) * $Strength + $XCenter;
- $YPos = sin($Angle * 3.1418 / 180 ) * $Strength + $YCenter;
-
- if ( $XLast != -1 )
- $this->drawLine($XLast,$YLast,$XPos,$YPos,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
-
- if ( $XLast == -1 )
- { $FirstX = $XPos; $FirstY = $YPos; }
-
- $Angle = $Angle + (360/$Points);
- $XLast = $XPos;
- $YLast = $YPos;
- }
- }
- $this->drawLine($XPos,$YPos,$FirstX,$FirstY,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- $GraphID++;
- }
- }
-
- /* This function draw a radar graph centered on the graph area */
- function drawFilledRadar($Data,$DataDescription,$Alpha=50,$BorderOffset=10,$MaxValue=-1)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawFilledRadar",$DataDescription);
- $this->validateData("drawFilledRadar",$Data);
-
- $Points = count($Data);
- $LayerWidth = $this->GArea_X2-$this->GArea_X1;
- $LayerHeight = $this->GArea_Y2-$this->GArea_Y1;
- $Radius = ( $this->GArea_Y2 - $this->GArea_Y1 ) / 2 - $BorderOffset;
- $XCenter = ( $this->GArea_X2 - $this->GArea_X1 ) / 2;
- $YCenter = ( $this->GArea_Y2 - $this->GArea_Y1 ) / 2;
-
- /* Search for the max value */
- if ( $MaxValue == -1 )
- {
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- if ( $Data[$Key][$ColName] > $MaxValue && is_numeric($Data[$Key][$ColName])) { $MaxValue = $Data[$Key][$ColName]; }
- }
- }
- }
-
- $GraphID = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- $ID = 0;
- foreach ( $DataDescription["Description"] as $keyI => $ValueI )
- { if ( $keyI == $ColName ) { $ColorID = $ID; }; $ID++; }
-
- $Angle = -90;
- $XLast = -1;
- $Plots = "";
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- {
- $Value = $Data[$Key][$ColName];
- if ( !is_numeric($Value) ) { $Value = 0; }
- $Strength = ( $Radius / $MaxValue ) * $Value;
-
- $XPos = cos($Angle * 3.1418 / 180 ) * $Strength + $XCenter;
- $YPos = sin($Angle * 3.1418 / 180 ) * $Strength + $YCenter;
-
- $Plots[] = $XPos;
- $Plots[] = $YPos;
-
- $Angle = $Angle + (360/$Points);
- $XLast = $XPos;
- $YLast = $YPos;
- }
- }
-
- if (isset($Plots[0]))
- {
- $Plots[] = $Plots[0];
- $Plots[] = $Plots[1];
-
- $this->Layers[0] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White = $this->AllocateColor($this->Layers[0],255,255,255);
- imagefilledrectangle($this->Layers[0],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[0],$C_White);
-
- $C_Graph = $this->AllocateColor($this->Layers[0],$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- imagefilledpolygon($this->Layers[0],$Plots,(count($Plots)+1)/2,$C_Graph);
-
- imagecopymerge($this->Picture,$this->Layers[0],$this->GArea_X1,$this->GArea_Y1,0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[0]);
-
- for($i=0;$i<=count($Plots)-4;$i=$i+2)
- $this->drawLine($Plots[$i]+$this->GArea_X1,$Plots[$i+1]+$this->GArea_Y1,$Plots[$i+2]+$this->GArea_X1,$Plots[$i+3]+$this->GArea_Y1,$this->Palette[$ColorID]["R"],$this->Palette[$ColorID]["G"],$this->Palette[$ColorID]["B"]);
- }
-
- $GraphID++;
- }
- }
-
- /* This function draw a flat pie chart */
- function drawBasicPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$R=255,$G=255,$B=255,$Decimals=0)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawBasicPieGraph",$DataDescription,FALSE);
- $this->validateData("drawBasicPieGraph",$Data);
-
- /* Determine pie sum */
- $Series = 0; $PieSum = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if ( $ColName != $DataDescription["Position"] )
- {
- $Series++;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- $PieSum = $PieSum + $Data[$Key][$ColName]; $iValues[] = $Data[$Key][$ColName]; $iLabels[] = $Data[$Key][$DataDescription["Position"]];
- }
- }
- }
-
- /* Validate serie */
- if ( $Series != 1 )
- RaiseFatal("Pie chart can only accept one serie of data.");
-
- $SpliceRatio = 360 / $PieSum;
- $SplicePercent = 100 / $PieSum;
-
- /* Calculate all polygons */
- $Angle = 0; $TopPlots = "";
- foreach($iValues as $Key => $Value)
- {
- $TopPlots[$Key][] = $XPos;
- $TopPlots[$Key][] = $YPos;
-
- /* Process labels position & size */
- $Caption = "";
- if ( !($DrawLabels == PIE_NOLABEL) )
- {
- $TAngle = $Angle+($Value*$SpliceRatio/2);
- if ($DrawLabels == PIE_PERCENTAGE)
- $Caption = (round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
- elseif ($DrawLabels == PIE_LABELS)
- $Caption = $iLabels[$Key];
- elseif ($DrawLabels == PIE_PERCENTAGE_LABEL)
- $Caption = $iLabels[$Key]."\r\n".(round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
- elseif ($DrawLabels == PIE_PERCENTAGE_LABEL)
- $Caption = $iLabels[$Key]."\r\n".(round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Caption);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = abs($Position[1])+abs($Position[3]);
-
- $TX = cos(($TAngle) * 3.1418 / 180 ) * ($Radius+10) + $XPos;
-
- if ( $TAngle > 0 && $TAngle < 180 )
- $TY = sin(($TAngle) * 3.1418 / 180 ) * ($Radius+10) + $YPos + 4;
- else
- $TY = sin(($TAngle) * 3.1418 / 180 ) * ($Radius+4) + $YPos - ($TextHeight/2);
-
- if ( $TAngle > 90 && $TAngle < 270 )
- $TX = $TX - $TextWidth;
-
- $C_TextColor = $this->AllocateColor($this->Picture,70,70,70);
- imagettftext($this->Picture,$this->FontSize,0,$TX,$TY,$C_TextColor,$this->FontName,$Caption);
- }
-
- /* Process pie slices */
- for($iAngle=$Angle;$iAngle<=$Angle+$Value*$SpliceRatio;$iAngle=$iAngle+.5)
- {
- $TopX = cos($iAngle * 3.1418 / 180 ) * $Radius + $XPos;
- $TopY = sin($iAngle * 3.1418 / 180 ) * $Radius + $YPos;
-
- $TopPlots[$Key][] = $TopX;
- $TopPlots[$Key][] = $TopY;
- }
-
- $TopPlots[$Key][] = $XPos;
- $TopPlots[$Key][] = $YPos;
-
- $Angle = $iAngle;
- }
- $PolyPlots = $TopPlots;
-
- /* Set array values type to float --- PHP Bug with imagefilledpolygon casting to integer */
- foreach ($TopPlots as $Key => $Value)
- { foreach ($TopPlots[$Key] as $Key2 => $Value2) { settype($TopPlots[$Key][$Key2],"float"); } }
-
- /* Draw Top polygons */
- foreach ($PolyPlots as $Key => $Value)
- {
- $C_GraphLo = $this->AllocateColor($this->Picture,$this->Palette[$Key]["R"],$this->Palette[$Key]["G"],$this->Palette[$Key]["B"]);
- imagefilledpolygon($this->Picture,$PolyPlots[$Key],(count($PolyPlots[$Key])+1)/2,$C_GraphLo);
- }
-
- $this->drawCircle($XPos-.5,$YPos-.5,$Radius,$R,$G,$B);
- $this->drawCircle($XPos-.5,$YPos-.5,$Radius+.5,$R,$G,$B);
-
- /* Draw Top polygons */
- foreach ($TopPlots as $Key => $Value)
- {
- for($j=0;$j<=count($TopPlots[$Key])-4;$j=$j+2)
- $this->drawLine($TopPlots[$Key][$j],$TopPlots[$Key][$j+1],$TopPlots[$Key][$j+2],$TopPlots[$Key][$j+3],$R,$G,$B);
- }
- }
-
- function drawFlatPieGraphWithShadow($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$SpliceDistance=0,$Decimals=0)
- {
- $this->drawFlatPieGraph($Data,$DataDescription,$XPos+$this->ShadowXDistance,$YPos+$this->ShadowYDistance,$Radius,PIE_NOLABEL,$SpliceDistance,$Decimals,TRUE);
- $this->drawFlatPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius,$DrawLabels,$SpliceDistance,$Decimals,FALSE);
- }
-
- /* This function draw a flat pie chart */
- function drawFlatPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$SpliceDistance=0,$Decimals=0,$AllBlack=FALSE)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawFlatPieGraph",$DataDescription,FALSE);
- $this->validateData("drawFlatPieGraph",$Data);
-
- $ShadowStatus = $this->ShadowActive ; $this->ShadowActive = FALSE;
-
- /* Determine pie sum */
- $Series = 0; $PieSum = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if ( $ColName != $DataDescription["Position"] )
- {
- $Series++;
- foreach ( $Data as $Key => $Values )
- {
- if ( isset($Data[$Key][$ColName]))
- $PieSum = $PieSum + $Data[$Key][$ColName]; $iValues[] = $Data[$Key][$ColName]; $iLabels[] = $Data[$Key][$DataDescription["Position"]];
- }
- }
- }
-
- /* Validate serie */
- if ( $Series != 1 )
- {
- RaiseFatal("Pie chart can only accept one serie of data.");
- return(0);
- }
-
- $SpliceRatio = 360 / $PieSum;
- $SplicePercent = 100 / $PieSum;
-
- /* Calculate all polygons */
- $Angle = 0; $TopPlots = "";
- foreach($iValues as $Key => $Value)
- {
- $XOffset = cos(($Angle+($Value/2*$SpliceRatio)) * 3.1418 / 180 ) * $SpliceDistance;
- $YOffset = sin(($Angle+($Value/2*$SpliceRatio)) * 3.1418 / 180 ) * $SpliceDistance;
-
- $TopPlots[$Key][] = round($XPos + $XOffset);
- $TopPlots[$Key][] = round($YPos + $YOffset);
-
- if ( $AllBlack )
- { $Rc = $this->ShadowRColor; $Gc = $this->ShadowGColor; $Bc = $this->ShadowBColor; }
- else
- { $Rc = $this->Palette[$Key]["R"]; $Gc = $this->Palette[$Key]["G"]; $Bc = $this->Palette[$Key]["B"]; }
-
- $XLineLast = ""; $YLineLast = "";
-
- /* Process labels position & size */
- $Caption = "";
- if ( !($DrawLabels == PIE_NOLABEL) )
- {
- $TAngle = $Angle+($Value*$SpliceRatio/2);
- if ($DrawLabels == PIE_PERCENTAGE)
- $Caption = (round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
- elseif ($DrawLabels == PIE_LABELS)
- $Caption = $iLabels[$Key];
- elseif ($DrawLabels == PIE_PERCENTAGE_LABEL)
- $Caption = $iLabels[$Key]."\r\n".(round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
- elseif ($DrawLabels == PIE_PERCENTAGE_LABEL)
- $Caption = $iLabels[$Key]."\r\n".(round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Caption);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = abs($Position[1])+abs($Position[3]);
-
- $TX = cos(($TAngle) * 3.1418 / 180 ) * ($Radius+10+$SpliceDistance) + $XPos;
-
- if ( $TAngle > 0 && $TAngle < 180 )
- $TY = sin(($TAngle) * 3.1418 / 180 ) * ($Radius+10+$SpliceDistance) + $YPos + 4;
- else
- $TY = sin(($TAngle) * 3.1418 / 180 ) * ($Radius+$SpliceDistance+4) + $YPos - ($TextHeight/2);
-
- if ( $TAngle > 90 && $TAngle < 270 )
- $TX = $TX - $TextWidth;
-
- $C_TextColor = $this->AllocateColor($this->Picture,70,70,70);
- imagettftext($this->Picture,$this->FontSize,0,$TX,$TY,$C_TextColor,$this->FontName,$Caption);
- }
-
- /* Process pie slices */
- if ( !$AllBlack )
- $LineColor = $this->AllocateColor($this->Picture,$Rc,$Gc,$Bc);
- else
- $LineColor = $this->AllocateColor($this->Picture,$Rc,$Gc,$Bc);
-
- $XLineLast = ""; $YLineLast = "";
- for($iAngle=$Angle;$iAngle<=$Angle+$Value*$SpliceRatio;$iAngle=$iAngle+.5)
- {
- $PosX = cos($iAngle * 3.1418 / 180 ) * $Radius + $XPos + $XOffset;
- $PosY = sin($iAngle * 3.1418 / 180 ) * $Radius + $YPos + $YOffset;
-
- $TopPlots[$Key][] = round($PosX); $TopPlots[$Key][] = round($PosY);
-
- if ( $iAngle == $Angle || $iAngle == $Angle+$Value*$SpliceRatio || $iAngle +.5 > $Angle+$Value*$SpliceRatio)
- $this->drawLine($XPos+$XOffset,$YPos+$YOffset,$PosX,$PosY,$Rc,$Gc,$Bc);
-
- if ( $XLineLast != "" )
- $this->drawLine($XLineLast,$YLineLast,$PosX,$PosY,$Rc,$Gc,$Bc);
-
- $XLineLast = $PosX; $YLineLast = $PosY;
- }
-
- $TopPlots[$Key][] = round($XPos + $XOffset); $TopPlots[$Key][] = round($YPos + $YOffset);
-
- $Angle = $iAngle;
- }
- $PolyPlots = $TopPlots;
-
- /* Draw Top polygons */
- foreach ($PolyPlots as $Key => $Value)
- {
- if ( !$AllBlack )
- $C_GraphLo = $this->AllocateColor($this->Picture,$this->Palette[$Key]["R"],$this->Palette[$Key]["G"],$this->Palette[$Key]["B"]);
- else
- $C_GraphLo = $this->AllocateColor($this->Picture,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor);
-
- imagefilledpolygon($this->Picture,$PolyPlots[$Key],(count($PolyPlots[$Key])+1)/2,$C_GraphLo);
- }
- $this->ShadowActive = $ShadowStatus;
- }
-
- /* This function draw a pseudo-3D pie chart */
- function drawPieGraph($Data,$DataDescription,$XPos,$YPos,$Radius=100,$DrawLabels=PIE_NOLABEL,$EnhanceColors=TRUE,$Skew=60,$SpliceHeight=20,$SpliceDistance=0,$Decimals=0)
- {
- /* Validate the Data and DataDescription array */
- $this->validateDataDescription("drawPieGraph",$DataDescription,FALSE);
- $this->validateData("drawPieGraph",$Data);
-
- /* Determine pie sum */
- $Series = 0; $PieSum = 0; $rPieSum = 0;
- foreach ( $DataDescription["Values"] as $Key2 => $ColName )
- {
- if ( $ColName != $DataDescription["Position"] )
- {
- $Series++;
- foreach ( $Data as $Key => $Values )
- if ( isset($Data[$Key][$ColName]))
- {
- if ( $Data[$Key][$ColName] == 0 )
- { $iValues[] = 0; $rValues[] = 0; $iLabels[] = $Data[$Key][$DataDescription["Position"]]; }
- // Removed : $PieSum++; $rValues[] = 1;
- else
- { $PieSum += $Data[$Key][$ColName]; $iValues[] = $Data[$Key][$ColName]; $iLabels[] = $Data[$Key][$DataDescription["Position"]]; $rValues[] = $Data[$Key][$ColName]; $rPieSum += $Data[$Key][$ColName];}
- }
- }
- }
-
- /* Validate serie */
- if ( $Series != 1 )
- RaiseFatal("Pie chart can only accept one serie of data.");
-
- $SpliceDistanceRatio = $SpliceDistance;
- $SkewHeight = ($Radius * $Skew) / 100;
- $SpliceRatio = (360 - $SpliceDistanceRatio * count($iValues) ) / $PieSum;
- $SplicePercent = 100 / $PieSum;
- $rSplicePercent = 100 / $rPieSum;
-
- /* Calculate all polygons */
- $Angle = 0; $CDev = 5;
- $TopPlots = ""; $BotPlots = "";
- $aTopPlots = ""; $aBotPlots = "";
- foreach($iValues as $Key => $Value)
- {
- $XCenterPos = cos(($Angle-$CDev+($Value*$SpliceRatio+$SpliceDistanceRatio)/2) * 3.1418 / 180 ) * $SpliceDistance + $XPos;
- $YCenterPos = sin(($Angle-$CDev+($Value*$SpliceRatio+$SpliceDistanceRatio)/2) * 3.1418 / 180 ) * $SpliceDistance + $YPos;
- $XCenterPos2 = cos(($Angle+$CDev+($Value*$SpliceRatio+$SpliceDistanceRatio)/2) * 3.1418 / 180 ) * $SpliceDistance + $XPos;
- $YCenterPos2 = sin(($Angle+$CDev+($Value*$SpliceRatio+$SpliceDistanceRatio)/2) * 3.1418 / 180 ) * $SpliceDistance + $YPos;
-
- $TopPlots[$Key][] = round($XCenterPos); $BotPlots[$Key][] = round($XCenterPos);
- $TopPlots[$Key][] = round($YCenterPos); $BotPlots[$Key][] = round($YCenterPos + $SpliceHeight);
- $aTopPlots[$Key][] = $XCenterPos; $aBotPlots[$Key][] = $XCenterPos;
- $aTopPlots[$Key][] = $YCenterPos; $aBotPlots[$Key][] = $YCenterPos + $SpliceHeight;
-
- /* Process labels position & size */
- $Caption = "";
- if ( !($DrawLabels == PIE_NOLABEL) )
- {
- $TAngle = $Angle+($Value*$SpliceRatio/2);
- if ($DrawLabels == PIE_PERCENTAGE)
- $Caption = (round($rValues[$Key] * pow(10,$Decimals) * $rSplicePercent)/pow(10,$Decimals))."%";
- elseif ($DrawLabels == PIE_LABELS)
- $Caption = $iLabels[$Key];
- elseif ($DrawLabels == PIE_PERCENTAGE_LABEL)
- $Caption = $iLabels[$Key]."\r\n".(round($Value * pow(10,$Decimals) * $SplicePercent)/pow(10,$Decimals))."%";
-
- $Position = imageftbbox($this->FontSize,0,$this->FontName,$Caption);
- $TextWidth = $Position[2]-$Position[0];
- $TextHeight = abs($Position[1])+abs($Position[3]);
-
- $TX = cos(($TAngle) * 3.1418 / 180 ) * ($Radius + 10)+ $XPos;
-
- if ( $TAngle > 0 && $TAngle < 180 )
- $TY = sin(($TAngle) * 3.1418 / 180 ) * ($SkewHeight + 10) + $YPos + $SpliceHeight + 4;
- else
- $TY = sin(($TAngle) * 3.1418 / 180 ) * ($SkewHeight + 4) + $YPos - ($TextHeight/2);
-
- if ( $TAngle > 90 && $TAngle < 270 )
- $TX = $TX - $TextWidth;
-
- $C_TextColor = $this->AllocateColor($this->Picture,70,70,70);
- imagettftext($this->Picture,$this->FontSize,0,$TX,$TY,$C_TextColor,$this->FontName,$Caption);
- }
-
- /* Process pie slices */
- for($iAngle=$Angle;$iAngle<=$Angle+$Value*$SpliceRatio;$iAngle=$iAngle+.5)
- {
- $TopX = cos($iAngle * 3.1418 / 180 ) * $Radius + $XPos;
- $TopY = sin($iAngle * 3.1418 / 180 ) * $SkewHeight + $YPos;
-
- $TopPlots[$Key][] = round($TopX); $BotPlots[$Key][] = round($TopX);
- $TopPlots[$Key][] = round($TopY); $BotPlots[$Key][] = round($TopY + $SpliceHeight);
- $aTopPlots[$Key][] = $TopX; $aBotPlots[$Key][] = $TopX;
- $aTopPlots[$Key][] = $TopY; $aBotPlots[$Key][] = $TopY + $SpliceHeight;
- }
-
- $TopPlots[$Key][] = round($XCenterPos2); $BotPlots[$Key][] = round($XCenterPos2);
- $TopPlots[$Key][] = round($YCenterPos2); $BotPlots[$Key][] = round($YCenterPos2 + $SpliceHeight);
- $aTopPlots[$Key][] = $XCenterPos2; $aBotPlots[$Key][] = $XCenterPos2;
- $aTopPlots[$Key][] = $YCenterPos2; $aBotPlots[$Key][] = $YCenterPos2 + $SpliceHeight;
-
- $Angle = $iAngle + $SpliceDistanceRatio;
- }
-
- /* Draw Bottom polygons */
- foreach($iValues as $Key => $Value)
- {
- $C_GraphLo = $this->AllocateColor($this->Picture,$this->Palette[$Key]["R"],$this->Palette[$Key]["G"],$this->Palette[$Key]["B"],-20);
- imagefilledpolygon($this->Picture,$BotPlots[$Key],(count($BotPlots[$Key])+1)/2,$C_GraphLo);
-
- if ( $EnhanceColors ) { $En = -10; } else { $En = 0; }
-
- for($j=0;$j<=count($aBotPlots[$Key])-4;$j=$j+2)
- $this->drawLine($aBotPlots[$Key][$j],$aBotPlots[$Key][$j+1],$aBotPlots[$Key][$j+2],$aBotPlots[$Key][$j+3],$this->Palette[$Key]["R"]+$En,$this->Palette[$Key]["G"]+$En,$this->Palette[$Key]["B"]+$En);
- }
-
- /* Draw pie layers */
- if ( $EnhanceColors ) { $ColorRatio = 30 / $SpliceHeight; } else { $ColorRatio = 25 / $SpliceHeight; }
- for($i=$SpliceHeight-1;$i>=1;$i--)
- {
- foreach($iValues as $Key => $Value)
- {
- $C_GraphLo = $this->AllocateColor($this->Picture,$this->Palette[$Key]["R"],$this->Palette[$Key]["G"],$this->Palette[$Key]["B"],-10);
- $Plots = ""; $Plot = 0;
- foreach($TopPlots[$Key] as $Key2 => $Value2)
- {
- $Plot++;
- if ( $Plot % 2 == 1 )
- $Plots[] = $Value2;
- else
- $Plots[] = $Value2+$i;
- }
- imagefilledpolygon($this->Picture,$Plots,(count($Plots)+1)/2,$C_GraphLo);
-
- $Index = count($Plots);
- if ($EnhanceColors ) {$ColorFactor = -20 + ($SpliceHeight - $i) * $ColorRatio; } else { $ColorFactor = 0; }
-
- $this->drawAntialiasPixel($Plots[0],$Plots[1],$this->Palette[$Key]["R"]+$ColorFactor,$this->Palette[$Key]["G"]+$ColorFactor,$this->Palette[$Key]["B"]+$ColorFactor);
- $this->drawAntialiasPixel($Plots[2],$Plots[3],$this->Palette[$Key]["R"]+$ColorFactor,$this->Palette[$Key]["G"]+$ColorFactor,$this->Palette[$Key]["B"]+$ColorFactor);
- $this->drawAntialiasPixel($Plots[$Index-4],$Plots[$Index-3],$this->Palette[$Key]["R"]+$ColorFactor,$this->Palette[$Key]["G"]+$ColorFactor,$this->Palette[$Key]["B"]+$ColorFactor);
- }
- }
-
- if ( $this->BuildMap )
- {
- // Add points to Image Map.
- foreach ($TopPlots as $key => $PointArr)
- {
- $serieName = $Data[$key][$DataDescription['Values'][1]];
- $serieValue = $Data[$key][$DataDescription['Values'][0]];
-
- // last point of the arc
- $lastX = $PointArr[count($PointArr)-4];
- $lastY = $PointArr[count($PointArr)-3];
-
- // the point at the middle
- $middleX = $PointArr[0];
- $middleY = $PointArr[1];
-
- // first point in the arc
- $firstX = $PointArr[2];
- $firstY = $PointArr[3];
-
- // point on the first third of the arc
- $firstThird = count($PointArr)/3;
- $firstThirdX = $PointArr[$firstThird + ($firstThird % 2)];
- $firstThirdY = $PointArr[$firstThird + ($firstThird % 2)+1];
-
- // point on the second third of the arc
- $secondThird = count($PointArr)/3*2;
- $secondThirdX = $PointArr[$secondThird + ($secondThird % 2)];
- $secondThirdY = $PointArr[$secondThird + ($secondThird % 2)+1];
-
- // Will create three polygons for every piece of the pie. In such way
- // no polygon will be concave. JS only works with convex polygons.
- $poly = array(
- array($middleX,$middleY),
- array($firstX,$firstY),
- array($firstThirdX,$firstThirdY),
- );
- $this->addPolyToImageMap($poly,$serieName,$serieValue,"Pie");
-
- $poly = array(
- array($middleX,$middleY),
- array($firstThirdX,$firstThirdY),
- array($secondThirdX,$secondThirdY),
- );
- $this->addPolyToImageMap($poly,$serieName,$serieValue,"Pie");
-
- $poly = array(
- array($middleX,$middleY),
- array($secondThirdX,$secondThirdY),
- array($lastX,$lastY),
- );
- $this->addPolyToImageMap($poly,$serieName,$serieValue,"Pie");
- }
- }
-
- /* Draw Top polygons */
- for($Key=count($iValues)-1;$Key>=0;$Key--)
- {
- $C_GraphLo = $this->AllocateColor($this->Picture,$this->Palette[$Key]["R"],$this->Palette[$Key]["G"],$this->Palette[$Key]["B"]);
- imagefilledpolygon($this->Picture,$TopPlots[$Key],(count($TopPlots[$Key])+1)/2,$C_GraphLo);
-
- if ( $EnhanceColors ) { $En = 10; } else { $En = 0; }
- for($j=0;$j<=count($aTopPlots[$Key])-4;$j=$j+2)
- $this->drawLine($aTopPlots[$Key][$j],$aTopPlots[$Key][$j+1],$aTopPlots[$Key][$j+2],$aTopPlots[$Key][$j+3],$this->Palette[$Key]["R"]+$En,$this->Palette[$Key]["G"]+$En,$this->Palette[$Key]["B"]+$En);
- }
- }
-
- /* This function can be used to set the background color */
- function drawBackground($R,$G,$B)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_Background = $this->AllocateColor($this->Picture,$R,$G,$B);
- imagefilledrectangle($this->Picture,0,0,$this->XSize,$this->YSize,$C_Background);
- }
-
- /* This function can be used to set the background color */
- function drawGraphAreaGradient($R,$G,$B,$Decay,$Target=TARGET_GRAPHAREA)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- if ( $Target == TARGET_GRAPHAREA ) { $X1 = $this->GArea_X1+1; $X2 = $this->GArea_X2-1; $Y1 = $this->GArea_Y1+1; $Y2 = $this->GArea_Y2; }
- if ( $Target == TARGET_BACKGROUND ) { $X1 = 0; $X2 = $this->XSize; $Y1 = 0; $Y2 = $this->YSize; }
-
- /* Positive gradient */
- if ( $Decay > 0 )
- {
- $YStep = ($Y2 - $Y1 - 2) / $Decay;
- for($i=0;$i<=$Decay;$i++)
- {
- $R-=1;$G-=1;$B-=1;
- $Yi1 = $Y1 + ( $i * $YStep );
- $Yi2 = ceil( $Yi1 + ( $i * $YStep ) + $YStep );
- if ( $Yi2 >= $Yi2 ) { $Yi2 = $Y2-1; }
-
- $C_Background = $this->AllocateColor($this->Picture,$R,$G,$B);
- imagefilledrectangle($this->Picture,$X1,$Yi1,$X2,$Yi2,$C_Background);
- }
- }
-
- /* Negative gradient */
- if ( $Decay < 0 )
- {
- $YStep = ($Y2 - $Y1 - 2) / -$Decay;
- $Yi1 = $Y1; $Yi2 = $Y1+$YStep;
- for($i=-$Decay;$i>=0;$i--)
- {
- $R+=1;$G+=1;$B+=1;
- $C_Background = $this->AllocateColor($this->Picture,$R,$G,$B);
- imagefilledrectangle($this->Picture,$X1,$Yi1,$X2,$Yi2,$C_Background);
-
- $Yi1+= $YStep;
- $Yi2+= $YStep;
- if ( $Yi2 >= $Yi2 ) { $Yi2 = $Y2-1; }
- }
- }
- }
-
- /* This function create a rectangle with antialias */
- function drawRectangle($X1,$Y1,$X2,$Y2,$R,$G,$B)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_Rectangle = $this->AllocateColor($this->Picture,$R,$G,$B);
-
- $X1=$X1-.2;$Y1=$Y1-.2;
- $X2=$X2+.2;$Y2=$Y2+.2;
- $this->drawLine($X1,$Y1,$X2,$Y1,$R,$G,$B);
- $this->drawLine($X2,$Y1,$X2,$Y2,$R,$G,$B);
- $this->drawLine($X2,$Y2,$X1,$Y2,$R,$G,$B);
- $this->drawLine($X1,$Y2,$X1,$Y1,$R,$G,$B);
- }
-
- /* This function create a filled rectangle with antialias */
- function drawFilledRectangle($X1,$Y1,$X2,$Y2,$R,$G,$B,$DrawBorder=TRUE,$Alpha=100,$NoFallBack=FALSE)
- {
- if ( $X2 < $X1 ) { list($X1, $X2) = array($X2, $X1); }
- if ( $Y2 < $Y1 ) { list($Y1, $Y2) = array($Y2, $Y1); }
-
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- if ( $Alpha == 100 )
- {
- /* Process shadows */
- if ( $this->ShadowActive && !$NoFallBack )
- {
- $this->drawFilledRectangle($X1+$this->ShadowXDistance,$Y1+$this->ShadowYDistance,$X2+$this->ShadowXDistance,$Y2+$this->ShadowYDistance,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor,FALSE,$this->ShadowAlpha,TRUE);
- if ( $this->ShadowBlur != 0 )
- {
- $AlphaDecay = ($this->ShadowAlpha / $this->ShadowBlur);
-
- for($i=1; $i<=$this->ShadowBlur; $i++)
- $this->drawFilledRectangle($X1+$this->ShadowXDistance-$i/2,$Y1+$this->ShadowYDistance-$i/2,$X2+$this->ShadowXDistance-$i/2,$Y2+$this->ShadowYDistance-$i/2,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor,FALSE,$this->ShadowAlpha-$AlphaDecay*$i,TRUE);
- for($i=1; $i<=$this->ShadowBlur; $i++)
- $this->drawFilledRectangle($X1+$this->ShadowXDistance+$i/2,$Y1+$this->ShadowYDistance+$i/2,$X2+$this->ShadowXDistance+$i/2,$Y2+$this->ShadowYDistance+$i/2,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor,FALSE,$this->ShadowAlpha-$AlphaDecay*$i,TRUE);
- }
- }
-
- $C_Rectangle = $this->AllocateColor($this->Picture,$R,$G,$B);
- imagefilledrectangle($this->Picture,round($X1),round($Y1),round($X2),round($Y2),$C_Rectangle);
- }
- else
- {
- $LayerWidth = abs($X2-$X1)+2;
- $LayerHeight = abs($Y2-$Y1)+2;
-
- $this->Layers[0] = imagecreatetruecolor($LayerWidth,$LayerHeight);
- $C_White = $this->AllocateColor($this->Layers[0],255,255,255);
- imagefilledrectangle($this->Layers[0],0,0,$LayerWidth,$LayerHeight,$C_White);
- imagecolortransparent($this->Layers[0],$C_White);
-
- $C_Rectangle = $this->AllocateColor($this->Layers[0],$R,$G,$B);
- imagefilledrectangle($this->Layers[0],round(1),round(1),round($LayerWidth-1),round($LayerHeight-1),$C_Rectangle);
-
- imagecopymerge($this->Picture,$this->Layers[0],round(min($X1,$X2)-1),round(min($Y1,$Y2)-1),0,0,$LayerWidth,$LayerHeight,$Alpha);
- imagedestroy($this->Layers[0]);
- }
-
- if ( $DrawBorder )
- {
- $ShadowSettings = $this->ShadowActive; $this->ShadowActive = FALSE;
- $this->drawRectangle($X1,$Y1,$X2,$Y2,$R,$G,$B);
- $this->ShadowActive = $ShadowSettings;
- }
- }
-
- /* This function create a rectangle with rounded corners and antialias */
- function drawRoundedRectangle($X1,$Y1,$X2,$Y2,$Radius,$R,$G,$B)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_Rectangle = $this->AllocateColor($this->Picture,$R,$G,$B);
-
- $Step = 90 / ((3.1418 * $Radius)/2);
-
- for($i=0;$i<=90;$i=$i+$Step)
- {
- $X = cos(($i+180)*3.1418/180) * $Radius + $X1 + $Radius;
- $Y = sin(($i+180)*3.1418/180) * $Radius + $Y1 + $Radius;
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
-
- $X = cos(($i-90)*3.1418/180) * $Radius + $X2 - $Radius;
- $Y = sin(($i-90)*3.1418/180) * $Radius + $Y1 + $Radius;
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
-
- $X = cos(($i)*3.1418/180) * $Radius + $X2 - $Radius;
- $Y = sin(($i)*3.1418/180) * $Radius + $Y2 - $Radius;
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
-
- $X = cos(($i+90)*3.1418/180) * $Radius + $X1 + $Radius;
- $Y = sin(($i+90)*3.1418/180) * $Radius + $Y2 - $Radius;
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
- }
-
- $X1=$X1-.2;$Y1=$Y1-.2;
- $X2=$X2+.2;$Y2=$Y2+.2;
- $this->drawLine($X1+$Radius,$Y1,$X2-$Radius,$Y1,$R,$G,$B);
- $this->drawLine($X2,$Y1+$Radius,$X2,$Y2-$Radius,$R,$G,$B);
- $this->drawLine($X2-$Radius,$Y2,$X1+$Radius,$Y2,$R,$G,$B);
- $this->drawLine($X1,$Y2-$Radius,$X1,$Y1+$Radius,$R,$G,$B);
- }
-
- /* This function create a filled rectangle with rounded corners and antialias */
- function drawFilledRoundedRectangle($X1,$Y1,$X2,$Y2,$Radius,$R,$G,$B)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_Rectangle = $this->AllocateColor($this->Picture,$R,$G,$B);
-
- $Step = 90 / ((3.1418 * $Radius)/2);
-
- for($i=0;$i<=90;$i=$i+$Step)
- {
- $Xi1 = cos(($i+180)*3.1418/180) * $Radius + $X1 + $Radius;
- $Yi1 = sin(($i+180)*3.1418/180) * $Radius + $Y1 + $Radius;
-
- $Xi2 = cos(($i-90)*3.1418/180) * $Radius + $X2 - $Radius;
- $Yi2 = sin(($i-90)*3.1418/180) * $Radius + $Y1 + $Radius;
-
- $Xi3 = cos(($i)*3.1418/180) * $Radius + $X2 - $Radius;
- $Yi3 = sin(($i)*3.1418/180) * $Radius + $Y2 - $Radius;
-
- $Xi4 = cos(($i+90)*3.1418/180) * $Radius + $X1 + $Radius;
- $Yi4 = sin(($i+90)*3.1418/180) * $Radius + $Y2 - $Radius;
-
- imageline($this->Picture,$Xi1,$Yi1,$X1+$Radius,$Yi1,$C_Rectangle);
- imageline($this->Picture,$X2-$Radius,$Yi2,$Xi2,$Yi2,$C_Rectangle);
- imageline($this->Picture,$X2-$Radius,$Yi3,$Xi3,$Yi3,$C_Rectangle);
- imageline($this->Picture,$Xi4,$Yi4,$X1+$Radius,$Yi4,$C_Rectangle);
-
- $this->drawAntialiasPixel($Xi1,$Yi1,$R,$G,$B);
- $this->drawAntialiasPixel($Xi2,$Yi2,$R,$G,$B);
- $this->drawAntialiasPixel($Xi3,$Yi3,$R,$G,$B);
- $this->drawAntialiasPixel($Xi4,$Yi4,$R,$G,$B);
- }
-
- imagefilledrectangle($this->Picture,$X1,$Y1+$Radius,$X2,$Y2-$Radius,$C_Rectangle);
- imagefilledrectangle($this->Picture,$X1+$Radius,$Y1,$X2-$Radius,$Y2,$C_Rectangle);
-
- $X1=$X1-.2;$Y1=$Y1-.2;
- $X2=$X2+.2;$Y2=$Y2+.2;
- $this->drawLine($X1+$Radius,$Y1,$X2-$Radius,$Y1,$R,$G,$B);
- $this->drawLine($X2,$Y1+$Radius,$X2,$Y2-$Radius,$R,$G,$B);
- $this->drawLine($X2-$Radius,$Y2,$X1+$Radius,$Y2,$R,$G,$B);
- $this->drawLine($X1,$Y2-$Radius,$X1,$Y1+$Radius,$R,$G,$B);
- }
-
- /* This function create a circle with antialias */
- function drawCircle($Xc,$Yc,$Height,$R,$G,$B,$Width=0)
- {
- if ( $Width == 0 ) { $Width = $Height; }
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_Circle = $this->AllocateColor($this->Picture,$R,$G,$B);
- $Step = 360 / (2 * 3.1418 * max($Width,$Height));
-
- for($i=0;$i<=360;$i=$i+$Step)
- {
- $X = cos($i*3.1418/180) * $Height + $Xc;
- $Y = sin($i*3.1418/180) * $Width + $Yc;
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
- }
- }
-
- /* This function create a filled circle/ellipse with antialias */
- function drawFilledCircle($Xc,$Yc,$Height,$R,$G,$B,$Width=0)
- {
- if ( $Width == 0 ) { $Width = $Height; }
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $C_Circle = $this->AllocateColor($this->Picture,$R,$G,$B);
- $Step = 360 / (2 * 3.1418 * max($Width,$Height));
-
- for($i=90;$i<=270;$i=$i+$Step)
- {
- $X1 = cos($i*3.1418/180) * $Height + $Xc;
- $Y1 = sin($i*3.1418/180) * $Width + $Yc;
- $X2 = cos((180-$i)*3.1418/180) * $Height + $Xc;
- $Y2 = sin((180-$i)*3.1418/180) * $Width + $Yc;
-
- $this->drawAntialiasPixel($X1-1,$Y1-1,$R,$G,$B);
- $this->drawAntialiasPixel($X2-1,$Y2-1,$R,$G,$B);
-
- if ( ($Y1-1) > $Yc - max($Width,$Height) )
- imageline($this->Picture,$X1,$Y1-1,$X2-1,$Y2-1,$C_Circle);
- }
- }
-
- /* This function will draw a filled ellipse */
- function drawEllipse($Xc,$Yc,$Height,$Width,$R,$G,$B)
- { $this->drawCircle($Xc,$Yc,$Height,$R,$G,$B,$Width); }
-
- /* This function will draw an ellipse */
- function drawFilledEllipse($Xc,$Yc,$Height,$Width,$R,$G,$B)
- { $this->drawFilledCircle($Xc,$Yc,$Height,$R,$G,$B,$Width); }
-
- /* This function create a line with antialias */
- function drawLine($X1,$Y1,$X2,$Y2,$R,$G,$B,$GraphFunction=FALSE)
- {
- if ( $this->LineDotSize > 1 ) { $this->drawDottedLine($X1,$Y1,$X2,$Y2,$this->LineDotSize,$R,$G,$B,$GraphFunction); return(0); }
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $Distance = sqrt(($X2-$X1)*($X2-$X1)+($Y2-$Y1)*($Y2-$Y1));
- if ( $Distance == 0 )
- return(-1);
- $XStep = ($X2-$X1) / $Distance;
- $YStep = ($Y2-$Y1) / $Distance;
-
- for($i=0;$i<=$Distance;$i++)
- {
- $X = $i * $XStep + $X1;
- $Y = $i * $YStep + $Y1;
-
- if ( ($X >= $this->GArea_X1 && $X <= $this->GArea_X2 && $Y >= $this->GArea_Y1 && $Y <= $this->GArea_Y2) || !$GraphFunction )
- {
- if ( $this->LineWidth == 1 )
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
- else
- {
- $StartOffset = -($this->LineWidth/2); $EndOffset = ($this->LineWidth/2);
- for($j=$StartOffset;$j<=$EndOffset;$j++)
- $this->drawAntialiasPixel($X+$j,$Y+$j,$R,$G,$B);
- }
- }
- }
- }
-
- /* This function create a line with antialias */
- function drawDottedLine($X1,$Y1,$X2,$Y2,$DotSize,$R,$G,$B,$GraphFunction=FALSE)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $Distance = sqrt(($X2-$X1)*($X2-$X1)+($Y2-$Y1)*($Y2-$Y1));
-
- $XStep = ($X2-$X1) / $Distance;
- $YStep = ($Y2-$Y1) / $Distance;
-
- $DotIndex = 0;
- for($i=0;$i<=$Distance;$i++)
- {
- $X = $i * $XStep + $X1;
- $Y = $i * $YStep + $Y1;
-
- if ( $DotIndex <= $DotSize)
- {
- if ( ($X >= $this->GArea_X1 && $X <= $this->GArea_X2 && $Y >= $this->GArea_Y1 && $Y <= $this->GArea_Y2) || !$GraphFunction )
- {
- if ( $this->LineWidth == 1 )
- $this->drawAntialiasPixel($X,$Y,$R,$G,$B);
- else
- {
- $StartOffset = -($this->LineWidth/2); $EndOffset = ($this->LineWidth/2);
- for($j=$StartOffset;$j<=$EndOffset;$j++)
- $this->drawAntialiasPixel($X+$j,$Y+$j,$R,$G,$B);
- }
- }
- }
-
- $DotIndex++;
- if ( $DotIndex == $DotSize * 2 )
- $DotIndex = 0;
- }
- }
-
- /* Load a PNG file and draw it over the chart */
- function drawFromPNG($FileName,$X,$Y,$Alpha=100)
- { $this->drawFromPicture(1,$FileName,$X,$Y,$Alpha); }
-
- /* Load a GIF file and draw it over the chart */
- function drawFromGIF($FileName,$X,$Y,$Alpha=100)
- { $this->drawFromPicture(2,$FileName,$X,$Y,$Alpha); }
-
- /* Load a JPEG file and draw it over the chart */
- function drawFromJPG($FileName,$X,$Y,$Alpha=100)
- { $this->drawFromPicture(3,$FileName,$X,$Y,$Alpha); }
-
- /* Generic loader function for external pictures */
- function drawFromPicture($PicType,$FileName,$X,$Y,$Alpha=100)
- {
- if ( file_exists($FileName))
- {
- $Infos = getimagesize($FileName);
- $Width = $Infos[0];
- $Height = $Infos[1];
- if ( $PicType == 1 ) { $Raster = imagecreatefrompng($FileName); }
- if ( $PicType == 2 ) { $Raster = imagecreatefromgif($FileName); }
- if ( $PicType == 3 ) { $Raster = imagecreatefromjpeg($FileName); }
-
- imagecopymerge($this->Picture,$Raster,$X,$Y,0,0,$Width,$Height,$Alpha);
- imagedestroy($Raster);
- }
- }
-
- /* Draw an alpha pixel */
- function drawAlphaPixel($X,$Y,$Alpha,$R,$G,$B)
- {
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- if ( $X < 0 || $Y < 0 || $X >= $this->XSize || $Y >= $this->YSize )
- return(-1);
-
- $RGB2 = imagecolorat($this->Picture, $X, $Y);
- $R2 = ($RGB2 >> 16) & 0xFF;
- $G2 = ($RGB2 >> 8) & 0xFF;
- $B2 = $RGB2 & 0xFF;
-
- $iAlpha = (100 - $Alpha)/100;
- $Alpha = $Alpha / 100;
-
- $Ra = floor($R*$Alpha+$R2*$iAlpha);
- $Ga = floor($G*$Alpha+$G2*$iAlpha);
- $Ba = floor($B*$Alpha+$B2*$iAlpha);
-
- $C_Aliased = $this->AllocateColor($this->Picture,$Ra,$Ga,$Ba);
- imagesetpixel($this->Picture,$X,$Y,$C_Aliased);
- }
-
- /* Color helper */
- function AllocateColor($Picture,$R,$G,$B,$Factor=0)
- {
- $R = $R + $Factor;
- $G = $G + $Factor;
- $B = $B + $Factor;
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- return(imagecolorallocate($Picture,$R,$G,$B));
- }
-
- /* Add a border to the picture */
- function addBorder($Size=3,$R=0,$G=0,$B=0)
- {
- $Width = $this->XSize+2*$Size;
- $Height = $this->YSize+2*$Size;
-
- $Resampled = imagecreatetruecolor($Width,$Height);
- $C_Background = $this->AllocateColor($Resampled,$R,$G,$B);
- imagefilledrectangle($Resampled,0,0,$Width,$Height,$C_Background);
-
- imagecopy($Resampled,$this->Picture,$Size,$Size,0,0,$this->XSize,$this->YSize);
- imagedestroy($this->Picture);
-
- $this->XSize = $Width;
- $this->YSize = $Height;
-
- $this->Picture = imagecreatetruecolor($this->XSize,$this->YSize);
- $C_White = $this->AllocateColor($this->Picture,255,255,255);
- imagefilledrectangle($this->Picture,0,0,$this->XSize,$this->YSize,$C_White);
- imagecolortransparent($this->Picture,$C_White);
- imagecopy($this->Picture,$Resampled,0,0,0,0,$this->XSize,$this->YSize);
- }
-
- /* Render the current picture to a file */
- function Render($FileName)
- {
- if ( $this->ErrorReporting )
- $this->printErrors($this->ErrorInterface);
-
- /* Save image map if requested */
- if ( $this->BuildMap )
- $this->SaveImageMap();
-
- imagepng($this->Picture,$FileName);
- }
-
- /* Render the current picture to STDOUT */
- function Stroke()
- {
- if ( $this->ErrorReporting )
- $this->printErrors("GD");
-
- /* Save image map if requested */
- if ( $this->BuildMap )
- $this->SaveImageMap();
-
- header('Content-type: image/png');
- imagepng($this->Picture);
- }
-
- /* Private functions for internal processing */
- function drawAntialiasPixel($X,$Y,$R,$G,$B,$Alpha=100,$NoFallBack=FALSE)
- {
- /* Process shadows */
- if ( $this->ShadowActive && !$NoFallBack )
- {
- $this->drawAntialiasPixel($X+$this->ShadowXDistance,$Y+$this->ShadowYDistance,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor,$this->ShadowAlpha,TRUE);
- if ( $this->ShadowBlur != 0 )
- {
- $AlphaDecay = ($this->ShadowAlpha / $this->ShadowBlur);
-
- for($i=1; $i<=$this->ShadowBlur; $i++)
- $this->drawAntialiasPixel($X+$this->ShadowXDistance-$i/2,$Y+$this->ShadowYDistance-$i/2,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor,$this->ShadowAlpha-$AlphaDecay*$i,TRUE);
- for($i=1; $i<=$this->ShadowBlur; $i++)
- $this->drawAntialiasPixel($X+$this->ShadowXDistance+$i/2,$Y+$this->ShadowYDistance+$i/2,$this->ShadowRColor,$this->ShadowGColor,$this->ShadowBColor,$this->ShadowAlpha-$AlphaDecay*$i,TRUE);
- }
- }
-
- if ( $R < 0 ) { $R = 0; } if ( $R > 255 ) { $R = 255; }
- if ( $G < 0 ) { $G = 0; } if ( $G > 255 ) { $G = 255; }
- if ( $B < 0 ) { $B = 0; } if ( $B > 255 ) { $B = 255; }
-
- $Plot = "";
- $Xi = floor($X);
- $Yi = floor($Y);
-
- if ( $Xi == $X && $Yi == $Y)
- {
- if ( $Alpha == 100 )
- {
- $C_Aliased = $this->AllocateColor($this->Picture,$R,$G,$B);
- imagesetpixel($this->Picture,$X,$Y,$C_Aliased);
- }
- else
- $this->drawAlphaPixel($X,$Y,$Alpha,$R,$G,$B);
- }
- else
- {
- $Alpha1 = (((1 - ($X - floor($X))) * (1 - ($Y - floor($Y))) * 100) / 100) * $Alpha;
- if ( $Alpha1 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi,$Yi,$Alpha1,$R,$G,$B); }
-
- $Alpha2 = ((($X - floor($X)) * (1 - ($Y - floor($Y))) * 100) / 100) * $Alpha;
- if ( $Alpha2 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi+1,$Yi,$Alpha2,$R,$G,$B); }
-
- $Alpha3 = (((1 - ($X - floor($X))) * ($Y - floor($Y)) * 100) / 100) * $Alpha;
- if ( $Alpha3 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi,$Yi+1,$Alpha3,$R,$G,$B); }
-
- $Alpha4 = ((($X - floor($X)) * ($Y - floor($Y)) * 100) / 100) * $Alpha;
- if ( $Alpha4 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi+1,$Yi+1,$Alpha4,$R,$G,$B); }
- }
- }
-
- /* Validate data contained in the description array */
- function validateDataDescription($FunctionName,&$DataDescription,$DescriptionRequired=TRUE)
- {
- if (!isset($DataDescription["Position"]))
- {
- $this->Errors[] = "[Warning] ".$FunctionName." - Y Labels are not set.";
- $DataDescription["Position"] = "Name";
- }
-
- if ( $DescriptionRequired )
- {
- if (!isset($DataDescription["Description"]))
- {
- $this->Errors[] = "[Warning] ".$FunctionName." - Series descriptions are not set.";
- foreach($DataDescription["Values"] as $key => $Value)
- {
- $DataDescription["Description"][$Value] = $Value;
- }
- }
-
- if (count($DataDescription["Description"]) < count($DataDescription["Values"]))
- {
- $this->Errors[] = "[Warning] ".$FunctionName." - Some series descriptions are not set.";
- foreach($DataDescription["Values"] as $key => $Value)
- {
- if ( !isset($DataDescription["Description"][$Value]))
- $DataDescription["Description"][$Value] = $Value;
- }
- }
- }
- }
-
- /* Validate data contained in the data array */
- function validateData($FunctionName,&$Data)
- {
- $DataSummary = array();
-
- foreach($Data as $key => $Values)
- {
- foreach($Values as $key2 => $Value)
- {
- if (!isset($DataSummary[$key2]))
- $DataSummary[$key2] = 1;
- else
- $DataSummary[$key2]++;
- }
- }
-
- if ( max($DataSummary) == 0 )
- $this->Errors[] = "[Warning] ".$FunctionName." - No data set.";
-
- foreach($DataSummary as $key => $Value)
- {
- if ($Value < max($DataSummary))
- {
- $this->Errors[] = "[Warning] ".$FunctionName." - Missing data in serie ".$key.".";
- }
- }
- }
-
- /* Print all error messages on the CLI or graphically */
- function printErrors($Mode="CLI")
- {
- if (count($this->Errors) == 0)
- return(0);
-
- if ( $Mode == "CLI" )
- {
- foreach($this->Errors as $key => $Value)
- echo $Value."\r\n";
- }
- elseif ( $Mode == "GD" )
- {
- $this->setLineStyle($Width=1);
- $MaxWidth = 0;
- foreach($this->Errors as $key => $Value)
- {
- $Position = imageftbbox($this->ErrorFontSize,0,$this->ErrorFontName,$Value);
- $TextWidth = $Position[2]-$Position[0];
- if ( $TextWidth > $MaxWidth ) { $MaxWidth = $TextWidth; }
- }
- $this->drawFilledRoundedRectangle($this->XSize-($MaxWidth+20),$this->YSize-(20+(($this->ErrorFontSize+4)*count($this->Errors))),$this->XSize-10,$this->YSize-10,6,233,185,185);
- $this->drawRoundedRectangle($this->XSize-($MaxWidth+20),$this->YSize-(20+(($this->ErrorFontSize+4)*count($this->Errors))),$this->XSize-10,$this->YSize-10,6,193,145,145);
-
- $C_TextColor = $this->AllocateColor($this->Picture,133,85,85);
- $YPos = $this->YSize - (18 + (count($this->Errors)-1) * ($this->ErrorFontSize + 4));
- foreach($this->Errors as $key => $Value)
- {
- imagettftext($this->Picture,$this->ErrorFontSize,0,$this->XSize-($MaxWidth+15),$YPos,$C_TextColor,$this->ErrorFontName,$Value);
- $YPos = $YPos + ($this->ErrorFontSize + 4);
- }
- }
- }
-
- /* Activate the image map creation process */
- function setImageMap($Mode=TRUE,$GraphID="MyGraph")
- {
- $this->BuildMap = $Mode;
- $this->MapID = $GraphID;
- }
-
- /* Add a box into the image map */
- function addToImageMap($X1,$Y1,$X2,$Y2,$SerieName,$Value,$CallerFunction)
- {
- $poly = array(array($X1,$Y1),array($X2,$Y1),array($X2,$Y2),array($X1,$Y2));
- $this->addPolyToImageMap($poly,$SerieName,$Value,$CallerFunction);
- }
-
- function addPolyToImageMap($poly,$SerieName,$Value,$CallerFunction)
- {
- if ( $this->MapFunction == NULL || $this->MapFunction == $CallerFunction)
- {
- $this->ImageMap[] = array(
- 'n' => (string)$SerieName,
- 'v' => (string)$Value,
- 'p' => $poly,
- );
- $this->MapFunction = $CallerFunction;
- }
- }
-
- /* Draw image map to the current chart image */
- function debugImageMap()
- {
- foreach ($this->ImageMap as $polygon)
- {
- $points = array();
- foreach ($polygon['p'] as $point)
- {
- $points[] = $point[0];
- $points[] = $point[1];
- }
-
- $color = $this->AllocateColor($this->Picture,rand(0,255),rand(0,255),rand(0,255));
- imagefilledpolygon($this->Picture,$points,(count($points)+1)/2,$color);
- }
-
- }
-
- /* Get the current image map */
- function getImageMap()
- {
- return $this->ImageMap;
- }
-
- /* Load and cleanup the image map from disk */
- function getSavedImageMap($MapName,$Flush=TRUE)
- {
- /* Strip HTML query strings */
- $Values = $this->tmpFolder.$MapName;
- $Value = split("\?",$Values);
- $FileName = $Value[0];
-
- if ( file_exists($FileName) )
- {
- $Handle = fopen($FileName, "r");
- $MapContent = fread($Handle, filesize($FileName));
- fclose($Handle);
- echo $MapContent;
-
- if ( $Flush )
- unlink($FileName);
-
- exit();
- }
- else
- {
- header("HTTP/1.0 404 Not Found");
- exit();
- }
- }
-
- /* Save the image map to the disk */
- function SaveImageMap()
- {
- if ( !$this->BuildMap ) { return(-1); }
-
- if ( $this->ImageMap == NULL )
- {
- $this->Errors[] = "[Warning] SaveImageMap - Image map is empty.";
- return(-1);
- }
-
- $Handle = fopen($this->tmpFolder.$this->MapID, 'w');
- if ( !$Handle )
- {
- $this->Errors[] = "[Warning] SaveImageMap - Cannot save the image map.";
- return(-1);
- }
- else
- {
- fwrite($Handle, serialize($this->getImageMap()));
- }
- fclose ($Handle);
- }
-
- /* Convert seconds to a time format string */
- function ToTime($Value)
- {
- $Hour = floor($Value/3600);
- $Minute = floor(($Value - $Hour*3600)/60);
- $Second = floor($Value - $Hour*3600 - $Minute*60);
-
- if (strlen($Hour) == 1 ) { $Hour = "0".$Hour; }
- if (strlen($Minute) == 1 ) { $Minute = "0".$Minute; }
- if (strlen($Second) == 1 ) { $Second = "0".$Second; }
-
- return($Hour.":".$Minute.":".$Second);
- }
-
- /* Convert to metric system */
- function ToMetric($Value)
- {
- $Go = floor($Value/1000000000);
- $Mo = floor(($Value - $Go*1000000000)/1000000);
- $Ko = floor(($Value - $Go*1000000000 - $Mo*1000000)/1000);
- $o = floor($Value - $Go*1000000000 - $Mo*1000000 - $Ko*1000);
-
- if ($Go != 0) { return($Go.".".$Mo."g"); }
- if ($Mo != 0) { return($Mo.".".$ko."m"); }
- if ($Ko != 0) { return($Ko.".".$o)."k"; }
- return($o);
- }
-
- /* Convert to curency */
- function ToCurrency($Value)
- {
- $Go = floor($Value/1000000000);
- $Mo = floor(($Value - $Go*1000000000)/1000000);
- $Ko = floor(($Value - $Go*1000000000 - $Mo*1000000)/1000);
- $o = floor($Value - $Go*1000000000 - $Mo*1000000 - $Ko*1000);
-
- if ( strlen($o) == 1 ) { $o = "00".$o; }
- if ( strlen($o) == 2 ) { $o = "0".$o; }
-
- $ResultString = $o;
- if ( $Ko != 0 ) { $ResultString = $Ko.".".$ResultString; }
- if ( $Mo != 0 ) { $ResultString = $Mo.".".$ResultString; }
- if ( $Go != 0 ) { $ResultString = $Go.".".$ResultString; }
-
- $ResultString = $this->Currency.$ResultString;
- return($ResultString);
- }
-
- /* Set date format for axis labels */
- function setDateFormat($Format)
- {
- $this->DateFormat = $Format;
- }
-
- /* Convert TS to a date format string */
- function ToDate($Value)
- {
- return(date($this->DateFormat,$Value));
- }
-
- /* Check if a number is a full integer (for scaling) */
- function isRealInt($Value)
- {
- if ($Value == floor($Value))
- return(TRUE);
- return(FALSE);
- }
- }
-
- function RaiseFatal($Message)
- {
- echo "[FATAL] ".$Message."\r\n";
- exit();
- }
-?>
\ No newline at end of file
diff --git a/libraries/chart/pChart/pData.class b/libraries/chart/pChart/pData.class
deleted file mode 100644
index 1c4a301..0000000
--- a/libraries/chart/pChart/pData.class
+++ /dev/null
@@ -1,260 +0,0 @@
-<?php
- /*
- pData - Simplifying data population for pChart
- Copyright (C) 2008 Jean-Damien POGOLOTTI
- Version 1.13 last updated on 08/17/08
-
- http://pchart.sourceforge.net
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 1,2,3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- Class initialisation :
- pData()
- Data populating methods :
- ImportFromCSV($FileName,$Delimiter=",",$DataColumns=-1,$HasHeader=FALSE,$DataName=-1)
- AddPoint($Value,$Serie="Serie1",$Description="")
- Series manipulation methods :
- AddSerie($SerieName="Serie1")
- AddAllSeries()
- RemoveSerie($SerieName="Serie1")
- SetAbsciseLabelSerie($SerieName = "Name")
- SetSerieName($Name,$SerieName="Serie1")
- + SetSerieSymbol($Name,$Symbol)
- SetXAxisName($Name="X Axis")
- SetYAxisName($Name="Y Axis")
- SetXAxisFormat($Format="number")
- SetYAxisFormat($Format="number")
- SetXAxisUnit($Unit="")
- SetYAxisUnit($Unit="")
- removeSerieName($SerieName)
- removeAllSeries()
- Data retrieval methods :
- GetData()
- GetDataDescription()
- */
-
- /* pData class definition */
- class pData
- {
- var $Data;
- var $DataDescription;
-
- function pData()
- {
- $this->Data = array();
- $this->DataDescription = "";
- $this->DataDescription["Position"] = "Name";
- $this->DataDescription["Format"]["X"] = "number";
- $this->DataDescription["Format"]["Y"] = "number";
- $this->DataDescription["Unit"]["X"] = NULL;
- $this->DataDescription["Unit"]["Y"] = NULL;
- }
-
- function ImportFromCSV($FileName,$Delimiter=",",$DataColumns=-1,$HasHeader=FALSE,$DataName=-1)
- {
- $handle = @fopen($FileName,"r");
- if ($handle)
- {
- $HeaderParsed = FALSE;
- while (!feof($handle))
- {
- $buffer = fgets($handle, 4096);
- $buffer = str_replace(chr(10),"",$buffer);
- $buffer = str_replace(chr(13),"",$buffer);
- $Values = split($Delimiter,$buffer);
-
- if ( $buffer != "" )
- {
- if ( $HasHeader == TRUE && $HeaderParsed == FALSE )
- {
- if ( $DataColumns == -1 )
- {
- $ID = 1;
- foreach($Values as $key => $Value)
- { $this->SetSerieName($Value,"Serie".$ID); $ID++; }
- }
- else
- {
- $SerieName = "";
-
- foreach($DataColumns as $key => $Value)
- $this->SetSerieName($Values[$Value],"Serie".$Value);
- }
- $HeaderParsed = TRUE;
- }
- else
- {
- if ( $DataColumns == -1 )
- {
- $ID = 1;
- foreach($Values as $key => $Value)
- { $this->AddPoint(intval($Value),"Serie".$ID); $ID++; }
- }
- else
- {
- $SerieName = "";
- if ( $DataName != -1 )
- $SerieName = $Values[$DataName];
-
- foreach($DataColumns as $key => $Value)
- $this->AddPoint($Values[$Value],"Serie".$Value,$SerieName);
- }
- }
- }
- }
- fclose($handle);
- }
- }
-
- function AddPoint($Value,$Serie="Serie1",$Description="")
- {
- if (is_array($Value) && count($Value) == 1)
- $Value = array_pop($Value);
-
- $ID = 0;
- for($i=0;$i<=count($this->Data);$i++)
- { if(isset($this->Data[$i][$Serie])) { $ID = $i+1; } }
-
- if ( count($Value) == 1 )
- {
- $this->Data[$ID][$Serie] = $Value;
- if ( $Description != "" )
- $this->Data[$ID]["Name"] = $Description;
- elseif (!isset($this->Data[$ID]["Name"]))
- $this->Data[$ID]["Name"] = $ID;
- }
- else
- {
- foreach($Value as $key => $Val)
- {
- $this->Data[$ID][$Serie] = $Val;
- if (!isset($this->Data[$ID]["Name"]))
- $this->Data[$ID]["Name"] = $ID;
- $ID++;
- }
- }
- }
-
- function AddSerie($SerieName="Serie1")
- {
- if ( !isset($this->DataDescription["Values"]) )
- {
- $this->DataDescription["Values"][] = $SerieName;
- }
- else
- {
- $Found = FALSE;
- foreach($this->DataDescription["Values"] as $key => $Value )
- if ( $Value == $SerieName ) { $Found = TRUE; }
-
- if ( !$Found )
- $this->DataDescription["Values"][] = $SerieName;
- }
- }
-
- function AddAllSeries()
- {
- unset($this->DataDescription["Values"]);
-
- if ( isset($this->Data[0]) )
- {
- foreach($this->Data[0] as $Key => $Value)
- {
- if ( $Key != "Name" )
- $this->DataDescription["Values"][] = $Key;
- }
- }
- }
-
- function RemoveSerie($SerieName="Serie1")
- {
- if ( !isset($this->DataDescription["Values"]) )
- return(0);
-
- $Found = FALSE;
- foreach($this->DataDescription["Values"] as $key => $Value )
- {
- if ( $Value == $SerieName )
- unset($this->DataDescription["Values"][$key]);
- }
- }
-
- function SetAbsciseLabelSerie($SerieName = "Name")
- {
- $this->DataDescription["Position"] = $SerieName;
- }
-
- function SetSerieName($Name,$SerieName="Serie1")
- {
- $this->DataDescription["Description"][$SerieName] = $Name;
- }
-
- function SetXAxisName($Name="X Axis")
- {
- $this->DataDescription["Axis"]["X"] = $Name;
- }
-
- function SetYAxisName($Name="Y Axis")
- {
- $this->DataDescription["Axis"]["Y"] = $Name;
- }
-
- function SetXAxisFormat($Format="number")
- {
- $this->DataDescription["Format"]["X"] = $Format;
- }
-
- function SetYAxisFormat($Format="number")
- {
- $this->DataDescription["Format"]["Y"] = $Format;
- }
-
- function SetXAxisUnit($Unit="")
- {
- $this->DataDescription["Unit"]["X"] = $Unit;
- }
-
- function SetYAxisUnit($Unit="")
- {
- $this->DataDescription["Unit"]["Y"] = $Unit;
- }
-
- function SetSerieSymbol($Name,$Symbol)
- {
- $this->DataDescription["Symbol"][$Name] = $Symbol;
- }
-
- function removeSerieName($SerieName)
- {
- if ( isset($this->DataDescription["Description"][$SerieName]) )
- unset($this->DataDescription["Description"][$SerieName]);
- }
-
- function removeAllSeries()
- {
- foreach($this->DataDescription["Values"] as $Key => $Value)
- unset($this->DataDescription["Values"][$Key]);
- }
-
- function GetData()
- {
- return($this->Data);
- }
-
- function GetDataDescription()
- {
- return($this->DataDescription);
- }
- }
-?>
\ No newline at end of file
diff --git a/libraries/chart/pma_chart.php b/libraries/chart/pma_chart.php
deleted file mode 100644
index 6708804..0000000
--- a/libraries/chart/pma_chart.php
+++ /dev/null
@@ -1,183 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * Holds the base class that all charts inherit from and some widely used
- * constants.
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-define('RED', 0);
-define('GREEN', 1);
-define('BLUE', 2);
-
-/**
- * The base class that all charts inherit from.
- * @abstract
- * @package phpMyAdmin
- */
-abstract class PMA_chart
-{
- /**
- * @var array All the default settigs values are here.
- */
- protected $settings = array(
-
- // Default title for every chart.
- 'titleText' => 'Chart',
-
- // The style of the chart title.
- 'titleColor' => '#FAFAFA',
-
- // Colors for the different slices in the pie chart.
- 'colors' => array(
- '#BCE02E',
- '#E0642E',
- '#E0D62E',
- '#2E97E0',
- '#B02EE0',
- '#E02E75',
- '#5CE02E',
- '#E0B02E',
- '#000000',
- '#0022E0',
- '#726CB1',
- '#481A36',
- '#BAC658',
- '#127224',
- '#825119',
- '#238C74',
- '#4C489B',
- '#87C9BF',
- ),
-
- // Chart background color.
- 'bgColor' => '#84AD83',
-
- // The width of the chart.
- 'width' => 520,
-
- // The height of the chart.
- 'height' => 325,
-
- // Default X Axis label. If empty, label will be taken from the data.
- 'xLabel' => '',
-
- // Default Y Axis label. If empty, label will be taken from the data.
- 'yLabel' => '',
- );
-
- /**
- * @var array Options that the user has specified
- */
- private $userSpecifiedSettings = null;
-
- /**
- * @var array Error codes will be stored here
- */
- protected $errors = array();
-
- /**
- * Store user specified options
- * @param array $options users specified options
- */
- function __construct($options = null)
- {
- $this->userSpecifiedSettings = $options;
- }
-
- /**
- * All the variable initialization has to be done here.
- */
- protected function init()
- {
- $this->handleOptions();
- }
-
- /**
- * A function which handles passed parameters. Useful if desired
- * chart needs to be a little bit different from the default one.
- */
- private function handleOptions()
- {
- if (is_null($this->userSpecifiedSettings)) {
- return;
- }
-
- $this->settings = array_merge($this->settings, $this->userSpecifiedSettings);
- }
-
- protected function getTitleText()
- {
- return $this->settings['titleText'];
- }
-
- protected function getTitleColor($component)
- {
- return $this->hexStrToDecComp($this->settings['titleColor'], $component);
- }
-
- protected function getColors()
- {
- return $this->settings['colors'];
- }
-
- protected function getWidth()
- {
- return $this->settings['width'];
- }
-
- protected function getHeight()
- {
- return $this->settings['height'];
- }
-
- protected function getBgColor($component)
- {
- return $this->hexStrToDecComp($this->settings['bgColor'], $component);
- }
-
- protected function setXLabel($label)
- {
- $this->settings['xLabel'] = $label;
- }
-
- protected function getXLabel()
- {
- return $this->settings['xLabel'];
- }
-
- protected function setYLabel($label)
- {
- $this->settings['yLabel'] = $label;
- }
-
- protected function getYLabel()
- {
- return $this->settings['yLabel'];
- }
-
- public function getSettings()
- {
- return $this->settings;
- }
-
- public function getErrors()
- {
- return $this->errors;
- }
-
- /**
- * Get one the dec color component from the hex color string
- * @param string $colorString color string, i.e. #5F22A99
- * @param int $component color component to get, i.e. 0 gets red.
- */
- protected function hexStrToDecComp($colorString, $component)
- {
- return hexdec(substr($colorString, ($component * 2) + 1, 2));
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_chart.php b/libraries/chart/pma_pchart_chart.php
deleted file mode 100644
index e68a9b3..0000000
--- a/libraries/chart/pma_pchart_chart.php
+++ /dev/null
@@ -1,402 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * Holds the base class that all charts using pChart inherit from and some
- * widely used constants
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-define('TOP', 0);
-define('RIGHT', 1);
-define('BOTTOM', 2);
-define('LEFT', 3);
-
-require_once 'pma_chart.php';
-
-require_once 'pChart/pData.class';
-require_once 'pChart/pChart.class';
-
-/**
- * Base class for every chart implemented using pChart.
- * @abstract
- * @package phpMyAdmin
- */
-abstract class PMA_pChart_chart extends PMA_chart
-{
- /**
- * @var String title text
- */
- protected $titleText;
-
- /**
- * @var array data for the chart
- */
- protected $data;
-
- /**
- * @var object pData object that holds the description of the data
- */
- protected $dataSet;
-
- /**
- * @var object pChart object that holds the chart
- */
- protected $chart;
-
- /**
- * @var array holds base64 encoded chart image parts
- */
- protected $partsEncoded = array();
-
- public function __construct($data, $options = null)
- {
- parent::__construct($options);
-
- $this->data = $data;
-
- $this->settings['fontPath'] = './libraries/chart/pChart/fonts/';
-
- $this->settings['scale'] = SCALE_ADDALLSTART0;
-
- $this->settings['labelHeight'] = 20;
-
- $this->settings['fontSize'] = 8;
-
- $this->settings['continuous'] = 'off';
-
- // as in CSS (top, right, bottom, left)
- $this->setAreaMargins(array(20, 20, 40, 60));
-
- // Get color settings from theme
- $this->settings = array_merge($this->settings,$GLOBALS['cfg']['chartColor']);
- }
-
- protected function init()
- {
- parent::init();
-
- // create pChart object
- $this->chart = new pChart($this->getWidth(), $this->getHeight());
-
- // create pData object
- $this->dataSet = new pData;
-
- $this->chart->reportWarnings('GD');
- $this->chart->ErrorFontName = $this->getFontPath().'DejaVuSans.ttf';
-
- // initialize colors
- foreach ($this->getColors() as $key => $color) {
- $this->chart->setColorPalette(
- $key,
- hexdec(substr($color, 1, 2)),
- hexdec(substr($color, 3, 2)),
- hexdec(substr($color, 5, 2))
- );
- }
-
- $this->chart->setFontProperties($this->getFontPath().'DejaVuSans.ttf', $this->getFontSize());
-
- $this->chart->setImageMap(true, 'mapid');
- }
-
- /**
- * data is put to the $dataSet object according to what type chart is
- * @abstract
- */
- abstract protected function prepareDataSet();
-
- /**
- * all components of the chart are drawn
- */
- protected function prepareChart()
- {
- $this->drawBackground();
- $this->drawChart();
- }
-
- /**
- * draws the background
- */
- protected function drawBackground()
- {
- $this->drawCommon();
- $this->drawTitle();
- $this->setGraphAreaDimensions();
- $this->drawGraphArea();
- }
-
- /**
- * draws the part of the background which is common to most of the charts
- */
- protected function drawCommon()
- {
- $this->chart->drawGraphAreaGradient(
- $this->getBgColor(RED),
- $this->getBgColor(GREEN),
- $this->getBgColor(BLUE),
- // With a gradientIntensity of 0 the background does't draw, oddly
- ($this->settings['gradientIntensity']==0)?1:$this->settings['gradientIntensity'],TARGET_BACKGROUND);
-
- if(is_string($this->settings['border']))
- $this->chart->addBorder(1,$this->getBorderColor(RED),$this->getBorderColor(GREEN),$this->getBorderColor(BLUE));
- }
-
- /**
- * draws the chart title
- */
- protected function drawTitle()
- {
- // Draw the title
- $this->chart->drawTextBox(
- 0,
- 0,
- $this->getWidth(),
- $this->getLabelHeight(),
- $this->getTitleText(),
- 0,
- $this->getTitleColor(RED),
- $this->getTitleColor(GREEN),
- $this->getTitleColor(BLUE),
- ALIGN_CENTER,
- false,
- $this->getTitleBgColor(RED),
- $this->getTitleBgColor(GREEN),
- $this->getTitleBgColor(BLUE)
- );
- }
-
- /**
- * calculates and sets the dimensions that will be used for the actual graph
- */
- protected function setGraphAreaDimensions()
- {
- $this->chart->setGraphArea(
- $this->getAreaMargin(LEFT),
- $this->getLabelHeight() + $this->getAreaMargin(TOP),
- $this->getWidth() - $this->getAreaMargin(RIGHT),
- $this->getHeight() - $this->getAreaMargin(BOTTOM)
- );
- }
-
- /**
- * draws graph area (the area where all bars, lines, points will be seen)
- */
- protected function drawGraphArea()
- {
- $this->chart->drawGraphArea(
- $this->getGraphAreaColor(RED),
- $this->getGraphAreaColor(GREEN),
- $this->getGraphAreaColor(BLUE),
- false
- );
- $this->chart->drawScale(
- $this->dataSet->GetData(),
- $this->dataSet->GetDataDescription(),
- $this->getScale(),
- $this->getScaleColor(RED),
- $this->getScaleColor(GREEN),
- $this->getScaleColor(BLUE),
- true,0,2,true
- );
-
- if($this->settings['gradientIntensity']>0)
- $this->chart->drawGraphAreaGradient(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE),
- $this->settings['gradientIntensity']
- );
- else
- $this->chart->drawGraphArea(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE)
- );
-
- $this->chart->drawGrid(
- 4,
- true,
- $this->getGridColor(RED),
- $this->getGridColor(GREEN),
- $this->getGridColor(BLUE),
- 20
- );
- }
-
- /**
- * draws the chart
- * @abstract
- */
- protected abstract function drawChart();
-
- /**
- * Renders the chart, base 64 encodes the output and puts it into
- * array partsEncoded.
- *
- * Parameter can be used to slice the chart vertically into parts. This
- * solves an issue where some browsers (IE8) accept base64 images only up
- * to some length.
- *
- * @param integer $parts number of parts to render.
- * Default value 1 means that all the
- * chart will be in one piece.
- */
- protected function render($parts = 1)
- {
- $fullWidth = 0;
-
- for ($i = 0; $i < $parts; $i++) {
-
- // slicing is vertical so part height is the full height
- $partHeight = $this->chart->YSize;
-
- // there will be some rounding erros, will compensate later
- $partWidth = round($this->chart->XSize / $parts);
- $fullWidth += $partWidth;
- $partX = $partWidth * $i;
-
- if ($i == $parts - 1) {
- // if this is the last part, compensate for the rounding errors
- $partWidth += $this->chart->XSize - $fullWidth;
- }
-
- // get a part from the full chart image
- $part = imagecreatetruecolor($partWidth, $partHeight);
- imagecopy($part, $this->chart->Picture, 0, 0, $partX, 0, $partWidth, $partHeight);
-
- // render part and save it to variable
- ob_start();
- imagepng($part, NULL, 9, PNG_ALL_FILTERS);
- $output = ob_get_contents();
- ob_end_clean();
-
- // base64 encode the current part
- $partEncoded = base64_encode($output);
- $this->partsEncoded[$i] = $partEncoded;
- }
- }
-
- /**
- * get the HTML and JS code for the configured chart
- * @return string HTML and JS code for the chart
- */
- public function toString()
- {
- if (!function_exists('gd_info')) {
- array_push($this->errors, ERR_NO_GD);
- return '';
- }
-
- $this->init();
- $this->prepareDataSet();
- $this->prepareChart();
-
- //$this->chart->debugImageMap();
- //$this->chart->printErrors('GD');
-
- // check if a user wanted a chart in one part
- if ($this->isContinuous()) {
- $this->render(1);
- }
- else {
- $this->render(20);
- }
-
- $returnData = '<div id="chart">';
- foreach ($this->partsEncoded as $part) {
- $returnData .= '<img src="data:image/png;base64,'.$part.'" />';
- }
- $returnData .= '</div>';
-
- // add tooltips only if json is available
- if (function_exists('json_encode')) {
- $returnData .= '
- <script type="text/javascript">
- //<![CDATA[
- imageMap.loadImageMap(\''.json_encode($this->getImageMap()).'\');
- //]]>
- </script>
- ';
- }
- else {
- array_push($this->errors, ERR_NO_JSON);
- }
-
- return $returnData;
- }
-
- protected function getLabelHeight()
- {
- return $this->settings['labelHeight'];
- }
-
- protected function setAreaMargins($areaMargins)
- {
- $this->settings['areaMargins'] = $areaMargins;
- }
-
- protected function getAreaMargin($side)
- {
- return $this->settings['areaMargins'][$side];
- }
-
- protected function getFontPath()
- {
- return $this->settings['fontPath'];
- }
-
- protected function getScale()
- {
- return $this->settings['scale'];
- }
-
- protected function getFontSize()
- {
- return $this->settings['fontSize'];
- }
-
- protected function isContinuous()
- {
- return $this->settings['continuous'] == 'on';
- }
-
- protected function getImageMap()
- {
- return $this->chart->getImageMap();
- }
-
- protected function getGraphAreaColor($component)
- {
- return $this->hexStrToDecComp($this->settings['graphAreaColor'], $component);
- }
-
- protected function getGraphAreaGradientColor($component)
- {
- return $this->hexStrToDecComp($this->settings['graphAreaGradientColor'], $component);
- }
-
- protected function getGridColor($component)
- {
- return $this->hexStrToDecComp($this->settings['gridColor'], $component);
- }
-
- protected function getScaleColor($component)
- {
- return $this->hexStrToDecComp($this->settings['scaleColor'], $component);
- }
-
- protected function getTitleBgColor($component)
- {
- return $this->hexStrToDecComp($this->settings['titleBgColor'], $component);
- }
-
- protected function getBorderColor($component)
- {
- return $this->hexStrToDecComp($this->settings['border'], $component);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_multi.php b/libraries/chart/pma_pchart_multi.php
deleted file mode 100644
index 171cc50..0000000
--- a/libraries/chart/pma_pchart_multi.php
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_chart.php';
-
-/**
- * Base class for every chart that uses multiple series.
- * All of these charts will require legend box.
- * @abstract
- * @package phpMyAdmin
- */
-abstract class PMA_pChart_multi extends PMA_pChart_chart
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
-
- // as in CSS (top, right, bottom, left)
- $this->setLegendMargins(array(20, 10, 0, 0));
- }
-
- /**
- * data set preparation for multi serie graphs
- */
- protected function prepareDataSet()
- {
- $values = array_values($this->data);
- $keys = array_keys($this->data);
-
- // Dataset definition
- $this->dataSet->AddPoint($values[0], "Keys");
-
- $i = 0;
- foreach ($values[1] as $seriesName => $seriesData) {
- $this->dataSet->AddPoint($seriesData, "Values".$i);
- $this->dataSet->SetSerieName($seriesName, "Values".$i);
- $i++;
- }
- $this->dataSet->AddAllSeries();
-
- $this->dataSet->RemoveSerie("Keys");
- $this->dataSet->SetAbsciseLabelSerie("Keys");
-
- $xLabel = $this->getXLabel();
- if (empty($xLabel)) {
- $this->setXLabel($keys[0]);
- }
- $yLabel = $this->getYLabel();
- if (empty($yLabel)) {
- $this->setYLabel($keys[1]);
- }
-
- $this->dataSet->SetXAxisName($this->getXLabel());
- $this->dataSet->SetYAxisName($this->getYLabel());
- }
-
- /**
- * set graph area dimensions with respect to legend box size
- */
- protected function setGraphAreaDimensions()
- {
- $this->chart->setGraphArea(
- $this->getAreaMargin(LEFT),
- $this->getLabelHeight() + $this->getAreaMargin(TOP),
- $this->getWidth() - $this->getAreaMargin(RIGHT) - $this->getLegendBoxWidth() - $this->getLegendMargin(LEFT) - $this->getLegendMargin(RIGHT),
- $this->getHeight() - $this->getAreaMargin(BOTTOM)
- );
- }
-
- /**
- * multi serie charts need a legend. draw it
- */
- protected function drawChart()
- {
- $this->drawLegend();
- }
-
- /**
- * draws a legend
- */
- protected function drawLegend()
- {
- // Draw the legend
- $this->chart->drawLegend(
- $this->getWidth() - $this->getLegendMargin(RIGHT) - $this->getLegendBoxWidth(),
- $this->getLabelHeight() + $this->getLegendMargin(TOP),
- $this->dataSet->GetDataDescription(),
- 250,250,250,50,50,50
- );
- }
-
- protected function setLegendMargins($legendMargins)
- {
- if (!isset($this->settings['legendMargins'])) {
- $this->settings['legendMargins'] = $legendMargins;
- }
- }
-
- protected function getLegendMargin($side)
- {
- return $this->settings['legendMargins'][$side];
- }
-
- protected function getLegendBoxWidth()
- {
- $legendSize = $this->chart->getLegendBoxSize($this->dataSet->GetDataDescription());
- return $legendSize[0];
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_multi_bar.php b/libraries/chart/pma_pchart_multi_bar.php
deleted file mode 100644
index 619ef1a..0000000
--- a/libraries/chart/pma_pchart_multi_bar.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_multi.php';
-
-/**
- * implements multi bar chart
- * @package phpMyAdmin
- */
-class PMA_pChart_multi_bar extends PMA_pChart_multi
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
-
- $this->settings['scale'] = SCALE_NORMAL;
- }
-
- /**
- * draws multi bar graph
- */
- protected function drawChart()
- {
- parent::drawChart();
-
- // Draw the bar chart
- $this->chart->drawBarGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 70);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_multi_line.php b/libraries/chart/pma_pchart_multi_line.php
deleted file mode 100644
index 502f386..0000000
--- a/libraries/chart/pma_pchart_multi_line.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_multi.php';
-
-/**
- * implements multi line chart
- * @package phpMyAdmin
- */
-class PMA_pChart_multi_line extends PMA_pChart_multi
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
-
- $this->settings['scale'] = SCALE_NORMAL;
- }
-
- /**
- * draws multi line chart
- */
- protected function drawChart()
- {
- parent::drawChart();
-
- // Draw the bar chart
- $this->chart->drawLineGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription());
- $this->chart->drawPlotGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 3, 1, -1, -1, -1, true);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_multi_radar.php b/libraries/chart/pma_pchart_multi_radar.php
deleted file mode 100644
index 8c32cb3..0000000
--- a/libraries/chart/pma_pchart_multi_radar.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_multi.php';
-
-/**
- * implements multi radar chart
- * @package phpMyAdmin
- */
-class PMA_pChart_multi_radar extends PMA_pChart_multi
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
-
- $this->normalizeValues();
- }
-
- /**
- * Get the largest value from the data and normalize all the other values.
- */
- private function normalizeValues()
- {
- $maxValue = 0;
- $keys = array_keys($this->data);
- $valueKey = $keys[1];
-
- // get the max value
- foreach ($this->data[$valueKey] as $values) {
- if (max($values) > $maxValue) {
- $maxValue = max($values);
- }
- }
-
- // normalize all the values according to the max value
- foreach ($this->data[$valueKey] as &$values) {
- foreach ($values as &$value) {
- $value = $value / $maxValue * 10;
- }
- }
- }
-
- /**
- * graph area for the radar chart does not include grid lines
- */
- protected function drawGraphArea()
- {
- $this->chart->drawGraphArea(
- $this->getGraphAreaColor(RED),
- $this->getGraphAreaColor(GREEN),
- $this->getGraphAreaColor(BLUE),
- false
- );
-
- if($this->settings['gradientIntensity']>0)
- $this->chart->drawGraphAreaGradient(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE),
- $this->settings['gradientIntensity']
- );
- else
- $this->chart->drawGraphArea(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE)
- );
- }
-
- /**
- * draw multi radar chart
- */
- protected function drawChart()
- {
- parent::drawChart();
-
- // when drawing radar graph we can specify the border from the top of
- // graph area. We want border to be dynamic, so that either the top
- // or the side of the radar is some distance away from the top or the
- // side of the graph area.
- $areaWidth = $this->chart->GArea_X2 - $this->chart->GArea_X1;
- $areaHeight = $this->chart->GArea_Y2 - $this->chart->GArea_Y1;
-
- if ($areaHeight > $areaWidth) {
- $borderOffset = ($areaHeight - $areaWidth) / 2;
- }
- else {
- $borderOffset = 0;
- }
-
- // the least ammount that radar is away from the graph area side.
- $borderOffset += 40;
-
- // Draw the radar chart
- $this->chart->drawRadarAxis($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), true, $borderOffset,
- 120, 120, 120, 230, 230, 230, -1, 2);
- $this->chart->drawFilledRadar($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 50, $borderOffset);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_pie.php b/libraries/chart/pma_pchart_pie.php
deleted file mode 100644
index 56148e0..0000000
--- a/libraries/chart/pma_pchart_pie.php
+++ /dev/null
@@ -1,109 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_multi.php';
-
-/**
- * implements pie chart
- * @package phpMyAdmin
- */
-class PMA_pChart_Pie extends PMA_pChart_multi
-{
- public function __construct($data, $options = null)
- {
- // limit data size, no more than 18 pie slices
- $data = array_slice($data, 0, 18, true);
- parent::__construct($data, $options);
-
- $this->setAreaMargins(array(20, 10, 20, 20));
- }
-
- /**
- * prepare data set for the pie chart
- */
- protected function prepareDataSet()
- {
- // Dataset definition
- $this->dataSet->AddPoint(array_values($this->data), "Values");
- $this->dataSet->AddPoint(array_keys($this->data), "Keys");
- $this->dataSet->AddAllSeries();
- $this->dataSet->SetAbsciseLabelSerie("Keys");
- }
-
- /**
- * graph area for the pie chart does not include grid lines
- */
- protected function drawGraphArea()
- {
- $this->chart->drawGraphArea(
- $this->getGraphAreaColor(RED),
- $this->getGraphAreaColor(GREEN),
- $this->getGraphAreaColor(BLUE),
- false
- );
-
- if($this->settings['gradientIntensity']>0)
- $this->chart->drawGraphAreaGradient(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE),
- $this->settings['gradientIntensity']
- );
- else
- $this->chart->drawGraphArea(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE)
- );
-
- }
-
- /**
- * draw the pie chart
- */
- protected function drawChart()
- {
- parent::drawChart();
-
- // draw pie chart in the middle of graph area
- $middleX = ($this->chart->GArea_X1 + $this->chart->GArea_X2) / 2;
- $middleY = ($this->chart->GArea_Y1 + $this->chart->GArea_Y2) / 2;
-
- $this->chart->drawPieGraph(
- $this->dataSet->GetData(),
- $this->dataSet->GetDataDescription(),
- $middleX,
- // pie graph is skewed. Upper part is shorter than the
- // lower part. This is why we set an offset to the
- // Y middle coordiantes.
- $middleY - 15,
- 120, PIE_PERCENTAGE, false, 60, 30, 10, 1);
- }
-
- /**
- * draw legend for the pie chart
- */
- protected function drawLegend()
- {
- $this->chart->drawPieLegend(
- $this->getWidth() - $this->getLegendMargin(RIGHT) - $this->getLegendBoxWidth(),
- $this->getLabelHeight() + $this->getLegendMargin(TOP),
- $this->dataSet->GetData(),
- $this->dataSet->GetDataDescription(),
- 250, 250, 250);
- }
-
- protected function getLegendBoxWidth()
- {
- $legendSize = $this->chart->getPieLegendBoxSize($this->dataSet->GetData());
- return $legendSize[0];
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_single.php b/libraries/chart/pma_pchart_single.php
deleted file mode 100644
index 6579a9b..0000000
--- a/libraries/chart/pma_pchart_single.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_chart.php';
-
-/**
- * Base class for every chart that uses only one series.
- * @abstract
- * @package phpMyAdmin
- */
-abstract class PMA_pChart_single extends PMA_pChart_chart
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
- }
-
- /**
- * data set preparation for single serie charts
- */
- protected function prepareDataSet()
- {
- $values = array_values($this->data);
- $keys = array_keys($this->data);
-
- // Dataset definition
- $this->dataSet->AddPoint($values[0], "Values");
- $this->dataSet->AddPoint($values[1], "Keys");
-
- //$this->dataSet->AddAllSeries();
- $this->dataSet->AddSerie("Values");
-
- $this->dataSet->SetAbsciseLabelSerie("Keys");
-
- $yLabel = $this->getYLabel();
- if (empty($yLabel)) {
- $this->setYLabel($keys[0]);
- }
- $xLabel = $this->getXLabel();
- if (empty($xLabel)) {
- $this->setXLabel($keys[1]);
- }
-
- $this->dataSet->SetXAxisName($this->getXLabel());
- $this->dataSet->SetYAxisName($this->getYLabel());
- $this->dataSet->SetSerieName($this->getYLabel(), "Values");
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_single_bar.php b/libraries/chart/pma_pchart_single_bar.php
deleted file mode 100644
index e651970..0000000
--- a/libraries/chart/pma_pchart_single_bar.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_single.php';
-
-/**
- * implements single bar chart
- * @package phpMyAdmin
- */
-class PMA_pChart_single_bar extends PMA_pChart_single
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
- }
-
- /**
- * draws single bar chart
- */
- protected function drawChart()
- {
- // Draw the bar chart
- // use stacked bar graph function, because it gives bars with alpha
- $this->chart->drawStackedBarGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 70);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_single_line.php b/libraries/chart/pma_pchart_single_line.php
deleted file mode 100644
index 3003581..0000000
--- a/libraries/chart/pma_pchart_single_line.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_single.php';
-
-/**
- * implements single line chart
- * @package phpMyAdmin
- */
-class PMA_pChart_single_line extends PMA_pChart_single
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
- }
-
- /**
- * draws single line chart
- */
- protected function drawChart()
- {
- // Draw the line chart
- $this->chart->drawLineGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription());
- $this->chart->drawPlotGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 3, 1, -1, -1, -1, true);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_single_radar.php b/libraries/chart/pma_pchart_single_radar.php
deleted file mode 100644
index 0c5d9ec..0000000
--- a/libraries/chart/pma_pchart_single_radar.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_single.php';
-
-/**
- * implements single radar chart
- * @package phpMyAdmin
- */
-class PMA_pChart_single_radar extends PMA_pChart_single
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
-
- $this->normalizeValues();
- }
-
- /**
- * Get the largest value from the data and normalize all the other values.
- */
- private function normalizeValues()
- {
- $maxValue = 0;
- $keys = array_keys($this->data);
- $valueKey = $keys[0];
- $maxValue = max($this->data[$valueKey]);
-
- foreach ($this->data[$valueKey] as &$value) {
- $value = $value / $maxValue * 10;
- }
- }
-
- /**
- * graph area for the radar chart does not include grid lines
- */
- protected function drawGraphArea()
- {
- $this->chart->drawGraphArea(
- $this->getGraphAreaColor(RED),
- $this->getGraphAreaColor(GREEN),
- $this->getGraphAreaColor(BLUE),
- false
- );
-
- if($this->settings['gradientIntensity']>0)
- $this->chart->drawGraphAreaGradient(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE),
- $this->settings['gradientIntensity']
- );
- else
- $this->chart->drawGraphArea(
- $this->getGraphAreaGradientColor(RED),
- $this->getGraphAreaGradientColor(GREEN),
- $this->getGraphAreaGradientColor(BLUE)
- );
-
- }
-
- /**
- * draws the radar chart
- */
- protected function drawChart()
- {
- // when drawing radar graph we can specify the border from the top of
- // graph area. We want border to be dynamic, so that either the top
- // or the side of the radar is some distance away from the top or the
- // side of the graph area.
- $areaWidth = $this->chart->GArea_X2 - $this->chart->GArea_X1;
- $areaHeight = $this->chart->GArea_Y2 - $this->chart->GArea_Y1;
-
- if ($areaHeight > $areaWidth) {
- $borderOffset = ($areaHeight - $areaWidth) / 2;
- }
- else {
- $borderOffset = 0;
- }
-
- // the least ammount that radar is away from the graph area side.
- $borderOffset += 40;
-
- $this->chart->drawRadarAxis($this->dataSet->GetData(), $this->dataSet->GetDataDescription(),
- true, $borderOffset, 120, 120, 120, 230, 230, 230, -1, 2);
- $this->chart->drawFilledRadar($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 50, $borderOffset);
- }
-}
-
-?>
diff --git a/libraries/chart/pma_pchart_stacked_bar.php b/libraries/chart/pma_pchart_stacked_bar.php
deleted file mode 100644
index 0ef72ec..0000000
--- a/libraries/chart/pma_pchart_stacked_bar.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once 'pma_pchart_multi.php';
-
-/**
- * implements stacked bar chart
- * @package phpMyAdmin
- */
-class PMA_pChart_stacked_bar extends PMA_pChart_multi
-{
- public function __construct($data, $options = null)
- {
- parent::__construct($data, $options);
- }
-
- /**
- * draws stacked bar chart
- */
- protected function drawChart()
- {
- parent::drawChart();
-
- // Draw the bar chart
- $this->chart->drawStackedBarGraph($this->dataSet->GetData(), $this->dataSet->GetDataDescription(), 70);
- }
-}
-
-?>
diff --git a/libraries/common.lib.php b/libraries/common.lib.php
index 999174b..037656e 100644
--- a/libraries/common.lib.php
+++ b/libraries/common.lib.php
@@ -1303,43 +1303,6 @@ function PMA_profilingCheckbox($sql_query)
}
/**
- * Displays the results of SHOW PROFILE
- *
- * @param array the results
- * @param boolean show chart
- * @access public
- *
- */
-function PMA_profilingResults($profiling_results, $show_chart = false)
-{
- echo '<fieldset><legend>' . __('Profiling') . '</legend>' . "\n";
- echo '<div style="float: left;">';
- echo '<table>' . "\n";
- echo ' <tr>' . "\n";
- echo ' <th>' . __('Status') . '</th>' . "\n";
- echo ' <th>' . __('Time') . '</th>' . "\n";
- echo ' </tr>' . "\n";
-
- foreach($profiling_results as $one_result) {
- echo ' <tr>' . "\n";
- echo '<td>' . $one_result['Status'] . '</td>' . "\n";
- echo '<td>' . $one_result['Duration'] . '</td>' . "\n";
- }
-
- echo '</table>' . "\n";
- echo '</div>';
-
- if ($show_chart) {
- require_once './libraries/chart.lib.php';
- echo '<div style="float: left;">';
- PMA_chart_profiling($profiling_results);
- echo '</div>';
- }
-
- echo '</fieldset>' . "\n";
-}
-
-/**
* Formats $value to byte view
*
* @param double $value the value to format
@@ -1402,7 +1365,6 @@ function PMA_localizeNumber($value)
/**
* Formats $value to the given length and appends SI prefixes
- * $comma is not substracted from the length
* with a $length of 0 no truncation occurs, number is only formated
* to the current locale
*
@@ -1416,10 +1378,11 @@ function PMA_localizeNumber($value)
* echo PMA_formatNumber(0, 6); // 0
*
* </code>
- * @param double $value the value to format
- * @param integer $length the max length
- * @param integer $comma the number of decimals to retain
- * @param boolean $only_down do not reformat numbers below 1
+ * @param double $value the value to format
+ * @param integer $digits_left number of digits left of the comma
+ * @param integer $digits_right number of digits right of the comma
+ * @param boolean $only_down do not reformat numbers below 1
+ * @param boolean $noTrailingZero removes trailing zeros right of the comma (default: true)
*
* @return string the formatted value and its unit
*
@@ -1427,13 +1390,15 @@ function PMA_localizeNumber($value)
*
* @version 1.1.0 - 2005-10-27
*/
-function PMA_formatNumber($value, $length = 3, $comma = 0, $only_down = false)
+function PMA_formatNumber($value, $digits_left = 3, $digits_right = 0, $only_down = false, $noTrailingZero = true)
{
+ if($value==0) return '0';
+
$originalValue = $value;
//number_format is not multibyte safe, str_replace is safe
- if ($length === 0) {
- $value = number_format($value, $comma);
- if($originalValue!=0 && floatval($value) == 0) $value = ' <'.(1/PMA_pow(10,$comma));
+ if ($digits_left === 0) {
+ $value = number_format($value, $digits_right);
+ if($originalValue!=0 && floatval($value) == 0) $value = ' <'.(1/PMA_pow(10,$digits_right));
return PMA_localizeNumber($value);
}
@@ -1459,11 +1424,6 @@ function PMA_formatNumber($value, $length = 3, $comma = 0, $only_down = false)
8 => 'Y'
);
- // we need at least 3 digits to be displayed
- if (3 > $length + $comma) {
- $length = 3 - $comma;
- }
-
// check for negative value to retain sign
if ($value < 0) {
$sign = '-';
@@ -1472,33 +1432,29 @@ function PMA_formatNumber($value, $length = 3, $comma = 0, $only_down = false)
$sign = '';
}
- $dh = PMA_pow(10, $comma);
- $li = PMA_pow(10, $length);
- $unit = $units[0];
-
- if ($value >= 1) {
- for ($d = 8; $d >= 0; $d--) {
- if (isset($units[$d]) && $value >= $li * PMA_pow(1000, $d-1)) {
- $value = round($value / (PMA_pow(1000, $d) / $dh)) /$dh;
- $unit = $units[$d];
- break 1;
- } // end if
- } // end for
- } elseif (!$only_down && (float) $value !== 0.0) {
- for ($d = -8; $d <= 8; $d++) {
- // force using pow() because of the negative exponent
- if (isset($units[$d]) && $value <= $li * PMA_pow(1000, $d-1, 'pow')) {
- $value = round($value / (PMA_pow(1000, $d, 'pow') / $dh)) /$dh;
- $unit = $units[$d];
- break 1;
- } // end if
- } // end for
- } // end if ($value >= 1) elseif (!$only_down && (float) $value !== 0.0)
-
- //number_format is not multibyte safe, str_replace is safe
- $value = PMA_localizeNumber(number_format($value, $comma));
+ $dh = PMA_pow(10, $digits_right);
+
+ // This gives us the right SI prefix already, but $digits_left parameter not incorporated
+ $d = floor(log10($value) / 3);
+ // Lowering the SI prefix by 1 gives us an additional 3 zeros
+ // So if we have 3,6,9,12.. free digits ($digits_left - $cur_digits) to use, then lower the SI prefix
+ $cur_digits = floor(log10($value / PMA_pow(1000, $d, 'pow'))+1);
+ if($digits_left > $cur_digits) {
+ $d-= floor(($digits_left - $cur_digits)/3);
+ }
+
+ if($d<0 && $only_down) $d=0;
+
+ $value = round($value / (PMA_pow(1000, $d, 'pow') / $dh)) /$dh;
+ $unit = $units[$d];
+
+ // If we dont want any zeros after the comma just add the thousand seperator
+ if($noTrailingZero)
+ $value = PMA_localizeNumber(preg_replace("/(?<=\d)(?=(\d{3})+(?!\d))/",",",$value));
+ else
+ $value = PMA_localizeNumber(number_format($value, $digits_right)); //number_format is not multibyte safe, str_replace is safe
- if($originalValue!=0 && floatval($value) == 0) return ' <'.(1/PMA_pow(10,$comma)).' '.$unit;
+ if($originalValue!=0 && floatval($value) == 0) return ' <'.(1/PMA_pow(10,$digits_right)).' '.$unit;
return $sign . $value . ' ' . $unit;
} // end of the 'PMA_formatNumber' function
diff --git a/libraries/server_links.inc.php b/libraries/server_links.inc.php
index 2c1ac6a..e8e2fc9 100644
--- a/libraries/server_links.inc.php
+++ b/libraries/server_links.inc.php
@@ -50,9 +50,9 @@ if (!$GLOBALS['is_ajax_request']) {
$tabs['status']['link'] = 'server_status.php';
$tabs['status']['text'] = __('Status');
- $tabs['process']['icon'] = 's_process.png';
+ /*$tabs['process']['icon'] = 's_process.png';
$tabs['process']['link'] = 'server_processlist.php';
- $tabs['process']['text'] = __('Processes');
+ $tabs['process']['text'] = __('Processes');*/
if ($is_superuser) {
$tabs['rights']['icon'] = 's_rights.png';
diff --git a/server_processlist.php b/server_processlist.php
deleted file mode 100644
index 78f0013..0000000
--- a/server_processlist.php
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/* vim: set expandtab sw=4 ts=4 sts=4: */
-/**
- *
- * @package phpMyAdmin
- */
-
-/**
- *
- */
-require_once './libraries/common.inc.php';
-require_once './libraries/server_common.inc.php';
-require './libraries/server_links.inc.php';
-
-
-/**
- * Displays the sub-page heading
- */
-echo '<h2>' . "\n"
- . ($GLOBALS['cfg']['MainPageIconic'] ? '<img src="' . $pmaThemeImage . 's_process.png" width="16" height="16" border="0" hspace="2" align="middle" alt="" />' : '')
- . ' ' . __('Processes') . "\n"
- . '</h2>' . "\n";
-
-/**
- * Kills a selected process
- */
-if (!empty($_REQUEST['kill'])) {
- if (PMA_DBI_try_query('KILL ' . $_REQUEST['kill'] . ';')) {
- $message = PMA_Message::success(__('Thread %s was successfully killed.'));
- } else {
- $message = PMA_Message::error(__('phpMyAdmin was unable to kill thread %s. It probably has already been closed.'));
- }
- $message->addParam($_REQUEST['kill']);
- $message->display();
-}
-
-$url_params = array();
-
-if (! empty($_REQUEST['full'])) {
- $sql_query = 'SHOW FULL PROCESSLIST';
- $url_params['full'] = 1;
- $full_text_link = 'server_processlist.php' . PMA_generate_common_url(array(), 'html', '?');
-} else {
- $sql_query = 'SHOW PROCESSLIST';
- $full_text_link = 'server_processlist.php' . PMA_generate_common_url(array('full' => 1));
-}
-$result = PMA_DBI_query($sql_query);
-
-/**
- * Displays the page
- */
-?>
-<table id="tableprocesslist" class="data">
-<thead>
-<tr>
- <?php if (!PMA_DRIZZLE): ?>
- <th><a href="<?php echo $full_text_link; ?>"
- title="<?php echo empty($full) ? __('Show Full Queries') : __('Truncate Shown Queries'); ?>">
- <img src="<?php echo $pmaThemeImage . 's_' . (empty($_REQUEST['full']) ? 'full' : 'partial'); ?>text.png"
- alt="<?php echo empty($_REQUEST['full']) ? __('Show Full Queries') : __('Truncate Shown Queries'); ?>" />
- </a></th>
- <?php else: ?>
- <th></th>
- <?php endif; ?>
- <th><?php echo __('ID'); ?></th>
- <th><?php echo __('User'); ?></th>
- <th><?php echo __('Host'); ?></th>
- <th><?php echo __('Database'); ?></th>
- <th><?php echo __('Command'); ?></th>
- <th><?php echo __('Time'); ?></th>
- <th><?php echo __('Status'); ?></th>
- <th><?php echo __('SQL query'); ?></th>
-</tr>
-</thead>
-<tbody>
-<?php
-$odd_row = true;
-while($process = PMA_DBI_fetch_assoc($result)) {
- if (PMA_DRIZZLE) {
- // Drizzle uses uppercase keys
- foreach ($process as $k => $v) {
- $k = $k !== 'DB'
- ? $k = ucfirst(strtolower($k))
- : 'db';
- $process[$k] = $v;
- }
- }
- $url_params['kill'] = $process['Id'];
- $kill_process = 'server_processlist.php' . PMA_generate_common_url($url_params);
- ?>
-<tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; ?>">
- <td><a href="<?php echo $kill_process ; ?>"><?php echo __('Kill'); ?></a></td>
- <td class="value"><?php echo $process['Id']; ?></td>
- <td><?php echo $process['User']; ?></td>
- <td><?php echo $process['Host']; ?></td>
- <td><?php echo ((! isset($process['db']) || ! strlen($process['db'])) ? '<i>' . __('None') . '</i>' : $process['db']); ?></td>
- <td><?php echo $process['Command']; ?></td>
- <td class="value"><?php echo $process['Time']; ?></td>
- <td><?php echo (empty($process['State']) ? '---' : $process['State']); ?></td>
- <td><?php echo (empty($process['Info']) ? '---' : PMA_SQP_formatHtml(PMA_SQP_parse($process['Info']))); ?></td>
-</tr>
- <?php
- $odd_row = ! $odd_row;
-}
-?>
-</tbody>
-</table>
-<?php
-
-/**
- * Sends the footer
- */
-require './libraries/footer.inc.php';
-?>
diff --git a/server_status.php b/server_status.php
index 3b44a4a..70bd5d0 100644
--- a/server_status.php
+++ b/server_status.php
@@ -14,20 +14,45 @@
if (! defined('PMA_NO_VARIABLES_IMPORT')) {
define('PMA_NO_VARIABLES_IMPORT', true);
}
-require_once './libraries/common.inc.php';
-$GLOBALS['js_include'][] = 'pMap.js';
+require_once './libraries/common.inc.php';
-/**
- * Does the common work
+/**
+ * Ajax request
*/
-require './libraries/server_common.inc.php';
+// Prevent ajax requests from being cached
+if (isset($_REQUEST['ajax_request'])) {
+ header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
+ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
+ header_remove('Last-Modified');
-/**
- * Displays the links
- */
-require './libraries/server_links.inc.php';
+ if (isset($_REQUEST["query_chart"])) {
+ exit(createQueryChart());
+ }
+ if(isset($_REQUEST['chart_data'])) {
+ switch($_REQUEST['type']) {
+ case 'proc':
+ $c = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name="Connections"', 0, 1);
+ $result = PMA_DBI_query('SHOW PROCESSLIST');
+ $num_procs = PMA_DBI_num_rows($result);
+
+ $ret = Array('x'=>(microtime(true)*1000),'y_proc'=>$num_procs,'y_conn'=>$c['Connections']);
+ exit(json_encode($ret));
+ case 'queries':
+ $queries = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name LIKE "Com_%" AND Value>0', 0, 1);
+ cleanDeprecated($queries);
+ // admin commands are not queries
+ unset($queries['Com_admin_commands']);
+
+ $sum=array_sum($queries);
+
+ $ret = Array('x'=>(microtime(true)*1000),'y'=>$sum,'pointInfo'=>$queries,'numQueries'=>count($queries));
+ exit(json_encode($ret));
+ }
+ }
+}
+
/**
* Replication library
@@ -36,132 +61,18 @@ require './libraries/replication.inc.php';
require_once './libraries/replication_gui.lib.php';
/**
- * Chart generation
- */
-require_once './libraries/chart.lib.php';
-
-/**
- * Messages are built using the message name
- */
-$strShowStatusBinlog_cache_disk_useDescr = __('The number of transactions that used the temporary binary log cache but that exceeded the value of binlog_cache_size and used a temporary file to store statements from the transaction.');
-$strShowStatusBinlog_cache_useDescr = __('The number of transactions that used the temporary binary log cache.');
-$strShowStatusCreated_tmp_disk_tablesDescr = __('The number of temporary tables on disk created automatically by the server while executing statements. If Created_tmp_disk_tables is big, you may want to increase the tmp_table_size value to cause temporary tables to be memory-based instead of disk-based.');
-$strShowStatusCreated_tmp_filesDescr = __('How many temporary files mysqld has created.');
-$strShowStatusCreated_tmp_tablesDescr = __('The number of in-memory temporary tables created automatically by the server while executing statements.');
-$strShowStatusDelayed_errorsDescr = __('The number of rows written with INSERT DELAYED for which some error occurred (probably duplicate key).');
-$strShowStatusDelayed_insert_threadsDescr = __('The number of INSERT DELAYED handler threads in use. Every different table on which one uses INSERT DELAYED gets its own thread.');
-$strShowStatusDelayed_writesDescr = __('The number of INSERT DELAYED rows written.');
-$strShowStatusFlush_commandsDescr = __('The number of executed FLUSH statements.');
-$strShowStatusHandler_commitDescr = __('The number of internal COMMIT statements.');
-$strShowStatusHandler_deleteDescr = __('The number of times a row was deleted from a table.');
-$strShowStatusHandler_discoverDescr = __('The MySQL server can ask the NDB Cluster storage engine if it knows about a table with a given name. This is called discovery. Handler_discover indicates the number of time tables have been discovered.');
-$strShowStatusHandler_read_firstDescr = __('The number of times the first entry was read from an index. If this is high, it suggests that the server is doing a lot of full index scans; for example, SELECT col1 FROM foo, assuming that col1 is indexed.');
-$strShowStatusHandler_read_keyDescr = __('The number of requests to read a row based on a key. If this is high, it is a good indication that your queries and tables are properly indexed.');
-$strShowStatusHandler_read_nextDescr = __('The number of requests to read the next row in key order. This is incremented if you are querying an index column with a range constraint or if you are doing an index scan.');
-$strShowStatusHandler_read_prevDescr = __('The number of requests to read the previous row in key order. This read method is mainly used to optimize ORDER BY ... DESC.');
-$strShowStatusHandler_read_rndDescr = __('The number of requests to read a row based on a fixed position. This is high if you are doing a lot of queries that require sorting of the result. You probably have a lot of queries that require MySQL to scan whole tables or you have joins that don\'t use keys properly.');
-$strShowStatusHandler_read_rnd_nextDescr = __('The number of requests to read the next row in the data file. This is high if you are doing a lot of table scans. Generally this suggests that your tables are not properly indexed or that your queries are not written to take advantage of the indexes you have.');
-$strShowStatusHandler_rollbackDescr = __('The number of internal ROLLBACK statements.');
-$strShowStatusHandler_updateDescr = __('The number of requests to update a row in a table.');
-$strShowStatusHandler_writeDescr = __('The number of requests to insert a row in a table.');
-$strShowStatusInnodb_buffer_pool_pages_dataDescr = __('The number of pages containing data (dirty or clean).');
-$strShowStatusInnodb_buffer_pool_pages_dirtyDescr = __('The number of pages currently dirty.');
-$strShowStatusInnodb_buffer_pool_pages_flushedDescr = __('The number of buffer pool pages that have been requested to be flushed.');
-$strShowStatusInnodb_buffer_pool_pages_freeDescr = __('The number of free pages.');
-$strShowStatusInnodb_buffer_pool_pages_latchedDescr = __('The number of latched pages in InnoDB buffer pool. These are pages currently being read or written or that can\'t be flushed or removed for some other reason.');
-$strShowStatusInnodb_buffer_pool_pages_miscDescr = __('The number of pages busy because they have been allocated for administrative overhead such as row locks or the adaptive hash index. This value can also be calculated as Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free - Innodb_buffer_pool_pages_data.');
-$strShowStatusInnodb_buffer_pool_pages_totalDescr = __('Total size of buffer pool, in pages.');
-$strShowStatusInnodb_buffer_pool_read_ahead_rndDescr = __('The number of "random" read-aheads InnoDB initiated. This happens when a query is to scan a large portion of a table but in random order.');
-$strShowStatusInnodb_buffer_pool_read_ahead_seqDescr = __('The number of sequential read-aheads InnoDB initiated. This happens when InnoDB does a sequential full table scan.');
-$strShowStatusInnodb_buffer_pool_read_requestsDescr = __('The number of logical read requests InnoDB has done.');
-$strShowStatusInnodb_buffer_pool_readsDescr = __('The number of logical reads that InnoDB could not satisfy from buffer pool and had to do a single-page read.');
-$strShowStatusInnodb_buffer_pool_wait_freeDescr = __('Normally, writes to the InnoDB buffer pool happen in the background. However, if it\'s necessary to read or create a page and no clean pages are available, it\'s necessary to wait for pages to be flushed first. This counter counts instances of these waits. If the buffer pool size was set properly, this value should be small.');
-$strShowStatusInnodb_buffer_pool_write_requestsDescr = __('The number writes done to the InnoDB buffer pool.');
-$strShowStatusInnodb_data_fsyncsDescr = __('The number of fsync() operations so far.');
-$strShowStatusInnodb_data_pending_fsyncsDescr = __('The current number of pending fsync() operations.');
-$strShowStatusInnodb_data_pending_readsDescr = __('The current number of pending reads.');
-$strShowStatusInnodb_data_pending_writesDescr = __('The current number of pending writes.');
-$strShowStatusInnodb_data_readDescr = __('The amount of data read so far, in bytes.');
-$strShowStatusInnodb_data_readsDescr = __('The total number of data reads.');
-$strShowStatusInnodb_data_writesDescr = __('The total number of data writes.');
-$strShowStatusInnodb_data_writtenDescr = __('The amount of data written so far, in bytes.');
-$strShowStatusInnodb_dblwr_pages_writtenDescr = __('The number of pages that have been written for doublewrite operations.');
-$strShowStatusInnodb_dblwr_writesDescr = __('The number of doublewrite operations that have been performed.');
-$strShowStatusInnodb_log_waitsDescr = __('The number of waits we had because log buffer was too small and we had to wait for it to be flushed before continuing.');
-$strShowStatusInnodb_log_write_requestsDescr = __('The number of log write requests.');
-$strShowStatusInnodb_log_writesDescr = __('The number of physical writes to the log file.');
-$strShowStatusInnodb_os_log_fsyncsDescr = __('The number of fsync() writes done to the log file.');
-$strShowStatusInnodb_os_log_pending_fsyncsDescr = __('The number of pending log file fsyncs.');
-$strShowStatusInnodb_os_log_pending_writesDescr = __('Pending log file writes.');
-$strShowStatusInnodb_os_log_writtenDescr = __('The number of bytes written to the log file.');
-$strShowStatusInnodb_pages_createdDescr = __('The number of pages created.');
-$strShowStatusInnodb_page_sizeDescr = __('The compiled-in InnoDB page size (default 16KB). Many values are counted in pages; the page size allows them to be easily converted to bytes.');
-$strShowStatusInnodb_pages_readDescr = __('The number of pages read.');
-$strShowStatusInnodb_pages_writtenDescr = __('The number of pages written.');
-$strShowStatusInnodb_row_lock_current_waitsDescr = __('The number of row locks currently being waited for.');
-$strShowStatusInnodb_row_lock_time_avgDescr = __('The average time to acquire a row lock, in milliseconds.');
-$strShowStatusInnodb_row_lock_timeDescr = __('The total time spent in acquiring row locks, in milliseconds.');
-$strShowStatusInnodb_row_lock_time_maxDescr = __('The maximum time to acquire a row lock, in milliseconds.');
-$strShowStatusInnodb_row_lock_waitsDescr = __('The number of times a row lock had to be waited for.');
-$strShowStatusInnodb_rows_deletedDescr = __('The number of rows deleted from InnoDB tables.');
-$strShowStatusInnodb_rows_insertedDescr = __('The number of rows inserted in InnoDB tables.');
-$strShowStatusInnodb_rows_readDescr = __('The number of rows read from InnoDB tables.');
-$strShowStatusInnodb_rows_updatedDescr = __('The number of rows updated in InnoDB tables.');
-$strShowStatusKey_blocks_not_flushedDescr = __('The number of key blocks in the key cache that have changed but haven\'t yet been flushed to disk. It used to be known as Not_flushed_key_blocks.');
-$strShowStatusKey_blocks_unusedDescr = __('The number of unused blocks in the key cache. You can use this value to determine how much of the key cache is in use.');
-$strShowStatusKey_blocks_usedDescr = __('The number of used blocks in the key cache. This value is a high-water mark that indicates the maximum number of blocks that have ever been in use at one time.');
-$strShowStatusKey_read_requestsDescr = __('The number of requests to read a key block from the cache.');
-$strShowStatusKey_readsDescr = __('The number of physical reads of a key block from disk. If Key_reads is big, then your key_buffer_size value is probably too small. The cache miss rate can be calculated as Key_reads/Key_read_requests.');
-$strShowStatusKey_write_requestsDescr = __('The number of requests to write a key block to the cache.');
-$strShowStatusKey_writesDescr = __('The number of physical writes of a key block to disk.');
-$strShowStatusLast_query_costDescr = __('The total cost of the last compiled query as computed by the query optimizer. Useful for comparing the cost of different query plans for the same query. The default value of 0 means that no query has been compiled yet.');
-$strShowStatusNot_flushed_delayed_rowsDescr = __('The number of rows waiting to be written in INSERT DELAYED queues.');
-$strShowStatusOpened_tablesDescr = __('The number of tables that have been opened. If opened tables is big, your table cache value is probably too small.');
-$strShowStatusOpen_filesDescr = __('The number of files that are open.');
-$strShowStatusOpen_streamsDescr = __('The number of streams that are open (used mainly for logging).');
-$strShowStatusOpen_tablesDescr = __('The number of tables that are open.');
-$strShowStatusQcache_free_blocksDescr = __('The number of free memory blocks in query cache.');
-$strShowStatusQcache_free_memoryDescr = __('The amount of free memory for query cache.');
-$strShowStatusQcache_hitsDescr = __('The number of cache hits.');
-$strShowStatusQcache_insertsDescr = __('The number of queries added to the cache.');
-$strShowStatusQcache_lowmem_prunesDescr = __('The number of queries that have been removed from the cache to free up memory for caching new queries. This information can help you tune the query cache size. The query cache uses a least recently used (LRU) strategy to decide which queries to remove from the cache.');
-$strShowStatusQcache_not_cachedDescr = __('The number of non-cached queries (not cachable, or not cached due to the query_cache_type setting).');
-$strShowStatusQcache_queries_in_cacheDescr = __('The number of queries registered in the cache.');
-$strShowStatusQcache_total_blocksDescr = __('The total number of blocks in the query cache.');
-$strShowStatusReset = _pgettext('$strShowStatusReset', 'Reset');
-$strShowStatusRpl_statusDescr = __('The status of failsafe replication (not yet implemented).');
-$strShowStatusSelect_full_joinDescr = __('The number of joins that do not use indexes. If this value is not 0, you should carefully check the indexes of your tables.');
-$strShowStatusSelect_full_range_joinDescr = __('The number of joins that used a range search on a reference table.');
-$strShowStatusSelect_range_checkDescr = __('The number of joins without keys that check for key usage after each row. (If this is not 0, you should carefully check the indexes of your tables.)');
-$strShowStatusSelect_rangeDescr = __('The number of joins that used ranges on the first table. (It\'s normally not critical even if this is big.)');
-$strShowStatusSelect_scanDescr = __('The number of joins that did a full scan of the first table.');
-$strShowStatusSlave_open_temp_tablesDescr = __('The number of temporary tables currently open by the slave SQL thread.');
-$strShowStatusSlave_retried_transactionsDescr = __('Total (since startup) number of times the replication slave SQL thread has retried transactions.');
-$strShowStatusSlave_runningDescr = __('This is ON if this server is a slave that is connected to a master.');
-$strShowStatusSlow_launch_threadsDescr = __('The number of threads that have taken more than slow_launch_time seconds to create.');
-$strShowStatusSlow_queriesDescr = __('The number of queries that have taken more than long_query_time seconds.');
-$strShowStatusSort_merge_passesDescr = __('The number of merge passes the sort algorithm has had to do. If this value is large, you should consider increasing the value of the sort_buffer_size system variable.');
-$strShowStatusSort_rangeDescr = __('The number of sorts that were done with ranges.');
-$strShowStatusSort_rowsDescr = __('The number of sorted rows.');
-$strShowStatusSort_scanDescr = __('The number of sorts that were done by scanning the table.');
-$strShowStatusTable_locks_immediateDescr = __('The number of times that a table lock was acquired immediately.');
-$strShowStatusTable_locks_waitedDescr = __('The number of times that a table lock could not be acquired immediately and a wait was needed. If this is high, and you have performance problems, you should first optimize your queries, and then either split your table or tables or use replication.');
-$strShowStatusThreads_cachedDescr = __('The number of threads in the thread cache. The cache hit rate can be calculated as Threads_created/Connections. If this value is red you should raise your thread_cache_size.');
-$strShowStatusThreads_connectedDescr = __('The number of currently open connections.');
-$strShowStatusThreads_createdDescr = __('The number of threads created to handle connections. If Threads_created is big, you may want to increase the thread_cache_size value. (Normally this doesn\'t give a notable performance improvement if you have a good thread implementation.)');
-$strShowStatusThreads_runningDescr = __('The number of threads that are not sleeping.');
-
-/**
- * Displays the sub-page heading
+ * JS Includes
*/
-echo '<div id="serverstatus">' . "\n";
-echo '<h2>' . "\n"
- . ($GLOBALS['cfg']['MainPageIconic']
- ? '<img class="icon" src="' . $GLOBALS['pmaThemeImage'] .
- 's_status.png" width="16" height="16" alt="" />'
- : '')
- . __('Runtime Information') . "\n"
- . '</h2>' . "\n";
+
+$GLOBALS['js_include'][] = 'server_status.js';
+$GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js';
+$GLOBALS['js_include'][] = 'jquery/jquery.tablesorter.js';
+$GLOBALS['js_include'][] = 'jquery/jquery.cookie.js'; // For tab persistence
+$GLOBALS['js_include'][] = 'highcharts/highcharts.js';
+/* Files required for chart exporting */
+$GLOBALS['js_include'][] = 'highcharts/exporting.js';
+$GLOBALS['js_include'][] = 'canvg/canvg.js';
+$GLOBALS['js_include'][] = 'canvg/rgbcolor.js';
/**
@@ -180,6 +91,20 @@ if (isset($_REQUEST['flush'])) {
unset($_flush_commands);
}
+/**
+ * Kills a selected process
+ */
+if (!empty($_REQUEST['kill'])) {
+ if (PMA_DBI_try_query('KILL ' . $_REQUEST['kill'] . ';')) {
+ $message = PMA_Message::success(__('Thread %s was successfully killed.'));
+ } else {
+ $message = PMA_Message::error(__('phpMyAdmin was unable to kill thread %s. It probably has already been closed.'));
+ }
+ $message->addParam($_REQUEST['kill']);
+ //$message->display();
+}
+
+
/**
* get status from server
@@ -192,29 +117,9 @@ $server_status = PMA_DBI_fetch_result('SHOW GLOBAL STATUS', 0, 1);
$server_variables = PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES', 0, 1);
/**
- * starttime calculation
+ * cleanup of some deprecated values
*/
-$start_time = PMA_DBI_fetch_value(
- 'SELECT UNIX_TIMESTAMP() - ' . $server_status['Uptime']);
-
-
-/**
- * cleanup some deprecated values
- */
-$deprecated = array(
- 'Com_prepare_sql' => 'Com_stmt_prepare',
- 'Com_execute_sql' => 'Com_stmt_execute',
- 'Com_dealloc_sql' => 'Com_stmt_close',
-);
-
-foreach ($deprecated as $old => $new) {
- if (isset($server_status[$old])
- && isset($server_status[$new])) {
- unset($server_status[$old]);
- }
-}
-unset($deprecated);
-
+cleanDeprecated($server_status);
/**
* calculate some values
@@ -266,64 +171,12 @@ if (isset($server_status['Uptime_since_flush_status'])) {
}
/**
- * define some alerts
- */
-// name => max value before alert
-$alerts = array(
- // lower is better
- // variable => max value
- 'Aborted_clients' => 0,
- 'Aborted_connects' => 0,
-
- 'Binlog_cache_disk_use' => 0,
-
- 'Created_tmp_disk_tables' => 0,
-
- 'Handler_read_rnd' => 0,
- 'Handler_read_rnd_next' => 0,
-
- 'Innodb_buffer_pool_pages_dirty' => 0,
- 'Innodb_buffer_pool_reads' => 0,
- 'Innodb_buffer_pool_wait_free' => 0,
- 'Innodb_log_waits' => 0,
- 'Innodb_row_lock_time_avg' => 10, // ms
- 'Innodb_row_lock_time_max' => 50, // ms
- 'Innodb_row_lock_waits' => 0,
-
- 'Slow_queries' => 0,
- 'Delayed_errors' => 0,
- 'Select_full_join' => 0,
- 'Select_range_check' => 0,
- 'Sort_merge_passes' => 0,
- 'Opened_tables' => 0,
- 'Table_locks_waited' => 0,
- 'Qcache_lowmem_prunes' => 0,
- 'Slow_launch_threads' => 0,
-
- // depends on Key_read_requests
- // normaly lower then 1:0.01
- 'Key_reads' => (0.01 * $server_status['Key_read_requests']),
- // depends on Key_write_requests
- // normaly nearly 1:1
- 'Key_writes' => (0.9 * $server_status['Key_write_requests']),
-
- 'Key_buffer_fraction' => 0.5,
-
- // alert if more than 95% of thread cache is in use
- 'Threads_cached' => 0.95 * $server_variables['thread_cache_size']
-
- // higher is better
- // variable => min value
- //'Handler read key' => '> ',
-);
-
-
-/**
* split variables in sections
*/
$allocations = array(
// variable name => section
-
+ // variable names match when they begin with the given string
+
'Com_' => 'com',
'Innodb_' => 'innodb',
'Ndb_' => 'ndb',
@@ -368,24 +221,24 @@ $allocations = array(
$sections = array(
// section => section name (description)
- 'com' => array('title' => ''),
- 'query' => array('title' => __('SQL query')),
- 'innodb' => array('title' => 'InnoDB'),
- 'ndb' => array('title' => 'NDB'),
- 'handler' => array('title' => __('Handler')),
- 'qcache' => array('title' => __('Query cache')),
- 'threads' => array('title' => __('Threads')),
- 'binlog_cache' => array('title' => __('Binary log')),
- 'created_tmp' => array('title' => __('Temporary data')),
- 'delayed' => array('title' => __('Delayed inserts')),
- 'key' => array('title' => __('Key cache')),
- 'select' => array('title' => __('Joins')),
- 'repl' => array('title' => __('Replication')),
- 'sort' => array('title' => __('Sorting')),
- 'table' => array('title' => __('Tables')),
- 'tc' => array('title' => __('Transaction coordinator')),
- 'files' => array('title' => __('Files')),
- 'ssl' => array('title' => 'SSL'),
+ 'com' => 'Com',
+ 'query' => __('SQL query'),
+ 'innodb' => 'InnoDB',
+ 'ndb' => 'NDB',
+ 'handler' => __('Handler'),
+ 'qcache' => __('Query cache'),
+ 'threads' => __('Threads'),
+ 'binlog_cache' => __('Binary log'),
+ 'created_tmp' => __('Temporary data'),
+ 'delayed' => __('Delayed inserts'),
+ 'key' => __('Key cache'),
+ 'select' => __('Joins'),
+ 'repl' => __('Replication'),
+ 'sort' => __('Sorting'),
+ 'table' => __('Tables'),
+ 'tc' => __('Transaction coordinator'),
+ 'files' => __('Files'),
+ 'ssl' => 'SSL',
);
/**
@@ -417,8 +270,8 @@ $links['qcache'][__('Flush query cache')]
PMA_generate_common_url();
$links['qcache']['doc'] = 'query_cache';
-$links['threads'][__('Show processes')]
- = 'server_processlist.php?' . PMA_generate_common_url();
+//$links['threads'][__('Show processes')]
+// = 'server_processlist.php?' . PMA_generate_common_url();
$links['threads']['doc'] = 'mysql_threads';
$links['key']['doc'] = 'myisam_key_cache';
@@ -435,337 +288,717 @@ $links['innodb'][__('InnoDB Status')]
$links['innodb']['doc'] = 'innodb';
-// sort status vars into arrays
+// Variable to contain all com_ variables
+$used_queries = Array();
+
+// Variable to map variable names to their respective section name (used for js category filtering)
+$allocationMap = Array();
+
+// sort vars into arrays
foreach ($server_status as $name => $value) {
- if (isset($allocations[$name])) {
- $sections[$allocations[$name]]['vars'][$name] = $value;
- unset($server_status[$name]);
- } else {
- foreach ($allocations as $filter => $section) {
- if (preg_match('/^' . $filter . '/', $name)
- && isset($server_status[$name])) {
- unset($server_status[$name]);
- $sections[$section]['vars'][$name] = $value;
- }
+ foreach ($allocations as $filter => $section) {
+ if (strpos($name, $filter) !== FALSE) {
+ $allocationMap[$name] = $section;
+ if($section=='com' && $value>0) $used_queries[$name] = $value;
+ break; // Only exits inner loop
}
}
}
-unset($name, $value, $filter, $section, $allocations);
-
-// rest
-$sections['all']['vars'] =& $server_status;
-$hour_factor = 3600 / $server_status['Uptime'];
+// admin commands are not queries (e.g. they include COM_PING, which is excluded from $server_status['Questions'])
+unset($used_queries['Com_admin_commands']);
+
+/* Ajax request refresh */
+if(isset($_REQUEST['show']) && isset($_REQUEST['ajax_request'])) {
+ switch($_REQUEST['show']) {
+ case 'query_statistics':
+ printQueryStatistics();
+ exit();
+ case 'server_traffic':
+ printServerTraffic();
+ exit();
+ case 'variables_table':
+ // Prints the variables table
+ printVariablesTable();
+ exit();
+
+ default:
+ break;
+ }
+}
/**
* start output
*/
+
+ /**
+ * Does the common work
+ */
+require './libraries/server_common.inc.php';
+
+
+/**
+ * Displays the links
+ */
+require './libraries/server_links.inc.php';
+
?>
-<div id="statuslinks">
- <a href="<?php echo
- $PMA_PHP_SELF . '?' . PMA_generate_common_url(); ?>"
- ><?php echo __('Refresh'); ?></a>
- <a href="<?php echo
- $PMA_PHP_SELF . '?flush=STATUS&' . PMA_generate_common_url(); ?>"
- ><?php echo _pgettext('for Show status', 'Reset'); ?></a>
- <?php echo PMA_showMySQLDocu('server_status_variables','server_status_variables'); ?>
+<script type="text/javascript">
+pma_token = '<?php echo $_SESSION[' PMA_token ']; ?>';
+url_query = '<?php echo $url_query;?>';
+pma_theme_image = '<?php echo $GLOBALS['pmaThemeImage']; ?>';
+</script>
+<div id="serverstatus">
+ <h2><?
+
+/**
+ * Displays the sub-page heading
+ */
+if($GLOBALS['cfg']['MainPageIconic'])
+ echo '<img class="icon" src="' . $GLOBALS['pmaThemeImage'] . 's_status.png" width="16" height="16" alt="" />';
+
+echo __('Runtime Information');
+
+?></h2>
+ <div id="serverStatusTabs">
+ <ul>
+ <li><a href="#statustabs_traffic"><?php echo __('Server traffic'); ?></a></li>
+ <li><a href="#statustabs_queries"><?php echo __('Query statistics'); ?></a></li>
+ <li><a href="#statustabs_allvars"><?php echo __('All status variables'); ?></a></li>
+ </ul>
+
+ <div id="statustabs_traffic">
+ <div class="statuslinks">
+ <a class="tabRefresh" href="<?php echo $PMA_PHP_SELF . '?show=server_traffic&' . PMA_generate_common_url(); ?>" >
+ <img src="<?php echo $GLOBALS['pmaThemeImage'];?>ajax_clock_small.gif" alt="ajax clock" style="display: none;" />
+ <?php echo __('Refresh'); ?>
+ </a>
+ <select name="trafficChartRefresh" style="display:none;">
+ <option value="5"><?php echo __('Refresh rate'); ?></option>
+ <option value="1">1 <?php echo __('second'); ?></option>
+ <option value="2">2 <?php echo __('seconds'); ?></option>
+ <option value="5">5 <?php echo __('seconds'); ?></option>
+ <option value="10">10 <?php echo __('seconds'); ?></option>
+ <option value="20">20 <?php echo __('seconds'); ?></option>
+ <option value="40">40 <?php echo __('seconds'); ?></option>
+ <option value="60">1 <?php echo __('minutes'); ?></option>
+ <option value="120">2 <?php echo __('minutes'); ?></option>
+ <option value="300">5 <?php echo __('minutes'); ?></option>
+ <option value="600">10 <?php echo __('minutes'); ?></option>
+ </select>
+
+ <a class="tabChart" href="#">
+ <?php echo __('Realtime chart'); ?>
+ </a>
+ </div>
+ <div class="tabInnerContent">
+ <?php printServerTraffic(); ?>
+ </div>
+ </div>
+ <div id="statustabs_queries">
+ <div class="statuslinks">
+ <a class="tabRefresh" href="<?php echo $PMA_PHP_SELF . '?show=query_statistics&' . PMA_generate_common_url(); ?>" >
+ <img src="<?php echo $GLOBALS['pmaThemeImage'];?>ajax_clock_small.gif" alt="ajax clock" style="display: none;" />
+ <?php echo __('Refresh'); ?>
+ </a>
+ <select name="queryChartRefresh" style="display:none;">
+ <option value="5"><?php echo __('Refresh rate'); ?></option>
+ <option value="1">1 <?php echo __('second'); ?></option>
+ <option value="2">2 <?php echo __('seconds'); ?></option>
+ <option value="5">5 <?php echo __('seconds'); ?></option>
+ <option value="10">10 <?php echo __('seconds'); ?></option>
+ <option value="20">20 <?php echo __('seconds'); ?></option>
+ <option value="40">40 <?php echo __('seconds'); ?></option>
+ <option value="60">1 <?php echo __('minutes'); ?></option>
+ <option value="120">2 <?php echo __('minutes'); ?></option>
+ <option value="300">5 <?php echo __('minutes'); ?></option>
+ <option value="600">10 <?php echo __('minutes'); ?></option>
+ </select>
+ <a class="tabChart" href="#">
+ <?php echo __('Realtime chart'); ?>
+ </a>
+ </div>
+ <div class="tabInnerContent">
+ <?php printQueryStatistics(); ?>
+ </div>
+ </div>
+ <div id="statustabs_allvars">
+ <fieldset id="tableFilter">
+ <div class="statuslinks">
+ <a class="tabRefresh" href="<?php echo $PMA_PHP_SELF . '?show=variables_table&' . PMA_generate_common_url(); ?>" >
+ <img src="<?php echo $GLOBALS['pmaThemeImage'];?>ajax_clock_small.gif" alt="ajax clock" style="display: none;" />
+ <?php echo __('Refresh'); ?>
+ </a>
+ </div>
+ <legend>Filters</legend>
+ <div class="formelement">
+ <label for="filterText"><?php echo __('Containing the word:'); ?></label>
+ <input name="filterText" type="text" id="filterText" style="vertical-align: baseline;" />
+ </div>
+ <div class="formelement">
+ <input type="checkbox" name="filterAlert" id="filterAlert">
+ <label for="filterAlert"><?php echo __('Show only alert values'); ?></label>
+ </div>
+ <div class="formelement">
+ <select id="filterCategory" name="filterCategory">
+ <option value=''><?php echo __('Filter by category...'); ?></option>
+ <?php
+ foreach($sections as $section_id=>$section_name) {
+ ?>
+ <option value='<?php echo $section_id; ?>'><?php echo $section_name; ?></option>
+ <?php
+ }
+
+ ?>
+ </select>
+ </div>
+ </fieldset>
+ <div id="linkSuggestions" class="defaultLinks" style="display:none">
+ <p><?php echo __('Related links:'); ?>
+ <?php
+ foreach ($links as $section_name => $section_links) {
+ echo '<span class="status_'.$section_name.'"> ';
+ $i=0;
+ foreach ($section_links as $link_name => $link_url) {
+ if($i>0) echo ', ';
+ if ('doc' == $link_name) {
+ echo PMA_showMySQLDocu($link_url, $link_url);
+ } else {
+ echo '<a href="' . $link_url . '">' . $link_name . '</a>';
+ }
+ $i++;
+ }
+ echo '</span>';
+ }
+ unset($link_url, $link_name, $i);
+ ?>
+ </p>
+ </div>
+ <div class="tabInnerContent">
+ <?php printVariablesTable(); ?>
+ </div>
+ </div>
+ </div>
</div>
-<p>
<?php
-echo sprintf(__('This MySQL server has been running for %s. It started up on %s.'),
- PMA_timespanFormat($server_status['Uptime']),
- PMA_localisedDate($start_time)) . "\n";
-?>
-</p>
-<?php
-if ($server_master_status || $server_slave_status) {
- echo '<p>';
- if ($server_master_status && $server_slave_status) {
- echo __('This MySQL server works as <b>master</b> and <b>slave</b> in <b>replication</b> process.');
- } elseif ($server_master_status) {
- echo __('This MySQL server works as <b>master</b> in <b>replication</b> process.');
- } elseif ($server_slave_status) {
- echo __('This MySQL server works as <b>slave</b> in <b>replication</b> process.');
+function printQueryStatistics() {
+ global $server_status, $used_queries, $url_query, $PMA_PHP_SELF;
+
+ $hour_factor = 3600 / $server_status['Uptime'];
+
+ $total_queries = array_sum($used_queries);
+
+ ?>
+ <h3 id="serverstatusqueries"><?php echo
+ //sprintf(__('<b>Query statistics</b>: Since its startup, %s queries have been sent to the server.'),
+ //PMA_formatNumber($server_status['Questions'], 0));
+ sprintf('Queries since startup: %s',PMA_formatNumber($total_queries, 0));
+ //echo PMA_showMySQLDocu('server-status-variables', 'server-status-variables', false, 'statvar_Questions');
+ ?>
+ <br>
+ <span style="font-size:60%; display:inline;">
+ ø <?php echo __('per hour'); ?>:
+ <?php echo PMA_formatNumber($total_queries * $hour_factor, 0); ?><br>
+
+ ø <?php echo __('per minute'); ?>:
+ <?php echo PMA_formatNumber( $total_queries * 60 / $server_status['Uptime'], 0); ?><br>
+
+ <?php if($total_queries / $server_status['Uptime'] >= 1) {
+ ?>
+ ø <?php echo __('per second'); ?>:
+ <?php echo PMA_formatNumber( $total_queries / $server_status['Uptime'], 0); ?><br>
+
+ <?php
}
- echo __('For further information about replication status on the server, please visit the <a href=#replication>replication section</a>.');
- echo '</p>';
-}
-?>
-<div id="sectionlinks">
-<?php
-foreach ($sections as $section_name => $section) {
- if (! empty($section['vars']) && ! empty($section['title'])) {
- echo '<a href="' . $PMA_PHP_SELF . '?' .
- PMA_generate_common_url() . '#' . $section_name . '">' .
- $section['title'] . '</a>' . "\n";
+ // reverse sort by value to show most used statements first
+ arsort($used_queries);
+
+ $odd_row = true;
+ $count_displayed_rows = 0;
+ $perc_factor = 100 / $total_queries //(- $server_status['Connections']);
+
+ ?>
+ </h3>
+ <table id="serverstatusqueriesdetails" class="data sortable">
+ <col class="namecol" />
+ <col class="valuecol" span="3" />
+ <thead>
+ <tr><th><?php echo __('Query type'); ?></th>
+ <th><?php
+ /* l10n: # = Amount of queries */
+ echo __('#');
+ ?>
+ <th>ø <?php echo __('per hour'); ?></th>
+ <th>%</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ <?php
+ $chart_json = Array();
+ $query_sum = array_sum($used_queries);
+ $other_sum = 0;
+ foreach ($used_queries as $name => $value) {
+ $odd_row = !$odd_row;
+
+ // For the percentage column, use Questions - Connections, because
+ // the number of connections is not an item of the Query types
+ // but is included in Questions. Then the total of the percentages is 100.
+ $name = str_replace(Array('Com_','_'), Array('',' '), $name);
+
+ if($value < $query_sum * 0.02)
+ $other_sum += $value;
+ else $chart_json[$name] = $value;
+ ?>
+ <tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; ?>">
+ <th class="name"><?php echo htmlspecialchars($name); ?></th>
+ <td class="value"><?php echo PMA_formatNumber($value, 5, 0, true); ?></td>
+ <td class="value"><?php echo
+ PMA_formatNumber($value * $hour_factor, 4, 1, true); ?></td>
+ <td class="value"><?php echo
+ PMA_formatNumber($value * $perc_factor, 0, 2); ?>%</td>
+ </tr>
+ <?php
}
+ ?>
+ </tbody>
+ </table>
+
+ <div id="serverstatusquerieschart" style="width:500px; height:350px; ">
+ <?php
+ /*// Generate the graph if this is an ajax request
+ if(isset($_REQUEST['ajax_request'])) {
+ echo createQueryChart();
+ } else {
+ echo '<a href="'.$PMA_PHP_SELF.'?'.$url_query.'&query_chart=1#serverstatusqueries"'
+ .'title="' . __('Show query chart') . '">['.__('Show query chart').']</a>';
+ }*/
+
+ if($other_sum>0)
+ $chart_json[__('Other')] = $other_sum;
+
+ echo json_encode($chart_json);
+
+ ?>
+ </div>
+ <?php
}
-?>
-</div>
-
-<h3><?php echo __('<b>Server traffic</b>: These tables show the network traffic statistics of this MySQL server since its startup.'); ?></h3>
-
-<table id="serverstatustraffic" class="data">
-<thead>
-<tr>
- <th colspan="2"><?php echo __('Traffic') . ' ' . PMA_showHint(__('On a busy server, the byte counters may overrun, so those statistics as reported by the MySQL server may be incorrect.')); ?></th>
- <th>ø <?php echo __('per hour'); ?></th>
-</tr>
-</thead>
-<tbody>
-<tr class="noclick odd">
- <th class="name"><?php echo __('Received'); ?></th>
- <td class="value"><?php echo
- implode(' ',
- PMA_formatByteDown($server_status['Bytes_received'], 2, 1)); ?></td>
- <td class="value"><?php echo
- implode(' ',
- PMA_formatByteDown(
- $server_status['Bytes_received'] * $hour_factor, 2, 1)); ?></td>
-</tr>
-<tr class="noclick even">
- <th class="name"><?php echo __('Sent'); ?></th>
- <td class="value"><?php echo
- implode(' ',
- PMA_formatByteDown($server_status['Bytes_sent'], 2, 1)); ?></td>
- <td class="value"><?php echo
- implode(' ',
- PMA_formatByteDown(
- $server_status['Bytes_sent'] * $hour_factor, 2, 1)); ?></td>
-</tr>
-<tr class="noclick odd">
- <th class="name"><?php echo __('Total'); ?></th>
- <td class="value"><?php echo
- implode(' ',
- PMA_formatByteDown(
- $server_status['Bytes_received'] + $server_status['Bytes_sent'], 2, 1)
- ); ?></td>
- <td class="value"><?php echo
- implode(' ',
- PMA_formatByteDown(
- ($server_status['Bytes_received'] + $server_status['Bytes_sent'])
- * $hour_factor, 2, 1)
- ); ?></td>
-</tr>
-</tbody>
-</table>
-
-<table id="serverstatusconnections" class="data">
-<thead>
-<tr>
- <th colspan="2"><?php echo __('Connections'); ?></th>
- <th>ø <?php echo __('per hour'); ?></th>
- <th>%</th>
-</tr>
-</thead>
-<tbody>
-<tr class="noclick odd">
- <th class="name"><?php echo __('max. concurrent connections'); ?></th>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Max_used_connections'], 0); ?> </td>
- <td class="value">--- </td>
- <td class="value">--- </td>
-</tr>
-<tr class="noclick even">
- <th class="name"><?php echo __('Failed attempts'); ?></th>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Aborted_connects'], 4, 0); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Aborted_connects'] * $hour_factor,
- 4, 2); ?></td>
- <td class="value"><?php echo
- $server_status['Connections'] > 0
- ? PMA_formatNumber(
- $server_status['Aborted_connects'] * 100 / $server_status['Connections'],
- 0, 2) . '%'
- : '--- '; ?></td>
-</tr>
-<tr class="noclick odd">
- <th class="name"><?php echo __('Aborted'); ?></th>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Aborted_clients'], 4, 0); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Aborted_clients'] * $hour_factor,
- 4, 2); ?></td>
- <td class="value"><?php echo
- $server_status['Connections'] > 0
- ? PMA_formatNumber(
- $server_status['Aborted_clients'] * 100 / $server_status['Connections'],
- 0, 2) . '%'
- : '--- '; ?></td>
-</tr>
-<tr class="noclick even">
- <th class="name"><?php echo __('Total'); ?></th>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Connections'], 4, 0); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Connections'] * $hour_factor,
- 4, 2); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber(100, 0, 2); ?>%</td>
-</tr>
-</tbody>
-</table>
-
-<hr class="clearfloat" />
-
-<h3 id="serverstatusqueries"><?php echo
- sprintf(__('<b>Query statistics</b>: Since its startup, %s queries have been sent to the server.'),
- PMA_formatNumber($server_status['Questions'], 0));
- echo PMA_showMySQLDocu('server-status-variables', 'server-status-variables', false, 'statvar_Questions');
- ?></h3>
-
-<table id="serverstatusqueriessummary" class="data">
-<thead>
-<tr>
- <th><?php echo __('Total'); ?></th>
- <th>ø <?php echo __('per hour'); ?></th>
- <th>ø <?php echo __('per minute'); ?></th>
- <th>ø <?php echo __('per second'); ?></th>
-</tr>
-</thead>
-<tbody>
-<tr class="noclick odd">
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Questions'], 4, 0); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber($server_status['Questions'] * $hour_factor,
- 3, 2); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber(
- $server_status['Questions'] * 60 / $server_status['Uptime'],
- 3, 2); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber(
- $server_status['Questions'] / $server_status['Uptime'],
- 3, 2); ?></td>
-</tr>
-</tbody>
-</table>
-
-<div id="serverstatusqueriesdetails">
-<?php
-$used_queries = $sections['com']['vars'];
-// reverse sort by value to show most used statements first
-arsort($used_queries);
-// remove all zero values from the end
-// variable empty for Drizzle
-if ($used_queries) {
- while (end($used_queries) == 0) {
- array_pop($used_queries);
+function printServerTraffic() {
+ global $server_status,$PMA_PHP_SELF;
+ global $server_master_status, $server_slave_status;
+
+ $hour_factor = 3600 / $server_status['Uptime'];
+
+ /**
+ * starttime calculation
+ */
+ $start_time = PMA_DBI_fetch_value(
+ 'SELECT UNIX_TIMESTAMP() - ' . $server_status['Uptime']);
+
+ ?>
+ <h3><?php /* echo __('<b>Server traffic</b>: These tables show the network traffic statistics of this MySQL server since its startup.');*/
+ echo sprintf(__('Network traffic since startup: %s'),
+ implode(' ', PMA_formatByteDown( $server_status['Bytes_received'] + $server_status['Bytes_sent'], 3, 1))
+ );
+ ?>
+ </h3>
+
+ <p>
+ <?php
+ echo sprintf(__('This MySQL server has been running for %s. It started up on %s.'),
+ PMA_timespanFormat($server_status['Uptime']),
+ PMA_localisedDate($start_time)) . "\n";
+ ?>
+ </p>
+
+ <?php
+ if ($server_master_status || $server_slave_status) {
+ echo '<p>';
+ if ($server_master_status && $server_slave_status) {
+ echo __('This MySQL server works as <b>master</b> and <b>slave</b> in <b>replication</b> process.');
+ } elseif ($server_master_status) {
+ echo __('This MySQL server works as <b>master</b> in <b>replication</b> process.');
+ } elseif ($server_slave_status) {
+ echo __('This MySQL server works as <b>slave</b> in <b>replication</b> process.');
+ }
+ echo __('For further information about replication status on the server, please visit the <a href=#replication>replication section</a>.');
+ echo '</p>';
}
-}
-// number of tables to split values into
-$tables = 3;
-$max_rows_per_table = (int) ceil(count($used_queries) / $tables);
-$current_table = 0;
-$odd_row = true;
-$count_displayed_rows = 0;
-$perc_factor = 100 / ($server_status['Questions'] - $server_status['Connections']);
-
-foreach ($used_queries as $name => $value) {
- $current_table++;
- if ($count_displayed_rows === 0 || $count_displayed_rows === $max_rows_per_table) {
- $odd_row = true;
- if ($count_displayed_rows === $max_rows_per_table) {
- echo ' </tbody>' . "\n";
- echo ' </table>' . "\n";
- $count_displayed_rows = 0;
+ /* if the server works as master or slave in replication process, display useful information */
+ if ($server_master_status || $server_slave_status)
+ {
+ ?>
+ <hr class="clearfloat" />
+
+ <h3><a name="replication"></a><?php echo __('Replication status'); ?></h3>
+ <?php
+
+ foreach ($replication_types as $type)
+ {
+ if (${"server_{$type}_status"}) {
+ PMA_replication_print_status_table($type);
+ }
}
-?>
- <table id="serverstatusqueriesdetails<?php echo $current_table; ?>" class="data">
- <col class="namecol" />
- <col class="valuecol" span="3" />
+ unset($types);
+ }
+ ?>
+
+ <table id="serverstatustraffic" class="data">
<thead>
- <tr><th colspan="2"><?php echo __('Query type'); ?></th>
- <th>ø <?php echo __('per hour'); ?></th>
- <th>%</th>
- </tr>
+ <tr>
+ <th colspan="2"><?php echo __('Traffic') . ' ' . PMA_showHint(__('On a busy server, the byte counters may overrun, so those statistics as reported by the MySQL server may be incorrect.')); ?></th>
+ <th>ø <?php echo __('per hour'); ?></th>
+ </tr>
</thead>
<tbody>
-<?php
- } else {
- $odd_row = !$odd_row;
- }
- $count_displayed_rows++;
+ <tr class="noclick odd">
+ <th class="name"><?php echo __('Received'); ?></th>
+ <td class="value"><?php echo
+ implode(' ',
+ PMA_formatByteDown($server_status['Bytes_received'], 3, 1)); ?></td>
+ <td class="value"><?php echo
+ implode(' ',
+ PMA_formatByteDown(
+ $server_status['Bytes_received'] * $hour_factor, 3, 1)); ?></td>
+ </tr>
+ <tr class="noclick even">
+ <th class="name"><?php echo __('Sent'); ?></th>
+ <td class="value"><?php echo
+ implode(' ',
+ PMA_formatByteDown($server_status['Bytes_sent'], 3, 1)); ?></td>
+ <td class="value"><?php echo
+ implode(' ',
+ PMA_formatByteDown(
+ $server_status['Bytes_sent'] * $hour_factor, 3, 1)); ?></td>
+ </tr>
+ <tr class="noclick odd">
+ <th class="name"><?php echo __('Total'); ?></th>
+ <td class="value"><?php echo
+ implode(' ',
+ PMA_formatByteDown(
+ $server_status['Bytes_received'] + $server_status['Bytes_sent'], 3, 1)
+ ); ?></td>
+ <td class="value"><?php echo
+ implode(' ',
+ PMA_formatByteDown(
+ ($server_status['Bytes_received'] + $server_status['Bytes_sent'])
+ * $hour_factor, 3, 1)
+ ); ?></td>
+ </tr>
+ </tbody>
+ </table>
-// For the percentage column, use Questions - Connections, because
-// the number of connections is not an item of the Query types
-// but is included in Questions. Then the total of the percentages is 100.
- $name = str_replace('Com_', '', $name);
- $name = str_replace('_', ' ', $name);
-?>
- <tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; ?>">
- <th class="name"><?php echo htmlspecialchars($name); ?></th>
- <td class="value"><?php echo PMA_formatNumber($value, 4, 0); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber($value * $hour_factor, 3, 3); ?></td>
- <td class="value"><?php echo
- PMA_formatNumber($value * $perc_factor, 0, 2); ?>%</td>
- </tr>
-<?php
-}
-?>
+ <table id="serverstatusconnections" class="data">
+ <thead>
+ <tr>
+ <th colspan="2"><?php echo __('Connections'); ?></th>
+ <th>ø <?php echo __('per hour'); ?></th>
+ <th>%</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="noclick odd">
+ <th class="name"><?php echo __('max. concurrent connections'); ?></th>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Max_used_connections'], 0); ?> </td>
+ <td class="value">--- </td>
+ <td class="value">--- </td>
+ </tr>
+ <tr class="noclick even">
+ <th class="name"><?php echo __('Failed attempts'); ?></th>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Aborted_connects'], 4, 1, true); ?></td>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Aborted_connects'] * $hour_factor,
+ 4, 2, true); ?></td>
+ <td class="value"><?php echo
+ $server_status['Connections'] > 0
+ ? PMA_formatNumber(
+ $server_status['Aborted_connects'] * 100 / $server_status['Connections'],
+ 0, 2, true) . '%'
+ : '--- '; ?></td>
+ </tr>
+ <tr class="noclick odd">
+ <th class="name"><?php echo __('Aborted'); ?></th>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Aborted_clients'], 4, 1, true); ?></td>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Aborted_clients'] * $hour_factor,
+ 4, 2, true); ?></td>
+ <td class="value"><?php echo
+ $server_status['Connections'] > 0
+ ? PMA_formatNumber(
+ $server_status['Aborted_clients'] * 100 / $server_status['Connections'],
+ 0, 2, true) . '%'
+ : '--- '; ?></td>
+ </tr>
+ <tr class="noclick even">
+ <th class="name"><?php echo __('Total'); ?></th>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Connections'], 4, 0); ?></td>
+ <td class="value"><?php echo
+ PMA_formatNumber($server_status['Connections'] * $hour_factor,
+ 4, 2); ?></td>
+ <td class="value"><?php echo
+ PMA_formatNumber(100, 0, 2); ?>%</td>
+ </tr>
</tbody>
</table>
- <div class="clearfloat"></div>
-</div>
+ <?
-<?php if ($used_queries): ?>
-<div id="serverstatusquerieschart">
-<?php
- if (empty($_REQUEST["query_chart"])) {
- echo '<a href="' . $PMA_PHP_SELF . '?' . $url_query
- . '&query_chart=1#serverstatusqueries"'
- . 'title="' . __('Show query chart') . '">['
- . __('Show query chart') . ']</a>';
- PMA_Message::notice( __('Note: Generating the query chart can take a long time.'))->display();
- } else {
- echo PMA_chart_status($used_queries);
- }
-?>
-</div>
-<?php endif; ?>
+ $url_params = array();
-<div id="serverstatussection">
-<?php
-//Unset used variables
-unset(
- $tables, $max_rows_per_table, $current_table, $count_displayed_rows, $perc_factor,
- $hour_factor, $sections['com'],
- $server_status['Aborted_clients'], $server_status['Aborted_connects'],
- $server_status['Max_used_connections'], $server_status['Bytes_received'],
- $server_status['Bytes_sent'], $server_status['Connections'],
- $server_status['Questions'], $server_status['Uptime'],
- $used_queries
-);
+ if (! empty($_REQUEST['full'])) {
+ $sql_query = 'SHOW FULL PROCESSLIST';
+ $url_params['full'] = 1;
+ $full_text_link = 'server_status.php' . PMA_generate_common_url(array(), 'html', '?');
+ } else {
+ $sql_query = 'SHOW PROCESSLIST';
+ $full_text_link = 'server_status.php' . PMA_generate_common_url(array('full' => 1));
+ }
+ $result = PMA_DBI_query($sql_query);
-foreach ($sections as $section_name => $section) {
- if (! empty($section['vars'])) {
-?>
- <table class="data" id="serverstatussection<?php echo $section_name; ?>">
- <caption class="tblHeaders">
- <a class="top"
- href="<?php echo $PMA_PHP_SELF . '?' .
- PMA_generate_common_url() . '#serverstatus'; ?>"
- name="<?php echo $section_name; ?>"><?php echo __('Begin'); ?>
- <?php echo
- ($GLOBALS['cfg']['MainPageIconic']
- ? '<img src="' . $GLOBALS['pmaThemeImage'] .
- 's_asc.png" width="11" height="9" align="middle" alt="" />'
- : ''); ?>
- </a>
-<?php
-if (! empty($section['title'])) {
- echo $section['title'];
+ /**
+ * Displays the page
+ */
+ ?>
+ <table id="tableprocesslist" class="data clearfloat">
+ <thead>
+ <tr>
+ <th><?php echo __('Processes'); ?></th>
+ <th><?php echo __('ID'); ?></th>
+ <th><?php echo __('User'); ?></th>
+ <th><?php echo __('Host'); ?></th>
+ <th><?php echo __('Database'); ?></th>
+ <th><?php echo __('Command'); ?></th>
+ <th><?php echo __('Time'); ?></th>
+ <th><?php echo __('Status'); ?></th>
+ <th><?php
+ echo __('SQL query');
+ if (!PMA_DRIZZLE) { ?>
+ <a href="<?php echo $full_text_link; ?>"
+ title="<?php echo empty($full) ? __('Show Full Queries') : __('Truncate Shown Queries'); ?>">
+ <img src="<?php echo $GLOBALS['pmaThemeImage'] . 's_' . (empty($_REQUEST['full']) ? 'full' : 'partial'); ?>text.png"
+ alt="<?php echo empty($_REQUEST['full']) ? __('Show Full Queries') : __('Truncate Shown Queries'); ?>" />
+ </a>
+ <? } ?>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $odd_row = true;
+ while($process = PMA_DBI_fetch_assoc($result)) {
+ if (PMA_DRIZZLE) {
+ // Drizzle uses uppercase keys
+ foreach ($process as $k => $v) {
+ $k = $k !== 'DB'
+ ? $k = ucfirst(strtolower($k))
+ : 'db';
+ $process[$k] = $v;
+ }
+ }
+ $url_params['kill'] = $process['Id'];
+ $kill_process = 'server_status.php' . PMA_generate_common_url($url_params);
+ ?>
+ <tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; ?>">
+ <td><a href="<?php echo $kill_process ; ?>"><?php echo __('Kill'); ?></a></td>
+ <td class="value"><?php echo $process['Id']; ?></td>
+ <td><?php echo $process['User']; ?></td>
+ <td><?php echo $process['Host']; ?></td>
+ <td><?php echo ((! isset($process['db']) || ! strlen($process['db'])) ? '<i>' . __('None') . '</i>' : $process['db']); ?></td>
+ <td><?php echo $process['Command']; ?></td>
+ <td class="value"><?php echo $process['Time']; ?></td>
+ <td><?php echo (empty($process['State']) ? '---' : $process['State']); ?></td>
+ <td><?php echo (empty($process['Info']) ? '---' : PMA_SQP_formatHtml(PMA_SQP_parse($process['Info']))); ?></td>
+ </tr>
+ <?php
+ $odd_row = ! $odd_row;
+ }
+ ?>
+ </tbody>
+ </table>
+ <?php
}
+
+function printVariablesTable() {
+ global $server_status, $server_variables, $allocationMap, $links;
+ /**
+ * Messages are built using the message name
+ */
+ $strShowStatus = Array(
+ 'Aborted_connects' => __('The number of failed attempts to connect to the MySQL server.'),
+ 'Binlog_cache_disk_use' => __('The number of transactions that used the temporary binary log cache but that exceeded the value of binlog_cache_size and used a temporary file to store statements from the transaction.'),
+ 'Binlog_cache_use' => __('The number of transactions that used the temporary binary log cache.'),
+ 'Connections' => __('The number of connection attempts (successful or not) to the MySQL server.'),
+ 'Created_tmp_disk_tables' => __('The number of temporary tables on disk created automatically by the server while executing statements. If Created_tmp_disk_tables is big, you may want to increase the tmp_table_size value to cause temporary tables to be memory-based instead of disk-based.'),
+ 'Created_tmp_files' => __('How many temporary files mysqld has created.'),
+ 'Created_tmp_tables' => __('The number of in-memory temporary tables created automatically by the server while executing statements.'),
+ 'Delayed_errors' => __('The number of rows written with INSERT DELAYED for which some error occurred (probably duplicate key).'),
+ 'Delayed_insert_threads' => __('The number of INSERT DELAYED handler threads in use. Every different table on which one uses INSERT DELAYED gets its own thread.'),
+ 'Delayed_writes' => __('The number of INSERT DELAYED rows written.'),
+ 'Flush_commands' => __('The number of executed FLUSH statements.'),
+ 'Handler_commit' => __('The number of internal COMMIT statements.'),
+ 'Handler_delete' => __('The number of times a row was deleted from a table.'),
+ 'Handler_discover' => __('The MySQL server can ask the NDB Cluster storage engine if it knows about a table with a given name. This is called discovery. Handler_discover indicates the number of time tables have been discovered.'),
+ 'Handler_read_first' => __('The number of times the first entry was read from an index. If this is high, it suggests that the server is doing a lot of full index scans; for example, SELECT col1 FROM foo, assuming that col1 is indexed.'),
+ 'Handler_read_key' => __('The number of requests to read a row based on a key. If this is high, it is a good indication that your queries and tables are properly indexed.'),
+ 'Handler_read_next' => __('The number of requests to read the next row in key order. This is incremented if you are querying an index column with a range constraint or if you are doing an index scan.'),
+ 'Handler_read_prev' => __('The number of requests to read the previous row in key order. This read method is mainly used to optimize ORDER BY ... DESC.'),
+ 'Handler_read_rnd' => __('The number of requests to read a row based on a fixed position. This is high if you are doing a lot of queries that require sorting of the result. You probably have a lot of queries that require MySQL to scan whole tables or you have joins that don\'t use keys properly.'),
+ 'Handler_read_rnd_next' => __('The number of requests to read the next row in the data file. This is high if you are doing a lot of table scans. Generally this suggests that your tables are not properly indexed or that your queries are not written to take advantage of the indexes you have.'),
+ 'Handler_rollback' => __('The number of internal ROLLBACK statements.'),
+ 'Handler_update' => __('The number of requests to update a row in a table.'),
+ 'Handler_write' => __('The number of requests to insert a row in a table.'),
+ 'Innodb_buffer_pool_pages_data' => __('The number of pages containing data (dirty or clean).'),
+ 'Innodb_buffer_pool_pages_dirty' => __('The number of pages currently dirty.'),
+ 'Innodb_buffer_pool_pages_flushed' => __('The number of buffer pool pages that have been requested to be flushed.'),
+ 'Innodb_buffer_pool_pages_free' => __('The number of free pages.'),
+ 'Innodb_buffer_pool_pages_latched' => __('The number of latched pages in InnoDB buffer pool. These are pages currently being read or written or that can\'t be flushed or removed for some other reason.'),
+ 'Innodb_buffer_pool_pages_misc' => __('The number of pages busy because they have been allocated for administrative overhead such as row locks or the adaptive hash index. This value can also be calculated as Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free - Innodb_buffer_pool_pages_data.'),
+ 'Innodb_buffer_pool_pages_total' => __('Total size of buffer pool, in pages.'),
+ 'Innodb_buffer_pool_read_ahead_rnd' => __('The number of "random" read-aheads InnoDB initiated. This happens when a query is to scan a large portion of a table but in random order.'),
+ 'Innodb_buffer_pool_read_ahead_seq' => __('The number of sequential read-aheads InnoDB initiated. This happens when InnoDB does a sequential full table scan.'),
+ 'Innodb_buffer_pool_read_requests' => __('The number of logical read requests InnoDB has done.'),
+ 'Innodb_buffer_pool_reads' => __('The number of logical reads that InnoDB could not satisfy from buffer pool and had to do a single-page read.'),
+ 'Innodb_buffer_pool_wait_free' => __('Normally, writes to the InnoDB buffer pool happen in the background. However, if it\'s necessary to read or create a page and no clean pages are available, it\'s necessary to wait for pages to be flushed first. This counter counts instances of these waits. If the buffer pool size was set properly, this value should be small.'),
+ 'Innodb_buffer_pool_write_requests' => __('The number writes done to the InnoDB buffer pool.'),
+ 'Innodb_data_fsyncs' => __('The number of fsync() operations so far.'),
+ 'Innodb_data_pending_fsyncs' => __('The current number of pending fsync() operations.'),
+ 'Innodb_data_pending_reads' => __('The current number of pending reads.'),
+ 'Innodb_data_pending_writes' => __('The current number of pending writes.'),
+ 'Innodb_data_read' => __('The amount of data read so far, in bytes.'),
+ 'Innodb_data_reads' => __('The total number of data reads.'),
+ 'Innodb_data_writes' => __('The total number of data writes.'),
+ 'Innodb_data_written' => __('The amount of data written so far, in bytes.'),
+ 'Innodb_dblwr_pages_written' => __('The number of pages that have been written for doublewrite operations.'),
+ 'Innodb_dblwr_writes' => __('The number of doublewrite operations that have been performed.'),
+ 'Innodb_log_waits' => __('The number of waits we had because log buffer was too small and we had to wait for it to be flushed before continuing.'),
+ 'Innodb_log_write_requests' => __('The number of log write requests.'),
+ 'Innodb_log_writes' => __('The number of physical writes to the log file.'),
+ 'Innodb_os_log_fsyncs' => __('The number of fsync() writes done to the log file.'),
+ 'Innodb_os_log_pending_fsyncs' => __('The number of pending log file fsyncs.'),
+ 'Innodb_os_log_pending_writes' => __('Pending log file writes.'),
+ 'Innodb_os_log_written' => __('The number of bytes written to the log file.'),
+ 'Innodb_pages_created' => __('The number of pages created.'),
+ 'Innodb_page_size' => __('The compiled-in InnoDB page size (default 16KB). Many values are counted in pages; the page size allows them to be easily converted to bytes.'),
+ 'Innodb_pages_read' => __('The number of pages read.'),
+ 'Innodb_pages_written' => __('The number of pages written.'),
+ 'Innodb_row_lock_current_waits' => __('The number of row locks currently being waited for.'),
+ 'Innodb_row_lock_time_avg' => __('The average time to acquire a row lock, in milliseconds.'),
+ 'Innodb_row_lock_time' => __('The total time spent in acquiring row locks, in milliseconds.'),
+ 'Innodb_row_lock_time_max' => __('The maximum time to acquire a row lock, in milliseconds.'),
+ 'Innodb_row_lock_waits' => __('The number of times a row lock had to be waited for.'),
+ 'Innodb_rows_deleted' => __('The number of rows deleted from InnoDB tables.'),
+ 'Innodb_rows_inserted' => __('The number of rows inserted in InnoDB tables.'),
+ 'Innodb_rows_read' => __('The number of rows read from InnoDB tables.'),
+ 'Innodb_rows_updated' => __('The number of rows updated in InnoDB tables.'),
+ 'Key_blocks_not_flushed' => __('The number of key blocks in the key cache that have changed but haven\'t yet been flushed to disk. It used to be known as Not_flushed_key_blocks.'),
+ 'Key_blocks_unused' => __('The number of unused blocks in the key cache. You can use this value to determine how much of the key cache is in use.'),
+ 'Key_blocks_used' => __('The number of used blocks in the key cache. This value is a high-water mark that indicates the maximum number of blocks that have ever been in use at one time.'),
+ 'Key_read_requests' => __('The number of requests to read a key block from the cache.'),
+ 'Key_reads' => __('The number of physical reads of a key block from disk. If Key_reads is big, then your key_buffer_size value is probably too small. The cache miss rate can be calculated as Key_reads/Key_read_requests.'),
+ 'Key_write_requests' => __('The number of requests to write a key block to the cache.'),
+ 'Key_writes' => __('The number of physical writes of a key block to disk.'),
+ 'Last_query_cost' => __('The total cost of the last compiled query as computed by the query optimizer. Useful for comparing the cost of different query plans for the same query. The default value of 0 means that no query has been compiled yet.'),
+ 'Max_used_connections' => __('The maximum number of connections that have been in use simultaneously since the server started.'),
+ 'Not_flushed_delayed_rows' => __('The number of rows waiting to be written in INSERT DELAYED queues.'),
+ 'Opened_tables' => __('The number of tables that have been opened. If opened tables is big, your table cache value is probably too small.'),
+ 'Open_files' => __('The number of files that are open.'),
+ 'Open_streams' => __('The number of streams that are open (used mainly for logging).'),
+ 'Open_tables' => __('The number of tables that are open.'),
+ 'Qcache_free_blocks' => __('The number of free memory blocks in query cache. High numbers can indicate fragmentation issues, which may be solved by issuing a FLUSH QUERY CACHE statement.'),
+ 'Qcache_free_memory' => __('The amount of free memory for query cache.'),
+ 'Qcache_hits' => __('The number of cache hits.'),
+ 'Qcache_inserts' => __('The number of queries added to the cache.'),
+ 'Qcache_lowmem_prunes' => __('The number of queries that have been removed from the cache to free up memory for caching new queries. This information can help you tune the query cache size. The query cache uses a least recently used (LRU) strategy to decide which queries to remove from the cache.'),
+ 'Qcache_not_cached' => __('The number of non-cached queries (not cachable, or not cached due to the query_cache_type setting).'),
+ 'Qcache_queries_in_cache' => __('The number of queries registered in the cache.'),
+ 'Qcache_total_blocks' => __('The total number of blocks in the query cache.'),
+ 'Rpl_status' => __('The status of failsafe replication (not yet implemented).'),
+ 'Select_full_join' => __('The number of joins that do not use indexes. If this value is not 0, you should carefully check the indexes of your tables.'),
+ 'Select_full_range_join' => __('The number of joins that used a range search on a reference table.'),
+ 'Select_range_check' => __('The number of joins without keys that check for key usage after each row. (If this is not 0, you should carefully check the indexes of your tables.)'),
+ 'Select_range' => __('The number of joins that used ranges on the first table. (It\'s normally not critical even if this is big.)'),
+ 'Select_scan' => __('The number of joins that did a full scan of the first table.'),
+ 'Slave_open_temp_tables' => __('The number of temporary tables currently open by the slave SQL thread.'),
+ 'Slave_retried_transactions' => __('Total (since startup) number of times the replication slave SQL thread has retried transactions.'),
+ 'Slave_running' => __('This is ON if this server is a slave that is connected to a master.'),
+ 'Slow_launch_threads' => __('The number of threads that have taken more than slow_launch_time seconds to create.'),
+ 'Slow_queries' => __('The number of queries that have taken more than long_query_time seconds.'),
+ 'Sort_merge_passes' => __('The number of merge passes the sort algorithm has had to do. If this value is large, you should consider increasing the value of the sort_buffer_size system variable.'),
+ 'Sort_range' => __('The number of sorts that were done with ranges.'),
+ 'Sort_rows' => __('The number of sorted rows.'),
+ 'Sort_scan' => __('The number of sorts that were done by scanning the table.'),
+ 'Table_locks_immediate' => __('The number of times that a table lock was acquired immediately.'),
+ 'Table_locks_waited' => __('The number of times that a table lock could not be acquired immediately and a wait was needed. If this is high, and you have performance problems, you should first optimize your queries, and then either split your table or tables or use replication.'),
+ 'Threads_cached' => __('The number of threads in the thread cache. The cache hit rate can be calculated as Threads_created/Connections. If this value is red you should raise your thread_cache_size.'),
+ 'Threads_connected' => __('The number of currently open connections.'),
+ 'Threads_created' => __('The number of threads created to handle connections. If Threads_created is big, you may want to increase the thread_cache_size value. (Normally this doesn\'t give a notable performance improvement if you have a good thread implementation.)'),
+ 'Threads_running' => __('The number of threads that are not sleeping.')
+ );
+
+ /**
+ * define some alerts
+ */
+ // name => max value before alert
+ $alerts = array(
+ // lower is better
+ // variable => max value
+ 'Aborted_clients' => 0,
+ 'Aborted_connects' => 0,
+
+ 'Binlog_cache_disk_use' => 0,
+
+ 'Created_tmp_disk_tables' => 0,
+
+ 'Handler_read_rnd' => 0,
+ 'Handler_read_rnd_next' => 0,
+
+ 'Innodb_buffer_pool_pages_dirty' => 0,
+ 'Innodb_buffer_pool_reads' => 0,
+ 'Innodb_buffer_pool_wait_free' => 0,
+ 'Innodb_log_waits' => 0,
+ 'Innodb_row_lock_time_avg' => 10, // ms
+ 'Innodb_row_lock_time_max' => 50, // ms
+ 'Innodb_row_lock_waits' => 0,
+
+ 'Slow_queries' => 0,
+ 'Delayed_errors' => 0,
+ 'Select_full_join' => 0,
+ 'Select_range_check' => 0,
+ 'Sort_merge_passes' => 0,
+ 'Opened_tables' => 0,
+ 'Table_locks_waited' => 0,
+ 'Qcache_lowmem_prunes' => 0,
+
+ 'Qcache_free_blocks' => $server_status['Qcache_total_blocks'] / 5,
+ 'Slow_launch_threads' => 0,
+
+ // depends on Key_read_requests
+ // normaly lower then 1:0.01
+ 'Key_reads' => (0.01 * $server_status['Key_read_requests']),
+ // depends on Key_write_requests
+ // normaly nearly 1:1
+ 'Key_writes' => (0.9 * $server_status['Key_write_requests']),
+
+ 'Key_buffer_fraction' => 0.5,
+
+ // alert if more than 95% of thread cache is in use
+ 'Threads_cached' => 0.95 * $server_variables['thread_cache_size']
+
+ // higher is better
+ // variable => min value
+ //'Handler read key' => '> ',
+ );
+
?>
- </caption>
+<table class="data sortable" id="serverstatusvariables">
<col class="namecol" />
<col class="valuecol" />
<col class="descrcol" />
@@ -776,35 +1009,21 @@ if (! empty($section['title'])) {
<th><?php echo __('Description'); ?></th>
</tr>
</thead>
-<?php
- if (! empty($links[$section_name])) {
-?>
- <tfoot>
+ <!--<tfoot>
<tr class="tblFooters">
<th colspan="3" class="tblFooters">
-<?php
- foreach ($links[$section_name] as $link_name => $link_url) {
- if ('doc' == $link_name) {
- echo PMA_showMySQLDocu($link_url, $link_url);
- } else {
- echo '<a href="' . $link_url . '">' . $link_name . '</a>' . "\n";
- }
- }
- unset($link_url, $link_name);
-?>
</th>
</tr>
- </tfoot>
-<?php
- }
-?>
+ </tfoot>-->
<tbody>
-<?php
- $odd_row = false;
- foreach ($section['vars'] as $name => $value) {
+ <?
+
+ $odd_row = false;
+ foreach ($server_status as $name => $value) {
$odd_row = !$odd_row;
+ // $allocations
?>
- <tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; ?>">
+ <tr class="noclick <?php echo $odd_row ? 'odd' : 'even'; echo isset($allocationMap[$name])?' s_'.$allocationMap[$name]:''; ?>">
<th class="name"><?php echo htmlspecialchars($name) . PMA_showMySQLDocu('server-status-variables', 'server-status-variables', false, 'statvar_' . $name); ?>
</th>
<td class="value"><?php
@@ -820,7 +1039,7 @@ if (! empty($section['title'])) {
} elseif (is_numeric($value) && $value == (int) $value && $value > 1000) {
echo PMA_formatNumber($value, 3, 1);
} elseif (is_numeric($value) && $value == (int) $value) {
- echo PMA_formatNumber($value, 4, 0);
+ echo PMA_formatNumber($value, 3, 0);
} elseif (is_numeric($value)) {
echo PMA_formatNumber($value, 3, 1);
} else {
@@ -832,8 +1051,8 @@ if (! empty($section['title'])) {
?></td>
<td class="descr">
<?php
- if (isset($GLOBALS['strShowStatus' . $name . 'Descr'])) {
- echo $GLOBALS['strShowStatus' . $name . 'Descr'];
+ if (isset($strShowStatus[$name ])) {
+ echo $strShowStatus[$name];
}
if (isset($links[$name])) {
@@ -850,41 +1069,64 @@ if (! empty($section['title'])) {
?>
</td>
</tr>
-<?php
- }
- unset($name, $value);
-?>
+ <?php
+ }
+ ?>
</tbody>
</table>
-<?php
+ <?php
+}
+
+function createQueryChart($com_vars=FALSE) {
+ /**
+ * Chart generation
+ */
+ require_once './libraries/chart.lib.php';
+
+ if(!$com_vars)
+ $com_vars = PMA_DBI_fetch_result("SHOW GLOBAL STATUS LIKE 'Com\_%'", 0, 1);
+
+ // admin commands are not queries (e.g. they include COM_PING, which is excluded from $server_status['Questions'])
+ unset($com_vars['Com_admin_commands']);
+
+ arsort($com_vars);
+
+ $merge_minimum = array_sum($com_vars) * 0.005;
+ $merged_value = 0;
+
+ // remove zero values from the end, as well as merge together every value that is below 0.5%
+ // variable empty for Drizzle
+ if ($com_vars) {
+ while (($last_element=end($com_vars)) <= $merge_minimum) {
+ array_pop($com_vars);
+ $merged_value += $last_element;
+ }
+
+ $com_vars['Other'] = $merged_value;
+ return PMA_chart_status($com_vars);
}
+
+ return '';
}
-unset($section_name, $section, $sections, $server_status, $odd_row, $alerts);
-?>
-</div>
-<?php
-/* if the server works as master or slave in replication process, display useful information */
-if ($server_master_status || $server_slave_status)
-{
-?>
- <hr class="clearfloat" />
- <h3><a name="replication"></a><?php echo __('Replication status'); ?></h3>
-<?php
+/**
+ * cleanup of some deprecated values
+ */
+function cleanDeprecated(&$server_status) {
+ $deprecated = array(
+ 'Com_prepare_sql' => 'Com_stmt_prepare',
+ 'Com_execute_sql' => 'Com_stmt_execute',
+ 'Com_dealloc_sql' => 'Com_stmt_close',
+ );
- foreach ($replication_types as $type)
- {
- if (${"server_{$type}_status"}) {
- PMA_replication_print_status_table($type);
+ foreach ($deprecated as $old => $new) {
+ if (isset($server_status[$old])
+ && isset($server_status[$new])) {
+ unset($server_status[$old]);
}
}
- unset($types);
}
-?>
-
-</div>
-<?php
/**
* Sends the footer
*/
diff --git a/server_variables.php b/server_variables.php
index b04a049..00c8a85 100644
--- a/server_variables.php
+++ b/server_variables.php
@@ -14,6 +14,8 @@ if (! defined('PMA_NO_VARIABLES_IMPORT')) {
}
require_once './libraries/common.inc.php';
+$GLOBALS['js_include'][] = 'server_variables.js';
+
/**
* Does the common work
*/
@@ -52,7 +54,14 @@ $serverVarsGlobal = PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES;', 0, 1);
* Displays the page
*/
?>
-<table class="data">
+<fieldset id="tableFilter" style="display:none;">
+<legend>Filters</legend>
+<div class="formelement">
+ <label for="filterText">Containing the word:</label>
+ <input name="filterText" type="text" id="filterText" style="vertical-align: baseline;" />
+</div>
+</fieldset>
+<table class="data filteredData">
<thead>
<tr><th><?php echo __('Variable'); ?></th>
<th>
diff --git a/sql.php b/sql.php
index 61a4668..cb21b20 100644
--- a/sql.php
+++ b/sql.php
@@ -15,7 +15,14 @@ require_once './libraries/check_user_privileges.lib.php';
require_once './libraries/bookmark.lib.php';
$GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js';
-$GLOBALS['js_include'][] = 'pMap.js';
+
+if(isset($_SESSION['profiling'])) {
+ $GLOBALS['js_include'][] = 'highcharts/highcharts.js';
+ /* Files required for chart exporting */
+ $GLOBALS['js_include'][] = 'highcharts/exporting.js';
+ $GLOBALS['js_include'][] = 'canvg/canvg.js';
+ $GLOBALS['js_include'][] = 'canvg/rgbcolor.js';
+}
/**
* Defines the url to return to in case of error in a sql statement
@@ -890,9 +897,40 @@ else {
}
if (isset($profiling_results)) {
- PMA_profilingResults($profiling_results, true);
- }
+ // pma_token/url_query needed for chart export
+?>
+<script type="text/javascript">
+pma_token = '<?php echo $_SESSION[' PMA_token ']; ?>';
+url_query = '<?php echo isset($url_query)?$url_query:PMA_generate_common_url($db);?>';
+$(document).ready(createProfilingChart);
+</script>
+<?
+ echo '<fieldset><legend>' . __('Profiling') . '</legend>' . "\n";
+ echo '<div style="float: left;">';
+ echo '<table>' . "\n";
+ echo ' <tr>' . "\n";
+ echo ' <th>' . __('Status') . '</th>' . "\n";
+ echo ' <th>' . __('Time') . '</th>' . "\n";
+ echo ' </tr>' . "\n";
+
+ $chart_json = Array();
+ foreach($profiling_results as $one_result) {
+ echo ' <tr>' . "\n";
+ echo '<td>' . ucwords($one_result['Status']) . '</td>' . "\n";
+ echo '<td align="right">' . (PMA_formatNumber($one_result['Duration'],3,1)) . 's</td>' . "\n";
+ $chart_json[ucwords($one_result['Status'])] = $one_result['Duration'];
+ }
+ echo '</table>' . "\n";
+ echo '</div>';
+ //require_once './libraries/chart.lib.php';
+ echo '<div id="profilingchart" style="display:none;">';
+ //PMA_chart_profiling($profiling_results);
+ echo json_encode($chart_json);
+ echo '</div>';
+ echo '</fieldset>' . "\n";
+ }
+
// Displays the results in a table
if (empty($disp_mode)) {
// see the "PMA_setDisplayMode()" function in
diff --git a/tbl_chart.php b/tbl_chart.php
index 5853c45..817a8ea 100644
--- a/tbl_chart.php
+++ b/tbl_chart.php
@@ -19,7 +19,12 @@ if (! defined('PMA_NO_VARIABLES_IMPORT')) {
*/
require_once './libraries/common.inc.php';
-$GLOBALS['js_include'][] = 'pMap.js';
+$GLOBALS['js_include'][] = 'tbl_chart.js';
+$GLOBALS['js_include'][] = 'highcharts/highcharts.js';
+/* Files required for chart exporting */
+$GLOBALS['js_include'][] = 'highcharts/exporting.js';
+$GLOBALS['js_include'][] = 'canvg/canvg.js';
+$GLOBALS['js_include'][] = 'canvg/rgbcolor.js';
/**
* Runs common work
@@ -43,11 +48,6 @@ if (strlen($GLOBALS['table'])) {
}
/*
- * Import chart functions
- */
-require_once './libraries/chart.lib.php';
-
-/*
* Execute the query and return the result
*/
$data = array();
@@ -63,129 +63,79 @@ if (PMA_isValid($_REQUEST['chartSettings'], 'array')) {
$chartSettings = $_REQUEST['chartSettings'];
}
-// get the chart and settings after chart generation
-$chart = PMA_chart_results($data, $chartSettings);
-
-if (!empty($chart)) {
- $message = PMA_Message::success(__('Chart generated successfully.'));
-}
-else {
- $message = PMA_Message::error(__('The result of this query can\'t be used for a chart. See [a@./Documentation.html#faq6_29@Documentation]FAQ 6.29[/a]'));
-}
-
$url_params['db'] = $GLOBALS['db'];
$url_params['reload'] = 1;
/**
* Displays the page
*/
+// pma_token/url_query needed for chart export
?>
+<script type="text/javascript">
+pma_token = '<?php echo $_SESSION[' PMA_token ']; ?>';
+url_query = '<?php echo $url_query;?>';
+</script>
<!-- Display Chart options -->
<div id="div_view_options">
<form method="post" action="tbl_chart.php">
<?php echo PMA_generate_common_hidden_inputs($url_params); ?>
<fieldset>
<legend><?php echo __('Display chart'); ?></legend>
-
- <div style="float: right">
- <?php echo $chart; ?>
+ <div style="float:left;">
+ <input type="radio" name="chartType" value="bar"><?php echo __('Bar'); ?>
+ <input type="radio" name="chartType" value="column"><?php echo __('Column'); ?>
+ <input type="radio" name="chartType" value="line" checked><?php echo __('Line'); ?>
+ <input type="radio" name="chartType" value="spline"><?php echo __('Spline'); ?>
+ <input type="radio" name="chartType" value="pie"><?php echo __('Pie'); ?>
+ <span class="barStacked" style="display:none;">
+ <input type="checkbox" name="barStacked" value="1"><?php echo __('Stacked'); ?>
+ </span>
+ <br>
+ <input type="text" name="chartTitle" value="<?php echo __('Chart title'); ?>">
+ <?php $keys = array_keys($data[0]);
+ $yaxis=-1;
+ if(count($keys)>1) {
+ echo '<br>';
+ echo __('X-Axis:'); ?> <select name="chartXAxis">
+ <?php
+
+ foreach($keys as $idx=>$key) {
+ if($yaxis==-1 && ($idx==count($data[0])-1 || preg_match("/(date|time)/i",$key))) {
+ echo '<option value="'.$idx.'" selected>'.$key.'</option>';
+ $yaxis=$idx;
+ } else {
+ echo '<option value="'.$idx.'">'.$key.'</option>';
+ }
+ }
+
+ ?>
+ </select><br>
+ <?php echo __('Series:'); ?>
+ <select name="chartSeries">
+ <option value="columns"><?php echo __('The remaining columns'); ?></option>
+ <?php
+ foreach($keys as $idx=>$key) {
+ echo '<option>'.$key.'</option>';
+ }
+ ?>
+ </select>
+ <?php
+ }
+ ?>
+
+ </div>
+ <div style="float:left; padding-left:40px;">
+ <?php echo __('X-Axis label:'); ?> <input style="margin-top:0;" type="text" name="xaxis_label" value="<?php echo ($yaxis==-1)?__('X Values'):$keys[$yaxis]; ?>"><br>
+ <?php echo __('Y-Axis label:'); ?> <input type="text" name="yaxis_label" value="<?php echo __('Y Values'); ?>">
+ </div>
+ <p style="clear:both;"> </p>
+ <div id="resizer" style="width:600px; height:400px;">
+ <div id="inner-resizer">
+ <div id="querychart" style="display:none;">
+ <?php echo json_encode($data); ?>
+ </div>
+ </div>
</div>
-
- <input type="hidden" name="sql_query" id="sql_query" value="<?php echo htmlspecialchars($sql_query); ?>" />
-
- <table>
- <tr><td><label for="width"><?php echo __("Width"); ?></label></td>
- <td><input type="text" name="chartSettings[width]" id="width" value="<?php echo (isset($chartSettings['width']) ? htmlspecialchars($chartSettings['width']) : ''); ?>" /></td>
- </tr>
-
- <tr><td><label for="height"><?php echo __("Height"); ?></label></td>
- <td><input type="text" name="chartSettings[height]" id="height" value="<?php echo (isset($chartSettings['height']) ? htmlspecialchars($chartSettings['height']) : ''); ?>" /></td>
- </tr>
-
- <tr><td><label for="titleText"><?php echo __("Title"); ?></label></td>
- <td><input type="text" name="chartSettings[titleText]" id="titleText" value="<?php echo (isset($chartSettings['titleText']) ? htmlspecialchars($chartSettings['titleText']) : ''); ?>" /></td>
- </tr>
-
- <?php if ($chartSettings['type'] != 'pie' && $chartSettings['type'] != 'radar') { ?>
- <tr><td><label for="xLabel"><?php echo __("X Axis label"); ?></label></td>
- <td><input type="text" name="chartSettings[xLabel]" id="xLabel" value="<?php echo (isset($chartSettings['xLabel']) ? htmlspecialchars($chartSettings['xLabel']) : ''); ?>" /></td>
- </tr>
-
- <tr><td><label for="yLabel"><?php echo __("Y Axis label"); ?></label></td>
- <td><input type="text" name="chartSettings[yLabel]" id="yLabel" value="<?php echo (isset($chartSettings['yLabel']) ? htmlspecialchars($chartSettings['yLabel']) : ''); ?>" /></td>
- </tr>
- <?php } ?>
-
- <tr><td><label for="areaMargins"><?php echo __("Area margins"); ?></label></td>
- <td>
- <input type="text" name="chartSettings[areaMargins][]" size="2" value="<?php echo (isset($chartSettings['areaMargins'][0]) ? htmlspecialchars($chartSettings['areaMargins'][0]) : ''); ?>" />
- <input type="text" name="chartSettings[areaMargins][]" size="2" value="<?php echo (isset($chartSettings['areaMargins'][1]) ? htmlspecialchars($chartSettings['areaMargins'][1]) : ''); ?>" />
- <input type="text" name="chartSettings[areaMargins][]" size="2" value="<?php echo (isset($chartSettings['areaMargins'][2]) ? htmlspecialchars($chartSettings['areaMargins'][2]) : ''); ?>" />
- <input type="text" name="chartSettings[areaMargins][]" size="2" value="<?php echo (isset($chartSettings['areaMargins'][3]) ? htmlspecialchars($chartSettings['areaMargins'][3]) : ''); ?>" />
- </td>
- </tr>
-
- <?php if ($chartSettings['legend'] == true) { ?>
- <tr><td><label for="legendMargins"><?php echo __("Legend margins"); ?></label></td>
- <td>
- <input type="text" name="chartSettings[legendMargins][]" size="2" value="<?php echo htmlspecialchars($chartSettings['legendMargins'][0]); ?>" />
- <input type="text" name="chartSettings[legendMargins][]" size="2" value="<?php echo htmlspecialchars($chartSettings['legendMargins'][1]); ?>" />
- <input type="text" name="chartSettings[legendMargins][]" size="2" value="<?php echo htmlspecialchars($chartSettings['legendMargins'][2]); ?>" />
- <input type="text" name="chartSettings[legendMargins][]" size="2" value="<?php echo htmlspecialchars($chartSettings['legendMargins'][3]); ?>" />
- </td>
- </tr>
- <?php } ?>
-
- <tr><td><label for="type"><?php echo __("Type"); ?></label></td>
- <td>
- <input type="radio" name="chartSettings[type]" value="bar" <?php echo ($chartSettings['type'] == 'bar' ? 'checked' : ''); ?>><?php echo __('Bar'); ?>
- <input type="radio" name="chartSettings[type]" value="line" <?php echo ($chartSettings['type'] == 'line' ? 'checked' : ''); ?>><?php echo __('Line'); ?>
- <input type="radio" name="chartSettings[type]" value="radar" <?php echo ($chartSettings['type'] == 'radar' ? 'checked' : ''); ?>><?php echo __('Radar'); ?>
- <?php if ($chartSettings['multi'] == false) { ?>
- <input type="radio" name="chartSettings[type]" value="pie" <?php echo ($chartSettings['type'] == 'pie' ? 'checked' : ''); ?>><?php echo __('Pie'); ?>
- <?php } ?>
- </td>
- </tr>
-
- <?php if ($chartSettings['type'] == 'bar' && isset($chartSettings['multi']) && $chartSettings['multi'] == true) { ?>
- <tr><td><label for="barType"><?php echo __("Bar type"); ?></label></td>
- <td>
- <input type="radio" name="chartSettings[barType]" value="stacked" <?php echo ($chartSettings['barType'] == 'stacked' ? 'checked' : ''); ?>><?php echo __('Stacked'); ?>
- <input type="radio" name="chartSettings[barType]" value="multi" <?php echo ($chartSettings['barType'] == 'multi' ? 'checked' : ''); ?>><?php echo __('Multi'); ?>
- </td>
- </tr>
- <?php } ?>
-
- <tr><td><label for="continuous"><?php echo __("Continuous image"); ?></label></td>
- <td>
- <input type="checkbox" name="chartSettings[continuous]" id="continuous" <?php echo ($chartSettings['continuous'] == 'on' ? 'checked="checked"' : ''); ?>>
- <?php echo PMA_showHint(PMA_sanitize(__('For compatibility reasons the chart image is segmented by default, select this to draw the whole chart in one image.'))) ?>
- </td>
- </tr>
-
- <tr><td><label for="fontSize"><?php echo __("Font size"); ?></label></td>
- <td><input type="text" name="chartSettings[fontSize]" id="fontSize" value="<?php echo (isset($chartSettings['fontSize']) ? htmlspecialchars($chartSettings['fontSize']) : ''); ?>" /></td>
- </tr>
-
- <?php if ($chartSettings['type'] == 'radar') { ?>
- <tr><td colspan="2">
- <p>
- <?php echo __('When drawing a radar chart all values are normalized to a range [0..10].'); ?>
- </p>
- </td></tr>
- <?php } ?>
-
- <tr><td colspan="2">
- <p>
- <?php echo __('Note that not every result table can be put to the chart. See <a href="./Documentation.html#faq6_29" target="Documentation">FAQ 6.29</a>'); ?>
- </p>
- </td></tr>
-
- </table>
-
-</fieldset>
-<fieldset class="tblFooters">
- <input type="submit" name="displayChart" value="<?php echo __('Redraw'); ?>" />
</fieldset>
</form>
</div>
diff --git a/themes/original/css/theme_right.css.php b/themes/original/css/theme_right.css.php
index 855d611..f2bbfb8 100644
--- a/themes/original/css/theme_right.css.php
+++ b/themes/original/css/theme_right.css.php
@@ -678,6 +678,7 @@ ul#topmenu ul {
list-style-type: none;
display: none;
border: 1px #666 solid;
+ z-index: 2;
}
ul#topmenu li:hover ul, ul#topmenu .submenuhover ul {
@@ -951,17 +952,58 @@ div#tablestatistics table {
/* serverstatus */
+
+img.sortableIcon {
+ width:16px;
+ height:16px;
+ float:right;
+ background-repeat:no-repeat;
+}
+
+table#serverstatusqueriesdetails th img.sortableIcon, table#serverstatusvariables th img.sortableIcon {
+ background-image:url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>s_sortable.png);
+}
+table#serverstatusqueriesdetails th.headerSortUp img.sortableIcon, table#serverstatusvariables th.headerSortUp img.sortableIcon {
+ background-image:url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>s_asc.png);
+}
+table#serverstatusqueriesdetails th.headerSortDown img.sortableIcon, table#serverstatusvariables th.headerSortDown img.sortableIcon {
+ background-image:url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>s_desc.png);
+}
+
+.statuslinks {
+ float: <?php echo $right; ?>;
+}
+
+/* Also used for the variables page */
+fieldset#tableFilter {
+ margin-bottom:1em;
+}
+
+div#serverStatusTabs {
+ margin-top:1em;
+}
+
div#serverstatus table caption a.top {
float: <?php echo $right; ?>;
}
-div#serverstatus div#serverstatusqueriesdetails table,
-div#serverstatus table#serverstatustraffic,
-div#serverstatus table#serverstatusconnections {
+div#serverstatusquerieschart {
+ float:<?php echo $right; ?>;
+}
+
+div#serverstatus table#serverstatusqueriesdetails {
+ float: <?php echo $left; ?>;
+}
+
+table#serverstatustraffic {
+ float: <?php echo $left; ?>;
+}
+table#serverstatusconnections {
float: <?php echo $left; ?>;
+ margin-<?php echo $left; ?>: 30px;
}
-#serverstatussection,
+
.clearfloat {
clear: both;
}
@@ -1013,6 +1055,15 @@ div#querywindowcontainer fieldset {
}
/* END querywindow */
+/* profiling */
+
+div#profilingchart {
+ width:550px;
+ height:370px;
+ float:left;
+}
+
+/* END profiling */
/* querybox */
@@ -1423,12 +1474,12 @@ select#db_select, select#table_select {
}
.export_sub_options li.subgroup {
- display: inline-block;
- margin-top: 0;
+ display: inline-block;
+ margin-top: 0;
}
.export_sub_options li {
- margin-bottom: 0;
+ margin-bottom: 0;
}
#quick_or_custom, #output_quick_export {
@@ -1604,7 +1655,7 @@ iframe.IE_hack {
padding: 0;
list-style: none;
color: #9A0000;
- font-size: small;
+ font-size: small;
}
.config-form fieldset th {
diff --git a/themes/original/img/cleardot.gif b/themes/original/img/cleardot.gif
new file mode 100644
index 0000000..35d42e8
Binary files /dev/null and b/themes/original/img/cleardot.gif differ
diff --git a/themes/original/jquery/jquery-ui-1.8.custom.css b/themes/original/jquery/jquery-ui-1.8.custom.css
index 1f4e40b..b2ad14f 100644
--- a/themes/original/jquery/jquery-ui-1.8.custom.css
+++ b/themes/original/jquery/jquery-ui-1.8.custom.css
@@ -51,7 +51,7 @@
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
-.ui-widget-content a { color: #222222; }
+/*.ui-widget-content a { color: #222222; }*/
.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
.ui-widget-header a { color: #222222; }
diff --git a/themes/pmahomme/css/theme_right.css.php b/themes/pmahomme/css/theme_right.css.php
index 17ab26c..7092358 100644
--- a/themes/pmahomme/css/theme_right.css.php
+++ b/themes/pmahomme/css/theme_right.css.php
@@ -88,7 +88,7 @@ a:hover {
}
#initials_table {
- background:#f3f3f3;
+ background:#f3f3f3;
border:1px solid #aaa;
margin-bottom:10px;
-moz-border-radius:5px;
@@ -152,7 +152,7 @@ form {
}
input[type=text]{
- border-radius:2px;
+ border-radius:2px;
-moz-border-radius:2px;
-webkit-border-radius:2px;
@@ -169,7 +169,7 @@ input[type=text]{
}
input[type=password]{
- border-radius:2px;
+ border-radius:2px;
-moz-border-radius:2px;
-webkit-border-radius:2px;
@@ -177,7 +177,7 @@ input[type=password]{
-moz-box-shadow:0 1px 2px #ddd;
-webkit-box-shadow:0 1px 2px #ddd;
- background:url(./themes/pmahomme/img/input_bg.gif);
+ background:url(./themes/pmahomme/img/input_bg.gif);
border:1px solid #aaa;
color:#555555;
padding:4px;
@@ -188,17 +188,17 @@ input[type=password]{
input[type=submit]{
font-weight:bold;
margin-left:14px;
- border: 1px solid #aaa;
- padding: 3px 7px;
- color: #111;
- text-decoration: none;
- background: #ddd;
+ border: 1px solid #aaa;
+ padding: 3px 7px;
+ color: #111;
+ text-decoration: none;
+ background: #ddd;
border-radius: 12px;
- -webkit-border-radius: 12px;
- -moz-border-radius: 12px;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
- text-shadow: 0px 1px 0px #fff;
+ text-shadow: 0px 1px 0px #fff;
background-image: url(./themes/svg_gradient.php?from=ffffff&to=cccccc);
background-size: 100% 100%;
@@ -219,8 +219,8 @@ input[type=submit]:hover{ position: relative;
}
input[type=submit]:active{ position: relative;
- top: 1px;
- left: 1px;
+ top: 1px;
+ left: 1px;
}
textarea {
overflow: visible;
@@ -237,7 +237,7 @@ fieldset {
padding: 1.5em;
background: #eee;
text-shadow:0 1px 0 #fff;
- -moz-box-shadow: 1px 1px 2px #fff inset;
+ -moz-box-shadow: 1px 1px 2px #fff inset;
-webkit-box-shadow: 1px 1px 2px #fff inset;
box-shadow: 1px 1px 2px #fff inset;
}
@@ -284,20 +284,22 @@ table{border-collapse:collapse;}
th{border-right:1px solid #fff; text-align:left;}
-img,
-input,
-select,
-button {
+img, button {
vertical-align: middle;
}
+input[type="checkbox"],input[type="radio"] {
+ vertical-align: -11%;
+}
+
+
select{
-moz-border-radius:2px;
-webkit-border-radius:2px;
border-radius:2px;
-moz-box-shadow:0 1px 2px #ddd;
- -webkit-box-shadow:0 1px 2px #ddd;
+ -webkit-box-shadow:0 1px 2px #ddd;
box-shadow:0 1px 2px #ddd;
border:1px solid #aaa;
@@ -483,7 +485,7 @@ img.lightbulb {
/* MySQL Parser */
.syntax {
- font-family: Verdan, Arial, Tahoma;
+ font-family: Verdan, Arial, Tahoma;
font-size: 110%;
}
@@ -656,7 +658,7 @@ div.footnotes {
}
.error {
- border:1px solid maroon !important;
+ border:1px solid maroon !important;
color: #000;
background:pink;
}
@@ -861,6 +863,7 @@ ul#topmenu ul {
list-style-type: none;
display: none;
border: 1px #ddd solid;
+ z-index: 2;
}
ul#topmenu li:hover {
@@ -932,7 +935,7 @@ ul#topmenu > li {
/* default tab styles */
ul#topmenu a, ul#topmenu span {
- padding:10px;
+ padding:10px;
}
ul#topmenu ul a {
@@ -1155,30 +1158,67 @@ text-shadow:0 1px 0 #000000;
/* serverstatus */
+
+img.sortableIcon {
+ width:16px;
+ height:16px;
+ float:right;
+ background-repeat:no-repeat;
+}
+
+table#serverstatusqueriesdetails th.headerSortUp img.sortableIcon, table#serverstatusvariables th.headerSortUp img.sortableIcon {
+ background-image:url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>s_asc.png);
+}
+table#serverstatusqueriesdetails th.headerSortDown img.sortableIcon, table#serverstatusvariables th.headerSortDown img.sortableIcon {
+ background-image:url(<?php echo $_SESSION['PMA_Theme']->getImgPath(); ?>s_desc.png);
+}
+
+.statuslinks {
+ float: <?php echo $right; ?>;
+}
+
+/* Also used for the variables page */
+fieldset#tableFilter {
+ margin-bottom:1em;
+}
+
+div#serverStatusTabs {
+ margin-top:1em;
+}
+
div#serverstatus table caption a.top {
float: <?php echo $right; ?>;
}
-div#serverstatus div#serverstatusqueriesdetails table,
-div#serverstatus table#serverstatustraffic,
-div#serverstatus table#serverstatusconnections {
+div#serverstatusquerieschart {
+ float:<?php echo $right; ?>;
+}
+
+div#serverstatus table#serverstatusqueriesdetails {
float: <?php echo $left; ?>;
}
-#serverstatussection,
.clearfloat {
clear: both;
}
-div#serverstatussection table {
+table#serverstatusvariables {
width: 100%;
margin-bottom: 1em;
}
-div#serverstatussection table .name {
+table#serverstatusvariables .name {
width: 18em;
+ white-space:nowrap;
}
-div#serverstatussection table .value {
+table#serverstatusvariables .value {
width: 6em;
}
+table#serverstatustraffic {
+ float: <?php echo $left; ?>;
+}
+table#serverstatusconnections {
+ float: <?php echo $left; ?>;
+ margin-<?php echo $left; ?>: 30px;
+}
div#serverstatus table tbody td.descr a,
div#serverstatus table .tblFooters a {
@@ -1205,6 +1245,26 @@ div#querywindowcontainer fieldset {
}
/* END querywindow */
+/* profiling */
+
+div#profilingchart {
+ width:550px;
+ height:370px;
+ float:left;
+}
+
+/* END profiling */
+
+/* table charting */
+
+#resizer {
+ border: 1px solid silver;
+}
+#inner-resizer { /* make room for the resize handle */
+ padding: 10px;
+}
+
+/* END table charting */
/* querybox */
@@ -1238,39 +1298,42 @@ div#querywindowcontainer fieldset {
#serverstatus p a{color:#fff;text-decoration:underline;}
#serverstatus h3
{
- margin:35px 0px;font-weight:normal;color:#999;font-size:1.7em;
+ margin: 15px 0;
+ font-weight:normal;
+ color:#999;
+ font-size:1.7em;
}
#sectionlinks{
- padding:16px;
+ padding:16px;
background:#f3f3f3;
border:1px solid #aaa;
border-radius:5px;
-webkit-border-radius:5px;
-moz-border-radius:5px;
- box-shadow:0px 1px 1px #fff inset;
+ box-shadow:0px 1px 1px #fff inset;
-webkit-box-shadow:0px 1px 1px #fff inset;
-moz-box-shadow:0px 1px 1px #fff inset;
}
-#sectionlinks a, #statuslinks a{
- font-size:0.88em;
+#sectionlinks a, .statuslinks a{
+ font-size:0.88em;
font-weight:bold;
text-shadow: 0px 1px 0px #fff;
line-height:35px;
- margin-left:7px;
- border: 1px solid #aaa;
- padding: 5px 10px;
- color: #111;
- text-decoration: none;
- background: #ddd;
+ margin-left:7px;
+ border: 1px solid #aaa;
+ padding: 5px 10px;
+ color: #111;
+ text-decoration: none;
+ background: #ddd;
white-space: nowrap;
- border-radius: 20px;
- -webkit-border-radius: 20px;
- -moz-border-radius: 20px;
- box-shadow: 1px 1px 2px rgba(0,0,0,.5);
- /*
+ border-radius: 20px;
+ -webkit-border-radius: 20px;
+ -moz-border-radius: 20px;
+ box-shadow: 1px 1px 2px rgba(0,0,0,.5);
+ /*
-webkit-box-shadow: 1px 1px 2px rgba(0,0,0,.5);
- -moz-box-shadow: 1px 1px 2px rgba(0,0,0,.5);
- text-shadow: #fff 0px 1px 0px;
+ -moz-box-shadow: 1px 1px 2px rgba(0,0,0,.5);
+ text-shadow: #fff 0px 1px 0px;
*/
background-image: url(./themes/svg_gradient.php?from=ffffff&to=cccccc);
background-size: 100% 100%;
@@ -1279,7 +1342,7 @@ div#querywindowcontainer fieldset {
background: -o-linear-gradient(top, #ffffff, #cccccc);
<?php echo PMA_ieFilter('#ffffff', '#cccccc'); ?>
}
-#sectionlinks a:hover, #statuslinks a:hover{
+#sectionlinks a:hover, .statuslinks a:hover{
background-image: url(./themes/svg_gradient.php?from=cccccc&to=dddddd);
background-size: 100% 100%;
background: -webkit-gradient(linear, left top, left bottom, from(#cccccc), to(#dddddd));
@@ -1312,9 +1375,9 @@ textarea#sqlquery {
-moz-border-radius:4px;
-webkit-border-radius:4px;
border-raduis:4px
- border:1px solid #aaa;
- padding:5px;
- font-family:inherit;
+ border:1px solid #aaa;
+ padding:5px;
+ font-family:inherit;
}
textarea#sql_query_edit{
height:7em;
@@ -1553,7 +1616,7 @@ code.sql, div.sqlvalidate {
background-color: #bbb;
padding: 0.1em 0.3em;
margin-top: 0;
- color:#fff;
+ color:#fff;
font-size:1.6em;
font-weight:normal;
text-shadow:0 1px 0 #777;
@@ -1690,17 +1753,17 @@ input[type=text].invalid_value,
.exportoptions #buttonGo, .importoptions #buttonGo {
font-weight:bold;
margin-left:14px;
- border: 1px solid #aaa;
- padding: 5px 12px;
- color: #111;
- text-decoration: none;
- background: #ddd;
+ border: 1px solid #aaa;
+ padding: 5px 12px;
+ color: #111;
+ text-decoration: none;
+ background: #ddd;
border-radius: 12px;
- -webkit-border-radius: 12px;
- -moz-border-radius: 12px;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
- text-shadow: 0px 1px 0px #fff;
+ text-shadow: 0px 1px 0px #fff;
background-image: url(./themes/svg_gradient.php?from=ffffff&to=cccccc);
background-size: 100% 100%;
@@ -1750,12 +1813,12 @@ select#db_select, select#table_select {
}
.export_sub_options li.subgroup {
- display: inline-block;
- margin-top: 0;
+ display: inline-block;
+ margin-top: 0;
}
.export_sub_options li {
- margin-bottom: 0;
+ margin-bottom: 0;
}
#quick_or_custom, #output_quick_export {
@@ -1943,7 +2006,7 @@ iframe.IE_hack {
padding: 0;
list-style: none;
color: #9A0000;
- font-size: small;
+ font-size: small;
}
.config-form fieldset th {
diff --git a/themes/pmahomme/img/cleardot.gif b/themes/pmahomme/img/cleardot.gif
new file mode 100644
index 0000000..35d42e8
Binary files /dev/null and b/themes/pmahomme/img/cleardot.gif differ
diff --git a/themes/pmahomme/img/s_sortable.png b/themes/pmahomme/img/s_sortable.png
new file mode 100644
index 0000000..0a34142
Binary files /dev/null and b/themes/pmahomme/img/s_sortable.png differ
diff --git a/themes/pmahomme/jquery/jquery-ui-1.8.custom.css b/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
index 9446f01..d6a599f 100644
--- a/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
+++ b/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
@@ -51,7 +51,7 @@
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
-.ui-widget-content a { color: #222222; }
+/*.ui-widget-content a { color: #222222; }*/
.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
.ui-widget-header a { color: #222222; }
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, master, updated. RELEASE_3_4_2-3428-g8cd155d
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, master has been updated
via 8cd155d2e31d13d32f2779d15f7b39265657c50a (commit)
via 8cebaca19b08f8faa5eaebfdb2d87c466f274db8 (commit)
via c4c8fcb2b5fbf88fbe6dbcc987fd104d060eac4d (commit)
via 36cb783d8dd2ab378fc60be549f1f696ea1b5df9 (commit)
via d6057b37f38a9b20b8db78f7d1c08a188028c1a8 (commit)
via 1cbbeecd8ff21f0e5b772d1983cfad1b0fab01b7 (commit)
via 574a7d75c960b6fd195e67f89ca57657d2b02811 (commit)
from ca6f3b0ac27ab2124c196dda8484397d99656e0b (commit)
- Log -----------------------------------------------------------------
commit 8cd155d2e31d13d32f2779d15f7b39265657c50a
Merge: 36cb783d8dd2ab378fc60be549f1f696ea1b5df9 8cebaca19b08f8faa5eaebfdb2d87c466f274db8
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 09:16:28 2011 +0200
Merge remote-tracking branch 'origin/QA_3_4'
commit 36cb783d8dd2ab378fc60be549f1f696ea1b5df9
Author: stoyanster <stoyanster(a)gmail.com>
Date: Tue Jun 14 08:31:41 2011 +0200
Translation update done using Pootle.
commit d6057b37f38a9b20b8db78f7d1c08a188028c1a8
Author: stoyanster <stoyanster(a)gmail.com>
Date: Tue Jun 14 08:26:43 2011 +0200
Translation update done using Pootle.
commit 1cbbeecd8ff21f0e5b772d1983cfad1b0fab01b7
Author: stoyanster <stoyanster(a)gmail.com>
Date: Tue Jun 14 08:23:43 2011 +0200
Translation update done using Pootle.
commit 574a7d75c960b6fd195e67f89ca57657d2b02811
Author: stoyanster <stoyanster(a)gmail.com>
Date: Tue Jun 14 08:23:18 2011 +0200
Translation update done using Pootle.
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 1 +
db_search.php | 2 +-
po/bg.po | 10 +++++-----
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 288e572..60b046b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -23,6 +23,7 @@
- patch #3311539 [edit] Inline edit does not escape backslashes
- bug #3313210 [interface] Columns class sometimes changed for nothing
- patch #3313326 [interface] Some tooltips do not disappear
+- bug #3315720 [search] Fix search in non unicode tables
3.4.2.0 (2011-06-07)
- bug #3301249 [interface] Iconic table operations does not remove inline edit label
diff --git a/db_search.php b/db_search.php
index fba68d1..d93fd74 100644
--- a/db_search.php
+++ b/db_search.php
@@ -178,7 +178,7 @@ if (isset($_REQUEST['submit_search'])) {
$thefieldlikevalue = array();
foreach ($tblfields as $tblfield) {
if (! isset($field) || strlen($field) == 0 || $tblfield == $field) {
- $thefieldlikevalue[] = PMA_backquote($tblfield)
+ $thefieldlikevalue[] = 'CONVERT(' . PMA_backquote($tblfield) . ' USING utf8)'
. ' ' . $like_or_regex . ' '
. "'" . $automatic_wildcard
. $search_word
diff --git a/po/bg.po b/po/bg.po
index e7c05b1..7f68cfd 100644
--- a/po/bg.po
+++ b/po/bg.po
@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: phpMyAdmin 3.5.0-dev\n"
"Report-Msgid-Bugs-To: phpmyadmin-devel(a)lists.sourceforge.net\n"
"POT-Creation-Date: 2011-06-07 06:41-0400\n"
-"PO-Revision-Date: 2011-06-13 13:46+0200\n"
+"PO-Revision-Date: 2011-06-14 08:31+0200\n"
"Last-Translator: <stoyanster(a)gmail.com>\n"
"Language-Team: bulgarian <bg(a)li.org>\n"
"Language: bg\n"
@@ -1609,16 +1609,14 @@ msgid "Could not save recent table"
msgstr "Конфигурацията не може да бъде запазена"
#: libraries/RecentTable.class.php:148
-#, fuzzy
#| msgid "Count tables"
msgid "Recent tables"
-msgstr "Брой таблици"
+msgstr "Отваряни таблици"
#: libraries/RecentTable.class.php:154
-#, fuzzy
#| msgid "There are no files to upload"
msgid "There are no recent tables"
-msgstr "Няма файлве за качване"
+msgstr "Няма наскоро отваряни таблици"
#: libraries/StorageEngine.class.php:194
msgid ""
@@ -6681,6 +6679,8 @@ msgid ""
"Your preferences will be saved for current session only. Storing them "
"permanently requires %sphpMyAdmin configuration storage%s."
msgstr ""
+"Вашите настройки ще бъдат пазени само за текущата сесия. За да ги използвате "
+"постоянно е необходимо %sконфигурационно хранилище%s."
#: libraries/user_preferences.lib.php:142
msgid "Could not save configuration"
hooks/post-receive
--
phpMyAdmin
1
0

[Phpmyadmin-git] [SCM] phpMyAdmin branch, QA_3_4, updated. RELEASE_3_4_2-21-g8cebaca
by Michal Čihař 14 Jun '11
by Michal Čihař 14 Jun '11
14 Jun '11
The branch, QA_3_4 has been updated
via 8cebaca19b08f8faa5eaebfdb2d87c466f274db8 (commit)
from c4c8fcb2b5fbf88fbe6dbcc987fd104d060eac4d (commit)
- Log -----------------------------------------------------------------
commit 8cebaca19b08f8faa5eaebfdb2d87c466f274db8
Author: Michal Čihař <mcihar(a)novell.com>
Date: Tue Jun 14 09:15:07 2011 +0200
bug #3315720 [search] Fix search in non unicode tables
We need to convert field to unicode because the connection is in utf-8
and we might give utf-8 input as well.
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 1 +
db_search.php | 2 +-
2 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index ee86372..2d2d054 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,7 @@
- patch #3311539 [edit] Inline edit does not escape backslashes
- bug #3313210 [interface] Columns class sometimes changed for nothing
- patch #3313326 [interface] Some tooltips do not disappear
+- bug #3315720 [search] Fix search in non unicode tables
3.4.2.0 (2011-06-07)
- bug #3301249 [interface] Iconic table operations does not remove inline edit label
diff --git a/db_search.php b/db_search.php
index fba68d1..d93fd74 100644
--- a/db_search.php
+++ b/db_search.php
@@ -178,7 +178,7 @@ if (isset($_REQUEST['submit_search'])) {
$thefieldlikevalue = array();
foreach ($tblfields as $tblfield) {
if (! isset($field) || strlen($field) == 0 || $tblfield == $field) {
- $thefieldlikevalue[] = PMA_backquote($tblfield)
+ $thefieldlikevalue[] = 'CONVERT(' . PMA_backquote($tblfield) . ' USING utf8)'
. ' ' . $like_or_regex . ' '
. "'" . $automatic_wildcard
. $search_word
hooks/post-receive
--
phpMyAdmin
1
0