The branch, master has been updated via e0eaac2e144d1e738510839b93e5d9b0e9c020e8 (commit) via 51c9c1763ea70c36e0d22cc865b48f5186da1fbc (commit) via 0ea410c3e4fe6093723f65860f643cc096d78af2 (commit) via 58f8ebc23c2982a299773b3c2c15f144b089c27a (commit) via dcf470e6d752b80ffaf3d641eb0000e47f341d05 (commit) via e141acf5c6bec609cfcd72c9ac131942d59f8d2e (commit) via e076ec85e174038c7ffe17f701342e52f4413afe (commit) via 6fb5591f4edd26058056718c83305ad12501ae8e (commit) via d8edf38d0d66d2b30d7e8d56f85334c326527a31 (commit) via b477c4cb1a0ccbe75bd748470d590d7805b48e5d (commit) via b81a22e41f5027e85361997dc8d9936d86957f7f (commit) via b32e5879fd10bb7e650442e2e4af54f2ecc72c4c (commit) via 4d8d81f4e5191a19baa540c68003d99a0f7289b3 (commit) via 6c9f949613e766719a2a8bfd3a9919cd102f452a (commit) via 62b443c0691fa119958f6ac09ee6c6694ba54f3f (commit) via 0b90202b8ffb79b8246bb987248311757080f7aa (commit) via eb3f96ac51c78ec51827a2636c1757d8707377fa (commit) via d6185d7ace9d1e7fdba08568f3ded0e2b16462d3 (commit) via 59f220eb1307fd881ba43e809e0a729432d83d86 (commit) via 47f67613a2066b69bdf03ff8e43a857f59e8e230 (commit) via 68f4a8daf70a832f2f7d87c379250d6816895c0a (commit) via 55089f12dbd9493a92bab79e2d11a956368ad61f (commit) via fe77ca741c4ae9e89fdfd373c10b3cc36e068dc8 (commit) via 1c1d555002ec8b85ff134a8d9b572c9b4c8e51f7 (commit) via 0b620df9129c11087bde584b6f6f9e00b90e0f66 (commit) via 57614902eb7e7db60025a196ca79fa4170e22141 (commit) via 88b9e72597030218d781381ff3970efa30d65fbf (commit) via 4577d63b4d85e6f8c9700bf4fb80d3d9e865995a (commit) via b0888714b6ab232d7b0dfc24b38d0da9875eed70 (commit) via aecac3a6913aa6c93458f8df9edaf7cd4a055980 (commit) via c6ce98be18ae87c655721ef4559fbb42d3afdce2 (commit) via 34ec78abb5e222fc2fcf7bee31fd7cd73e0b7366 (commit) via 4f62c6c2ea45583539f3d191ad9bcf072b684d10 (commit) via 8b7e8257b34f5ae9ad60030d4d9f1d10f338b174 (commit) via 039e30450c7c81739b293b8138cabba2c25f5606 (commit) via 53c071941a7523ae35f8fdacb6ee9acdbda1aebc (commit) via 0604e81526cfd591b0ced14823a8a00c522ff66a (commit) via 3afe966b5196c0cc04d3d52ad61392a82949b0a1 (commit) via b7daf5d98571d9ec39c001f1b8065fcd490f4a8c (commit) via afe5f63c4bb5f34355a465ee8f85f620ad01d959 (commit) via 25a59f23d5d97e0d9459feba97eb63959c7b4f76 (commit) via e3736762aeecf47c4c4a062af49c5f5edc61448f (commit) via bcbd662f4856e3d7c413d182d2cf017c556daf64 (commit) via 557eb2096bedb5f46f49d10b8e3349f7273d4fe3 (commit) via 2f78d2d147ae22563e1c0d44e9f0a60155efee0e (commit) via c40e145597f1dddf04a99f3f776c1e83637b3653 (commit) via 979aa9a7ed7b4179d41de6998d6e8b4183611d22 (commit) via 3df1126c8539d410c0bc376a04c94401dbc109ed (commit) via 6b806f20477abf750fe071f89cb74c1d9a095f53 (commit) via e5c8b5a8945810af7178901056b7330f92191e8e (commit) via 08e048b3077c6d68e424372a7fc6f6958030ff55 (commit) via 7eedd0387db12235f529b11158683dca6c4b6522 (commit) via cd1cbce4efe567a05cd9c002987005b8ecc4b6c1 (commit) via 48f2caccae987a242912d4f17e87ebe3043c70f2 (commit) via 45e740e96b8f0b042d7974d2abc617095f7cb4eb (commit) via f53b1a52941fa839c76356a6de80c0fd11e97519 (commit) via 881bd0909febd4feab6144b8547e7881808afba4 (commit) via dfe34ad8d4860899c7415c94be5a0db9653860ad (commit) via 933238bcfb258a2f3b7276e804a13b1c4cd27c62 (commit) via 4d6fd853d341fcacd3bbdb878cba821a514cd5de (commit) via c530e97fb7a7cc28eebd410c39c11c0786b6eea8 (commit) via 2e421895edf8cb8557ec2c12158c96a8e2ab6d73 (commit) via e22cd9aab4dfcb3714aae10b949f8819f4e4e248 (commit) via 31622c3f83cb75bca67a171ac31bddb3290a798b (commit) via 09a7a4c9126d0a4175c047c44eab437347889218 (commit) via d387343ee00115b046572ab2757dce044364503d (commit) via 7958e84fffcc464e4a92512e37609c3340a9485b (commit) via 45fd079c9cf51f25fbcd8fd874255a54e861dbe7 (commit) via c1ed3e22915d91bfa613058e42a8f70fdd0a6e59 (commit) via c9a785cf038b081219f7493d946f65298d5e3f45 (commit) via 14ab690286cfe339ac661883228c59601be01bc1 (commit) via c342ad07e0e9c8ee1552ced4360ff2dbd07213f5 (commit) via f1e30fea1ee1c61819e1083a54ce3ce07915e559 (commit) via c2e7aef7bae96b1155cef26f9cdcf6505591b897 (commit) via 278ee6e2cabe1c38d3568cf8d342913f00393324 (commit) via 1cae244e8987ff294eed39cf4cd8ac8f3e08897c (commit) via eeead429908320594f2262c1071a99f02fb5aace (commit) via c5b49bb839f472487073d8b6508436aa4e0cff42 (commit) via 12d875dadc8539b6a7d2934d912878d723814e5c (commit) via 9970ce91da7655537ff442e98e987612f5bd39d0 (commit) via 8eb93f5a68bf81c2475dca4477d7e5e9e3a4964f (commit) via f9d20c8c7a82de57b965464782fb254244e7dec2 (commit) via 13c2a8cd24b333e31f6a3d9a688c1c61c851b479 (commit) via c152e0dd3e5776fe5ae6fe7f0543d07e48822aa7 (commit) via d1708970d6e5f407adf2a18a8dfe6c5720773efe (commit) via 7074f1b90ee7e9afe694caa6b9550679fbf52ad5 (commit) via f1b5ab52ef7f16fe66f0deede1c99964c10c25e8 (commit) via fc21678fa4d499085afa0fe333cf770cddd25a99 (commit) via d814efcfb634d8ddb25d5089d84583c4424bba1a (commit) via 473b9b38dcc8200b7efb9a52c8f24aa00d171ec6 (commit) via 9e25d0536d7d6e52f915d3cabde7bbde814f9e9e (commit) via 1f378cef7fed5565f722913dbed8ec7721a793fa (commit) via 7dd6900cfed526341e2004a3419b2f6974df5b04 (commit) via 61e00e523e7a36570bf05cacfae53dc33621f5e8 (commit) via 1cff6bd9283d6a9f3e1a3bf219ecf218ffe510fb (commit) via 79485cd126fb4111d129aaedc0ea1ec55ff59640 (commit) from 49ff70a30e1d185276b4862218ba3eb253708745 (commit)
- Log ----------------------------------------------------------------- commit e0eaac2e144d1e738510839b93e5d9b0e9c020e8 Merge: 49ff70a30e1d185276b4862218ba3eb253708745 51c9c1763ea70c36e0d22cc865b48f5186da1fbc Author: Herman van Rink rink@initfour.nl Date: Fri Aug 27 11:09:49 2010 +0200
Merge branch 'gsoc2010-blinky'
commit 51c9c1763ea70c36e0d22cc865b48f5186da1fbc Author: Herman van Rink rink@initfour.nl Date: Thu Aug 26 17:01:12 2010 +0200
Code cleanup, mostly added extra spaces
commit 0ea410c3e4fe6093723f65860f643cc096d78af2 Author: Herman van Rink rink@initfour.nl Date: Thu Aug 26 10:01:08 2010 +0200
updated changelog
commit 58f8ebc23c2982a299773b3c2c15f144b089c27a Merge: dcf470e6d752b80ffaf3d641eb0000e47f341d05 53b394181424dd08ee2d4431a47b49d9b6fd43c2 Author: Herman van Rink rink@initfour.nl Date: Thu Aug 26 09:54:42 2010 +0200
Merge branch 'master' into gsoc2010-blinky
commit dcf470e6d752b80ffaf3d641eb0000e47f341d05 Author: Herman van Rink rink@initfour.nl Date: Wed Aug 25 17:44:27 2010 +0200
duplicated icon from themes/original/img/b_chart.png
commit e141acf5c6bec609cfcd72c9ac131942d59f8d2e Author: Herman van Rink rink@initfour.nl Date: Thu Aug 19 11:03:58 2010 +0200
Changed text to tooltip, and elaborated.
commit e076ec85e174038c7ffe17f701342e52f4413afe Author: Herman van Rink rink@initfour.nl Date: Wed Aug 25 16:10:27 2010 +0200
Add vim mode lines
commit 6fb5591f4edd26058056718c83305ad12501ae8e Author: Herman van Rink rink@initfour.nl Date: Wed Aug 25 15:49:05 2010 +0200
Add CDATA to script code
commit d8edf38d0d66d2b30d7e8d56f85334c326527a31 Author: Herman van Rink rink@initfour.nl Date: Wed Aug 25 15:43:42 2010 +0200
Add some htmlspecialchars for XSS prevention
commit b477c4cb1a0ccbe75bd748470d590d7805b48e5d Merge: b81a22e41f5027e85361997dc8d9936d86957f7f b32e5879fd10bb7e650442e2e4af54f2ecc72c4c Author: Herman van Rink rink@initfour.nl Date: Wed Aug 25 16:59:54 2010 +0200
Merge branch 'blinky/master' into gsoc2010-blinky
Conflicts: Documentation.html - renumbered 6.28 to 6.29
commit b81a22e41f5027e85361997dc8d9936d86957f7f Merge: 0aba4e12f02f92c07204d8063f98cf1904686464 1cff6bd9283d6a9f3e1a3bf219ecf218ffe510fb Author: Herman van Rink rink@initfour.nl Date: Wed Aug 25 16:30:12 2010 +0200
Merge remote branch 'blinky/master' into gsoc2010-blinky
commit b32e5879fd10bb7e650442e2e4af54f2ecc72c4c Merge: 4d8d81f4e5191a19baa540c68003d99a0f7289b3 b51a217c36b45194f79915992a599338b3bac9ef Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon Aug 16 01:01:30 2010 +0300
Merge commit 'origin/master' into local
commit 4d8d81f4e5191a19baa540c68003d99a0f7289b3 Author: Martynas Mickevicius mmartynas@gmail.com Date: Sun Aug 15 22:15:12 2010 +0300
added documentation link
commit 6c9f949613e766719a2a8bfd3a9919cd102f452a Author: Martynas Mickevicius mmartynas@gmail.com Date: Sun Aug 15 15:10:32 2010 +0300
added entry in the faq
commit 62b443c0691fa119958f6ac09ee6c6694ba54f3f Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Aug 13 19:54:22 2010 +0300
chenaged comments in the JS files
commit 0b90202b8ffb79b8246bb987248311757080f7aa Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Aug 13 14:50:21 2010 +0300
fixed an issue when a serie name was not set, thus generating a warning
commit eb3f96ac51c78ec51827a2636c1757d8707377fa Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Aug 13 14:34:07 2010 +0300
changed comments to comply with phpdoc and added some new comments. Moved color settings to more appropriate places.
commit d6185d7ace9d1e7fdba08568f3ded0e2b16462d3 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Aug 13 12:17:26 2010 +0300
removed leftovers from never-used flash chart implmenetation
commit 59f220eb1307fd881ba43e809e0a729432d83d86 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Aug 6 13:22:04 2010 +0300
added check for json encoder, cleaned up error handling a bit
commit 47f67613a2066b69bdf03ff8e43a857f59e8e230 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Aug 5 16:47:28 2010 +0300
fixed a notice which was generated on non supported query result format
commit 68f4a8daf70a832f2f7d87c379250d6816895c0a Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 20:26:16 2010 +0300
removed OFC implementation, since we are not going to use it.
commit 55089f12dbd9493a92bab79e2d11a956368ad61f Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 20:22:55 2010 +0300
getting title shadow back. Will see how it looks on the demo server.
commit fe77ca741c4ae9e89fdfd373c10b3cc36e068dc8 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 20:22:03 2010 +0300
fixing built-in functions so that they work with modified imagemap.
commit 1c1d555002ec8b85ff134a8d9b572c9b4c8e51f7 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 20:12:10 2010 +0300
fixed a bug if a chart was drawn for a query result of one row
commit 0b620df9129c11087bde584b6f6f9e00b90e0f66 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 19:15:08 2010 +0300
enable warning report to the chart.
commit 57614902eb7e7db60025a196ca79fa4170e22141 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 19:06:11 2010 +0300
image map enchancements.
commit 88b9e72597030218d781381ff3970efa30d65fbf Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 16:18:57 2010 +0300
fixed not translated strings, thanks Herman
commit 4577d63b4d85e6f8c9700bf4fb80d3d9e865995a Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 16:18:35 2010 +0300
commented the render function, thanks Herman
commit b0888714b6ab232d7b0dfc24b38d0da9875eed70 Merge: aecac3a6913aa6c93458f8df9edaf7cd4a055980 d0fc397fe1c474150883e85a791a012a7ae03380 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 29 14:14:26 2010 +0300
Merge commit 'origin/master' into local
Conflicts: server_status.php
commit aecac3a6913aa6c93458f8df9edaf7cd4a055980 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 28 16:28:58 2010 +0300
trying to fix the title bug on the demo server
commit c6ce98be18ae87c655721ef4559fbb42d3afdce2 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 28 12:26:40 2010 +0300
moved setting defaults to the correct place.
commit 34ec78abb5e222fc2fcf7bee31fd7cd73e0b7366 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 28 11:27:32 2010 +0300
turned of shadow in the title
commit 4f62c6c2ea45583539f3d191ad9bcf072b684d10 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 28 11:02:17 2010 +0300
added font size setting
commit 8b7e8257b34f5ae9ad60030d4d9f1d10f338b174 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 27 18:32:47 2010 +0300
bugfix for changed format
commit 039e30450c7c81739b293b8138cabba2c25f5606 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 27 18:28:19 2010 +0300
added setting for continuous image
commit 53c071941a7523ae35f8fdacb6ee9acdbda1aebc Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 27 18:26:50 2010 +0300
forgot to swap label names
commit 0604e81526cfd591b0ced14823a8a00c522ff66a Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 27 18:11:01 2010 +0300
bugfix and a query result format changes according to the latest wiki article about charts.
commit 3afe966b5196c0cc04d3d52ad61392a82949b0a1 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 27 12:52:46 2010 +0300
added query results chart when results have only one column.
commit b7daf5d98571d9ec39c001f1b8065fcd490f4a8c Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 23 16:53:46 2010 +0300
removed overlib dependancy. Written simple tooltip.
commit afe5f63c4bb5f34355a465ee8f85f620ad01d959 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 23 13:54:19 2010 +0300
fixed radar chart tooltip text.
commit 25a59f23d5d97e0d9459feba97eb63959c7b4f76 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 23 13:19:30 2010 +0300
chart is rendered in parts. Fixes the issue with browsers which have limited size on base64 images.
commit e3736762aeecf47c4c4a062af49c5f5edc61448f Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 22 14:30:02 2010 +0300
cleaned JS for tooltips
commit bcbd662f4856e3d7c413d182d2cf017c556daf64 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 21 19:35:15 2010 +0300
added tooltip JS includes for other chart places.
commit 557eb2096bedb5f46f49d10b8e3349f7273d4fe3 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 21 19:32:17 2010 +0300
added tooltips for radar charts.
commit 2f78d2d147ae22563e1c0d44e9f0a60155efee0e Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 20 23:29:13 2010 +0300
tooltip on the pie working. Now only on the query results chart.
commit c40e145597f1dddf04a99f3f776c1e83637b3653 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 20 20:29:19 2010 +0300
added JS tooltips to the bar and line charts. Other types generate warnings for now.
commit 979aa9a7ed7b4179d41de6998d6e8b4183611d22 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 9 17:34:28 2010 +0300
graceful error handling
commit 3df1126c8539d410c0bc376a04c94401dbc109ed Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 9 16:29:14 2010 +0300
fixed custom axis lables on the multi chart.
commit 6b806f20477abf750fe071f89cb74c1d9a095f53 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 9 16:27:25 2010 +0300
Chart title has been put to the settings array.
commit e5c8b5a8945810af7178901056b7330f92191e8e Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 9 16:17:16 2010 +0300
default labels are returned to the settings form.
commit 08e048b3077c6d68e424372a7fc6f6958030ff55 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 9 16:16:22 2010 +0300
fixed chart error handler.
commit 7eedd0387db12235f529b11158683dca6c4b6522 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jul 9 16:08:53 2010 +0300
Fixed a case where a notice was generated when the first point was added to the data set.
commit cd1cbce4efe567a05cd9c002987005b8ecc4b6c1 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 20:29:27 2010 +0300
data overflow fix
commit 48f2caccae987a242912d4f17e87ebe3043c70f2 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 20:26:40 2010 +0300
error handling
commit 45e740e96b8f0b042d7974d2abc617095f7cb4eb Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 19:52:38 2010 +0300
fixed option hendling
commit f53b1a52941fa839c76356a6de80c0fd11e97519 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 14:19:16 2010 +0300
added radar chart type to the query results charts.
commit 881bd0909febd4feab6144b8547e7881808afba4 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 13:47:29 2010 +0300
added value skip option to the radar chart in pChar.class
commit dfe34ad8d4860899c7415c94be5a0db9653860ad Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 12:40:03 2010 +0300
added chart type PIE to the query results charts
commit 933238bcfb258a2f3b7276e804a13b1c4cd27c62 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jul 8 11:24:49 2010 +0300
fixed a bug where GD function was used before the check if GD is available.
commit 4d6fd853d341fcacd3bbdb878cba821a514cd5de Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 17:41:45 2010 +0300
Added multi bar chart. Added margin options.
commit c530e97fb7a7cc28eebd410c39c11c0786b6eea8 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 16:57:30 2010 +0300
decreased the size of the points in the line chart
commit 2e421895edf8cb8557ec2c12158c96a8e2ab6d73 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 16:57:06 2010 +0300
using background color setting now
commit e22cd9aab4dfcb3714aae10b949f8819f4e4e248 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 16:37:09 2010 +0300
Restructured code. This allows more chart types and removes some code duplication. Added line charts.
commit 31622c3f83cb75bca67a171ac31bddb3290a798b Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 16:34:02 2010 +0300
added getPieLegendBoxSize($Data) method
commit 09a7a4c9126d0a4175c047c44eab437347889218 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 13:51:30 2010 +0300
added error messaged
commit d387343ee00115b046572ab2757dce044364503d Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jul 7 09:29:29 2010 +0300
removed debug info added check for gd library
commit 7958e84fffcc464e4a92512e37609c3340a9485b Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 23:26:36 2010 +0300
debug on the demo server
commit 45fd079c9cf51f25fbcd8fd874255a54e861dbe7 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 22:23:34 2010 +0300
debug on the demo server
commit c1ed3e22915d91bfa613058e42a8f70fdd0a6e59 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 21:23:03 2010 +0300
debug on the demo server
commit c9a785cf038b081219f7493d946f65298d5e3f45 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 19:22:41 2010 +0300
debug on the demo server
commit 14ab690286cfe339ac661883228c59601be01bc1 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 18:23:05 2010 +0300
debug on the demo server
commit c342ad07e0e9c8ee1552ced4360ff2dbd07213f5 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 17:30:32 2010 +0300
debug on the demo server
commit f1e30fea1ee1c61819e1083a54ce3ce07915e559 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jul 6 17:02:48 2010 +0300
debug on the demo server
commit c2e7aef7bae96b1155cef26f9cdcf6505591b897 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jun 23 14:06:57 2010 +0300
added configurable settings for title and labels
commit 278ee6e2cabe1c38d3568cf8d342913f00393324 Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jun 23 13:32:23 2010 +0300
added curly braces where needed
commit 1cae244e8987ff294eed39cf4cd8ac8f3e08897c Author: Martynas Mickevicius mmartynas@gmail.com Date: Wed Jun 23 13:30:20 2010 +0300
fixed case error
commit eeead429908320594f2262c1071a99f02fb5aace Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jun 22 15:25:06 2010 +0300
Moved settings to an array. Form fields to choose settings for query result chart.
commit c5b49bb839f472487073d8b6508436aa4e0cff42 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jun 22 14:24:23 2010 +0300
added stacked bar chart
commit 12d875dadc8539b6a7d2934d912878d723814e5c Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon Jun 21 17:35:42 2010 +0300
early code to show a chart for query results
commit 9970ce91da7655537ff442e98e987612f5bd39d0 Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon Jun 21 17:34:50 2010 +0300
restructured the code to be more OOP friendly
commit 8eb93f5a68bf81c2475dca4477d7e5e9e3a4964f Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 20:54:32 2010 +0300
Increased default size.
commit f9d20c8c7a82de57b965464782fb254244e7dec2 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 19:04:59 2010 +0300
added chart button. Will be used later to draw a chart of the query results.
commit 13c2a8cd24b333e31f6a3d9a688c1c61c851b479 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 17:39:32 2010 +0300
fixed default sizes and colors. Fixed some var names.
commit c152e0dd3e5776fe5ae6fe7f0543d07e48822aa7 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 17:14:04 2010 +0300
modified status chart size.
commit d1708970d6e5f407adf2a18a8dfe6c5720773efe Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 17:10:19 2010 +0300
moved chart dependant code to the chart lib.
commit 7074f1b90ee7e9afe694caa6b9550679fbf52ad5 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 16:01:56 2010 +0300
color changes to make them more distinguishable
commit f1b5ab52ef7f16fe66f0deede1c99964c10c25e8 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 16:01:09 2010 +0300
added first pChart charts.
commit fc21678fa4d499085afa0fe333cf770cddd25a99 Author: Martynas Mickevicius mmartynas@gmail.com Date: Thu Jun 17 16:00:08 2010 +0300
moved flash file to appropriate directory
commit d814efcfb634d8ddb25d5089d84583c4424bba1a Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jun 15 20:13:34 2010 +0300
added the chart in the profiling window
commit 473b9b38dcc8200b7efb9a52c8f24aa00d171ec6 Author: Martynas Mickevicius mmartynas@gmail.com Date: Tue Jun 15 20:13:12 2010 +0300
fixed the layout of the chart in the server status page
commit 9e25d0536d7d6e52f915d3cabde7bbde814f9e9e Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon Jun 14 23:36:52 2010 +0300
implementation of the pie chart using OFC
commit 1f378cef7fed5565f722913dbed8ec7721a793fa Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon Jun 14 23:36:22 2010 +0300
added OFC php wrapper together with SWF file to PMA
commit 7dd6900cfed526341e2004a3419b2f6974df5b04 Merge: 61e00e523e7a36570bf05cacfae53dc33621f5e8 662f3d4da33363f77051a847488d41f40cf33655 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jun 4 16:29:15 2010 +0200
Merge commit 'origin/master' into local
commit 61e00e523e7a36570bf05cacfae53dc33621f5e8 Author: Martynas Mickevicius mmartynas@gmail.com Date: Fri Jun 4 16:19:55 2010 +0200
file placeholder creation. Will build on top of these.
commit 1cff6bd9283d6a9f3e1a3bf219ecf218ffe510fb Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon May 24 22:15:00 2010 +0200
Revert initial commit
commit 79485cd126fb4111d129aaedc0ea1ec55ff59640 Author: Martynas Mickevicius mmartynas@gmail.com Date: Mon May 24 21:55:44 2010 +0200
test commit
-----------------------------------------------------------------------
Summary of changes: ChangeLog | 1 + Documentation.html | 5 + js/pMap.js | 164 ++ libraries/chart.lib.php | 257 ++ libraries/chart/pChart/fonts/tahoma.ttf | Bin 0 -> 383804 bytes libraries/chart/pChart/pCache.class | 119 + libraries/chart/pChart/pChart.class | 3626 +++++++++++++++++++++++++++ libraries/chart/pChart/pData.class | 260 ++ libraries/chart/pma_chart.php | 184 ++ libraries/chart/pma_pchart_chart.php | 399 +++ libraries/chart/pma_pchart_multi.php | 118 + libraries/chart/pma_pchart_multi_bar.php | 38 + libraries/chart/pma_pchart_multi_line.php | 39 + libraries/chart/pma_pchart_multi_radar.php | 100 + libraries/chart/pma_pchart_pie.php | 101 + libraries/chart/pma_pchart_single.php | 57 + libraries/chart/pma_pchart_single_bar.php | 35 + libraries/chart/pma_pchart_single_line.php | 35 + libraries/chart/pma_pchart_single_radar.php | 88 + libraries/chart/pma_pchart_stacked_bar.php | 36 + libraries/common.lib.php | 14 +- libraries/display_tbl.lib.php | 6 + server_status.php | 14 + sql.php | 3 +- tbl_chart.php | 192 ++ themes/darkblue_orange/img/b_chart.png | Bin 0 -> 3118 bytes themes/original/img/b_chart.png | Bin 0 -> 3118 bytes 27 files changed, 5889 insertions(+), 2 deletions(-) create mode 100644 js/pMap.js create mode 100644 libraries/chart.lib.php create mode 100644 libraries/chart/pChart/fonts/tahoma.ttf create mode 100644 libraries/chart/pChart/pCache.class create mode 100644 libraries/chart/pChart/pChart.class create mode 100644 libraries/chart/pChart/pData.class create mode 100644 libraries/chart/pma_chart.php create mode 100644 libraries/chart/pma_pchart_chart.php create mode 100644 libraries/chart/pma_pchart_multi.php create mode 100644 libraries/chart/pma_pchart_multi_bar.php create mode 100644 libraries/chart/pma_pchart_multi_line.php create mode 100644 libraries/chart/pma_pchart_multi_radar.php create mode 100644 libraries/chart/pma_pchart_pie.php create mode 100644 libraries/chart/pma_pchart_single.php create mode 100644 libraries/chart/pma_pchart_single_bar.php create mode 100644 libraries/chart/pma_pchart_single_line.php create mode 100644 libraries/chart/pma_pchart_single_radar.php create mode 100644 libraries/chart/pma_pchart_stacked_bar.php create mode 100644 tbl_chart.php create mode 100644 themes/darkblue_orange/img/b_chart.png create mode 100644 themes/original/img/b_chart.png
diff --git a/ChangeLog b/ChangeLog index eb47035..10451cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -109,6 +109,7 @@ $Id$ - rfe #2973909 Users preferences - [relations] Dropped WYSIWYG-PDF configuration variable. - rfe #806035, #686260 [relations] Export relations to Dia, SVG and others ++ [interface] Added charts to status tab, profiling page and query results
3.3.7.0 (not yet released)
diff --git a/Documentation.html b/Documentation.html index 96f0bee..50d6dd1 100644 --- a/Documentation.html +++ b/Documentation.html @@ -4340,6 +4340,11 @@ chmod o+rwx tmp other. </p>
+<h4 id="faq6_29"> + <a href="#faq6_29">6.28 Why can't I get a chart from my query result table?</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/Devel:Charts#Data_formats_for_query_results_chart">wiki</a>.</p> + <h3 id="faqproject">phpMyAdmin project</h3>
<h4 id="faq7_1"> diff --git a/js/pMap.js b/js/pMap.js new file mode 100644 index 0000000..b63221c --- /dev/null +++ b/js/pMap.js @@ -0,0 +1,164 @@ +/** + * Holds the definition and the creation of the imageMap object + * @author Martynas Mickevicius mmartynas@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/libraries/chart.lib.php b/libraries/chart.lib.php new file mode 100644 index 0000000..045da94 --- /dev/null +++ b/libraries/chart.lib.php @@ -0,0 +1,257 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Chart functions used to generate various types of charts. + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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/tahoma.ttf b/libraries/chart/pChart/fonts/tahoma.ttf new file mode 100644 index 0000000..59b14a2 Binary files /dev/null and b/libraries/chart/pChart/fonts/tahoma.ttf differ diff --git a/libraries/chart/pChart/pCache.class b/libraries/chart/pChart/pCache.class new file mode 100644 index 0000000..2bcd6b0 --- /dev/null +++ b/libraries/chart/pChart/pCache.class @@ -0,0 +1,119 @@ +<?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 new file mode 100644 index 0000000..2b5a077 --- /dev/null +++ b/libraries/chart/pChart/pChart.class @@ -0,0 +1,3626 @@ +<?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 new file mode 100644 index 0000000..1c4a301 --- /dev/null +++ b/libraries/chart/pChart/pData.class @@ -0,0 +1,260 @@ +<?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 new file mode 100644 index 0000000..ae483c8 --- /dev/null +++ b/libraries/chart/pma_chart.php @@ -0,0 +1,184 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Holds the base class that all charts inherit from and some widely used + * constants. + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..b8e5375 --- /dev/null +++ b/libraries/chart/pma_pchart_chart.php @@ -0,0 +1,399 @@ +<?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 + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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)); + + // when graph area gradient is used, this is the color of the graph + // area border + $this->settings['graphAreaColor'] = '#D5D9DD'; + + // the background color of the graph area + $this->settings['graphAreaGradientColor'] = '#A3CBA7'; + + // the color of the grid lines in the graph area + $this->settings['gridColor'] = '#E6E6E6'; + + // the color of the scale and the labels + $this->settings['scaleColor'] = '#D5D9DD'; + + $this->settings['titleBgColor'] = '#000000'; + } + + 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().'tahoma.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().'tahoma.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), + 50,TARGET_BACKGROUND); + $this->chart->addBorder(2); + } + + /** + * 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, + True, + $this->getTitleBgColor(RED), + $this->getTitleBgColor(GREEN), + $this->getTitleBgColor(BLUE), + 30 + ); + } + + /** + * 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 + ); + $this->chart->drawGraphAreaGradient( + $this->getGraphAreaGradientColor(RED), + $this->getGraphAreaGradientColor(GREEN), + $this->getGraphAreaGradientColor(BLUE), + 50 + ); + $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); + } +} + +?> diff --git a/libraries/chart/pma_pchart_multi.php b/libraries/chart/pma_pchart_multi.php new file mode 100644 index 0000000..685b746 --- /dev/null +++ b/libraries/chart/pma_pchart_multi.php @@ -0,0 +1,118 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..6c3969b --- /dev/null +++ b/libraries/chart/pma_pchart_multi_bar.php @@ -0,0 +1,38 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..892c011 --- /dev/null +++ b/libraries/chart/pma_pchart_multi_line.php @@ -0,0 +1,39 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..2224acc --- /dev/null +++ b/libraries/chart/pma_pchart_multi_radar.php @@ -0,0 +1,100 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 + ); + $this->chart->drawGraphAreaGradient( + $this->getGraphAreaGradientColor(RED), + $this->getGraphAreaGradientColor(GREEN), + $this->getGraphAreaGradientColor(BLUE), + 50 + ); + } + + /** + * 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 new file mode 100644 index 0000000..5bf77fc --- /dev/null +++ b/libraries/chart/pma_pchart_pie.php @@ -0,0 +1,101 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 + ); + $this->chart->drawGraphAreaGradient( + $this->getGraphAreaGradientColor(RED), + $this->getGraphAreaGradientColor(GREEN), + $this->getGraphAreaGradientColor(BLUE), + 50 + ); + } + + /** + * 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 new file mode 100644 index 0000000..04fef80 --- /dev/null +++ b/libraries/chart/pma_pchart_single.php @@ -0,0 +1,57 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..821df63 --- /dev/null +++ b/libraries/chart/pma_pchart_single_bar.php @@ -0,0 +1,35 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..a0ac742 --- /dev/null +++ b/libraries/chart/pma_pchart_single_line.php @@ -0,0 +1,35 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 new file mode 100644 index 0000000..1b31cd0 --- /dev/null +++ b/libraries/chart/pma_pchart_single_radar.php @@ -0,0 +1,88 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 + ); + $this->chart->drawGraphAreaGradient( + $this->getGraphAreaGradientColor(RED), + $this->getGraphAreaGradientColor(GREEN), + $this->getGraphAreaGradientColor(BLUE), + 50 + ); + } + + /** + * 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 new file mode 100644 index 0000000..4a633f0 --- /dev/null +++ b/libraries/chart/pma_pchart_stacked_bar.php @@ -0,0 +1,36 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * @author Martynas Mickevicius <mmartynas@gmail.com> + * @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 741d8cb..dd7f40a 100644 --- a/libraries/common.lib.php +++ b/libraries/common.lib.php @@ -1280,12 +1280,14 @@ 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) +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"; @@ -1297,7 +1299,17 @@ function PMA_profilingResults($profiling_results) 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"; }
diff --git a/libraries/display_tbl.lib.php b/libraries/display_tbl.lib.php index 747c5b5..0deed6e 100644 --- a/libraries/display_tbl.lib.php +++ b/libraries/display_tbl.lib.php @@ -2182,6 +2182,12 @@ function PMA_displayResultsOperations($the_disp_mode, $analyzed_sql) { 'tbl_export.php' . PMA_generate_common_url($_url_params), PMA_getIcon('b_tblexport.png', __('Export'), false, true), '', true, true, '') . "\n"; + + // show chart + echo PMA_linkOrButton( + 'tbl_chart.php' . PMA_generate_common_url($_url_params), + PMA_getIcon('b_chart.png', __('Display chart'), false, true), + '', true, true, '') . "\n"; }
// CREATE VIEW diff --git a/server_status.php b/server_status.php index bcbfe06..520bb51 100644 --- a/server_status.php +++ b/server_status.php @@ -16,6 +16,8 @@ if (! defined('PMA_NO_VARIABLES_IMPORT')) { } require_once './libraries/common.inc.php';
+$GLOBALS['js_include'][] = 'pMap.js'; + /** * Does the common work */ @@ -34,6 +36,11 @@ 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.'); @@ -692,6 +699,13 @@ foreach ($used_queries as $name => $value) { ?> </tbody> </table> + <div class="clearfloat"></div> +</div> + +<div> + <?php + echo PMA_chart_status($used_queries); + ?> </div>
<div id="serverstatussection"> diff --git a/sql.php b/sql.php index 12c6ac5..9a370d8 100644 --- a/sql.php +++ b/sql.php @@ -14,6 +14,7 @@ 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';
/** * Defines the url to return to in case of error in a sql statement @@ -602,7 +603,7 @@ else { }
if (isset($profiling_results)) { - PMA_profilingResults($profiling_results); + PMA_profilingResults($profiling_results, true); }
// Displays the results in a table diff --git a/tbl_chart.php b/tbl_chart.php new file mode 100644 index 0000000..0e9ed40 --- /dev/null +++ b/tbl_chart.php @@ -0,0 +1,192 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * handles creation of the chart + * + * @version $Id$ + * @package phpMyAdmin + */ + +/** + * do not import request variable into global scope + * @ignore + */ +if (! defined('PMA_NO_VARIABLES_IMPORT')) { + define('PMA_NO_VARIABLES_IMPORT', true); +} + +/** + * + */ +require_once './libraries/common.inc.php'; + +$GLOBALS['js_include'][] = 'pMap.js'; + +/** + * Runs common work + */ +require './libraries/db_common.inc.php'; +$url_params['goto'] = $cfg['DefaultTabDatabase']; +$url_params['back'] = 'sql.php'; + +/* + * Import chart functions + */ +require_once './libraries/chart.lib.php'; + +/* + * Execute the query and return the result + */ +$data = array(); + +$result = PMA_DBI_try_query($sql_query); +while ($row = PMA_DBI_fetch_assoc($result)) { + $data[] = $row; +} + +// get settings if any posted +$chartSettings = array(); +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]')); +} + +/** + * Displays top menu links + * We use db links because a chart is not necessarily on a single table + */ +$num_tables = 0; +require_once './libraries/db_links.inc.php'; + +$url_params['db'] = $GLOBALS['db']; +$url_params['reload'] = 1; + +/** + * Displays the page + */ +?> +<!-- 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> + + <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> +<?php +/** + * Displays the footer + */ +require_once './libraries/footer.inc.php'; + +?> diff --git a/themes/darkblue_orange/img/b_chart.png b/themes/darkblue_orange/img/b_chart.png new file mode 100644 index 0000000..388ec30 Binary files /dev/null and b/themes/darkblue_orange/img/b_chart.png differ diff --git a/themes/original/img/b_chart.png b/themes/original/img/b_chart.png new file mode 100644 index 0000000..388ec30 Binary files /dev/null and b/themes/original/img/b_chart.png differ
hooks/post-receive