├── .travis.yml ├── LICENSE ├── README.md ├── artifacts ├── nextcharts-1.5.js └── nextcharts-1.5.min.js ├── build.xml ├── lib ├── jackson-annotations-2.2.3.jar ├── jackson-core-2.2.3.jar ├── jackson-databind-2.2.3.jar ├── javax.servlet.jar ├── jetty-all-9.0.4.v20130625.jar ├── servlet-api-2.5.jar └── yuicompressor-2.4.7.jar └── src ├── html ├── main-jetty-test.html ├── main-test-dualAxis.html ├── main-test.html └── main-widget-test.html ├── java └── ro │ └── nextreports │ └── charts │ └── JsonHandler.java └── js ├── alarm.js ├── barchart.js ├── bubblechart.js ├── chart-util.js ├── color-util.js ├── display.js ├── html2canvas-0.5.0.js ├── indicator.js ├── jquery-1.10.2.min.js ├── jspdf.min-1.0.272.js ├── linechart.js ├── nextchart.js ├── nextwidget.js ├── pdf-capture.js └── piechart.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | script: ant minify-js 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #NextCharts 2 | 3 | NextCharts is an open source HTML5 charts library which uses the canvas tag for drawing. This library is used by [NextReports](https://github.com/nextreports/nextreports) from version 7. 4 | 5 | Following types of charts & styles can be defined (where h stands for horizontal and n for negative): 6 | 7 | * __bar__ : normal, glass, dome, cylinder, parallelepiped, combo with lines 8 | * __nbar__ : normal, glass 9 | * __stackedbar__ : normal, glass, dome, cylinder, parallelepiped 10 | * __hbar__ : normal, glass, dome, cylinder, parallelepiped 11 | * __hstackedbar__ : normal, glass, dome, cylinder, parallelepiped 12 | * __line__ : normal, soliddot, hollowdot, anchordot, bowdot, stardot 13 | * __area__ : normal, soliddot, hollowdot, anchordot, bowdot, stardot 14 | * __pie__ 15 | * __bubble__ 16 | 17 | ![alt tag](http://2.bp.blogspot.com/-ouJicYwR4D0/Uv3pAiWORgI/AAAAAAAAJDo/a6RxWpXU3QM/s1600/NextServerCharts-white.png) 18 | 19 | NextCharts supports dual axis definition and it allows to have a combo chart with bars and lines. Highlight selection is available on mouse move. 20 | 21 | As opposite to other charts libraries, tooltips are seen only on real selection of elements (and not on any position) and they are following the mouse cursor to allow for a smooth visualization. Other charts libraries have a fixed position for tooltips when entering the selection and user cannot move the mouse to a position which is under the tooltip, making the interaction more clumsy. 22 | 23 | A small number of widgets is also contained by this library. This set includes: 24 | 25 | * __Alarm__ (Status) 26 | * __Indicator__ (Gauge) 27 | * __Display__ (Value & Comparison) 28 | 29 | ![alt tag](http://2.bp.blogspot.com/-1lSssWLMPOs/U5hWOr0pwWI/AAAAAAAAJf8/Eof9uAbvvm4/s1600/a2.png) 30 | 31 | ##Samples 32 | 33 | Some samples (to see how json properties must be specified) can be found in src/html: 34 | 35 | 1. main-test.html, main-test-dualAxis.html independent chart tests 36 | 2. main-jetty-test.html jetty chart test (must run ro.nextreports.charts.JsonHandler to start server) 37 | 3. main-widget-test.html independent widget test 38 | 39 | ##Articles 40 | 41 | * [Origins](http://blog.next-reports.com/2014/02/nextcharts-new-html5-library-for.html) 42 | * [How To Use](http://blog.next-reports.com/2014/02/nextcharts-developer-perspective.html) 43 | * [Styles](http://blog.next-reports.com/2014/02/nextcharts-styles.html) 44 | * [Tooltips](http://blog.next-reports.com/2014/03/nextcharts-tooltip-messages.html) 45 | * [Dual Y Axis](http://blog.next-reports.com/2014/10/nextcharts-dual-y-axis.html) 46 | * [Combo Bar & Lines](http://blog.next-reports.com/2014/02/nextcharts-combo-bar-line-charts.html) 47 | * [Bubble Chart](http://blog.next-reports.com/2014/03/nextreports-creating-bubble-chart.html) 48 | * [Indicator](http://blog.next-reports.com/2014/05/nextcharts-indicator.html) 49 | * [Display](http://blog.next-reports.com/2014/05/nextcharts-display-widget.html) 50 | * [Display-2](http://blog.next-reports.com/2014/08/display-revisited.html) 51 | * [Alarm](http://blog.next-reports.com/2014/06/nextcharts-alarm-widget.html) 52 | * [Highlights](http://blog.next-reports.com/2015/08/nextcharts-highlight-selection.html) 53 | * [Bar Chart with Negative Values](http://blog.next-reports.com/2015/08/nextcharts-bar-charts-with-negative.html) 54 | 55 | ##Read more 56 | 57 | You can find information about NextCharts on following links: 58 | 59 | 1. NextReports Blog: http://blog.next-reports.com/ 60 | 2. NextReports Site: http://next-reports.com/ 61 | 62 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/jackson-annotations-2.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/jackson-annotations-2.2.3.jar -------------------------------------------------------------------------------- /lib/jackson-core-2.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/jackson-core-2.2.3.jar -------------------------------------------------------------------------------- /lib/jackson-databind-2.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/jackson-databind-2.2.3.jar -------------------------------------------------------------------------------- /lib/javax.servlet.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/javax.servlet.jar -------------------------------------------------------------------------------- /lib/jetty-all-9.0.4.v20130625.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/jetty-all-9.0.4.v20130625.jar -------------------------------------------------------------------------------- /lib/servlet-api-2.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/servlet-api-2.5.jar -------------------------------------------------------------------------------- /lib/yuicompressor-2.4.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextreports/nextcharts/e4ab233cb426679facba834b3d2ecf1b7a342ffc/lib/yuicompressor-2.4.7.jar -------------------------------------------------------------------------------- /src/html/main-jetty-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NextReports Charts 6 | 7 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/html/main-test-dualAxis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NextReports Charts 6 | 7 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/html/main-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NextReports Charts 6 | 7 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 304 | 305 | 306 | 307 |
308 | 309 | -------------------------------------------------------------------------------- /src/html/main-widget-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NextReports Widgets 6 | 7 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/java/ro/nextreports/charts/JsonHandler.java: -------------------------------------------------------------------------------- 1 | package ro.nextreports.charts; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | import javax.servlet.ServletException; 6 | 7 | import java.io.IOException; 8 | 9 | import org.eclipse.jetty.server.Server; 10 | import org.eclipse.jetty.server.Request; 11 | import org.eclipse.jetty.server.handler.AbstractHandler; 12 | 13 | public class JsonHandler extends AbstractHandler { 14 | 15 | private String jsonHBar = 16 | "{\"data\":[[16,66,24,30,80,52],[48,50,29,60,60,58],[30,40,28,52,34,50]]," + 17 | " \"labels\":[\"JAN\",\"FEB\",\"MAR\",\"APR\",\"MAY\",\"JUN\"]," + 18 | " \"color\":[\"#004CB3\",\"#A04CB3\",\"rgb(40,75,75)\"]," + 19 | " \"legend\":[\"2011 First year of work\",\"2012 Second year of work\",\"2013 Third year of work\"]," + 20 | " \"alpha\":0.6, \"showGridX\":true, \"showGridY\":true, \"colorGridX\":\"rgb(0,198,189)\", \"colorGridY\":\"rgb(0,198,189)\"," + 21 | // " \"message\":\"Value #val from #total\"," + 22 | // " \"showTicks\":false, " + 23 | " \"tickCount\":5, " + 24 | " \"title\":{\"text\":\"Financial Analysis\", \"font\":{\"weight\":\"bold\", \"size\":16, \"family\":\"sans-serif\"}, \"color\":\"blue\", \"alignment\":\"center\"}," + 25 | " \"labelOrientation\":\"vertical\"," + 26 | // //" \"background\":\"rgb(231,254,254)\"," + 27 | " \"type\":\"hstackedbar\"," + 28 | " \"style\":\"cylinder\"," + 29 | " \"yData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 30 | " \"xData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 31 | " \"yLegend\":{\"text\":\"Price\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}," + 32 | " \"xLegend\":{\"text\":\"Month\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}" + 33 | "}"; 34 | 35 | // "{\"data\":[[16,66,24,42,49,8]], \"type\":\"hbar\" }"; 36 | 37 | // "{\"data\":[[20.0,5.0,12.0]],\"type\":\"bar\",\"style\":\"glass\",\"background\":\"#ffffff\",\"labels\":[\"Dunn Brandon\",\"Frest Mark\",\"Winters John\"],\"labelOrientation\":\"horizontal\",\"color\":[\"#0000cc\"],\"legend\":[\"WRK\"],\"alpha\":0.5,\"colorXaxis\":\"#000000\",\"colorYaxis\":\"#000000\",\"showGridX\":true,\"showGridY\":true,\"tickCount\":5,\"showTicks\":true,\"title\":{\"text\":\"Next Reports Hours\",\"font\":{\"weight\":\"bold\",\"size\":16,\"family\":\"SansSerif\"},\"color\":\"#000000\",\"alignment\":\"center\"},\"xData\":{\"color\":\"#000000\",\"font\":{\"weight\":\"normal\",\"size\":12,\"family\":\"SansSerif\"}},\"yData\":{\"color\":\"#000000\",\"font\":{\"weight\":\"normal\",\"size\":12,\"family\":\"SansSerif\"}}}"; 38 | 39 | private String jsonBar = 40 | "{\"data\":[[16,66,24,30,80,52],[48,50,29,60,70,58],[30,40,28,52,74,50]]," + 41 | " \"labels\":[\"JANUARY\",\"FEBRUARY\",\"MARCH\",\"APRIL\",\"MAY\",\"JUNE\"]," + 42 | " \"color\":[\"#004CB3\",\"#A04CB3\",\"#7aa37a\"]," + 43 | " \"legend\":[\"2011 First year of work\",\"2012 Second year of work\",\"2013 Third year of work\"]," + 44 | " \"alpha\":0.8, \"showGridX\":false, \"showGridY\":true," + 45 | " \"message\":\"Value #val\", \"tickCount\":5, " + 46 | " \"title\":{\"text\":\"Financial Analysis\", \"font\":{\"weight\":\"bold\", \"size\":16, \"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 47 | " \"labelOrientation\":\"horizontal\"," + 48 | " \"type\":\"bar\"," + 49 | " \"style\":\"glass\"," + 50 | //" \"onclick\":\"function() console.log(\"Call from JS function\");\"" + 51 | " \"onClick\":\"function doClick(value){ console.log('Call from function : ' + value);}\""+ 52 | "}"; 53 | 54 | private String jsonStacked = 55 | "{\"data\":[[16,66,24,30,80,52],[48,50,29,60,60,58],[20,40,28,52,34,50]]," + 56 | " \"labels\":[\"JAN\",\"FEB\",\"MAR\",\"APR\",\"MAY\",\"JUN\"]," + 57 | " \"color\":[\"#004CB3\",\"#A04CB3\",\"rgb(40,75,75)\"]," + 58 | " \"legend\":[\"2011 First year of work\",\"2012 Second year of work\",\"2013 Third year of work\"]," + 59 | " \"alpha\":0.6, \"showGridX\":true, \"showGridY\":true, \"colorGridX\":\"rgb(0,198,189)\", \"colorGridY\":\"rgb(0,198,189)\"," + 60 | //" \"colorXaxis\":\"blue\", \"colorYaxis\":\"red\"," + 61 | " \"message\":\"Value #val
from #total\", \"tickCount\":5, " + 62 | " \"title\":{\"text\":\"Financial Analysis\", \"font\":{\"weight\":\"bold\", \"size\":16, \"family\":\"sans-serif\"}, \"color\":\"blue\", \"alignment\":\"center\"}," + 63 | " \"labelOrientation\":\"horizontal\"," + 64 | " \"background\":\"rgb(231,254,254)\"," + 65 | " \"type\":\"stackedbar\"," + 66 | " \"style\":\"dome\"," + 67 | " \"yData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 68 | " \"xData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 69 | " \"yLegend\":{\"text\":\"Price\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}," + 70 | " \"xLegend\":{\"text\":\"Month\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}" + 71 | "}"; 72 | 73 | private String jsonStackLine = 74 | "{\"data\":[[16,66,24,30,80,52],[48,50,29,60,60,58],[30,40,28,52,34,50]]," + 75 | " \"lineData\":[[31.33, 52, 27, 47.33, 74.66, 53.33],[100, 120, 53, 190, 40, 130]],"+ 76 | " \"labels\":[\"JAN\",\"FEB\",\"MAR\",\"APR\",\"MAY\",\"JUN\"]," + 77 | " \"color\":[\"#004CB3\",\"#A04CB3\",\"rgb(40,75,75)\"]," + 78 | " \"lineColor\":[\"#270283\", \"#CC6633\"]," + 79 | " \"legend\":[\"2011\",\"2012\",\"2013\"]," + 80 | " \"lineLegend\":[\"Average\", \"Profit\"]," + 81 | " \"alpha\":0.6, \"showGridX\":true, \"showGridY\":true, \"colorGridX\":\"rgb(0,198,189)\", \"colorGridY\":\"rgb(0,198,189)\"," + 82 | " \"message\":\"Value #val from #total\", \"tickCount\":5, " + 83 | " \"title\":{\"text\":\"Financial Analysis\", \"font\":{\"weight\":\"bold\", \"size\":16, \"family\":\"sans-serif\"}, \"color\":\"blue\", \"alignment\":\"center\"}," + 84 | " \"labelOrientation\":\"horizontal\"," + 85 | //" \"background\":\"rgb(231,254,254)\"," + 86 | " \"type\":\"stackedbar\"," + 87 | " \"style\":\"glass\"," + 88 | " \"yData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 89 | " \"xData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 90 | " \"yLegend\":{\"text\":\"Price\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}," + 91 | " \"xLegend\":{\"text\":\"Month\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}" + 92 | "}"; 93 | 94 | /** bar line */ 95 | // "{\"data\":[[2,3,2,3,2,1],[1,2,1,2,3,1]]," + 96 | // " \"lineData\":[[2,1,3,2,3,2]],"+ 97 | // " \"labels\":[\"4\",\"10\",\"6\",\"5\",\"4\",\"9\"]," + 98 | // " \"color\":[\"#7C7CD2\",\"#FB7C6C\"]," + 99 | // " \"lineColor\":[\"#8BE2A0\"]," + 100 | // " \"legend\":[\"One\",\"Two\"]," + 101 | // " \"lineLegend\":[\"Three\"]," + 102 | // " \"alpha\":1, \"showGridX\":true, \"showGridY\":true, \"colorGridX\":\"#F5E1AA\", \"colorGridY\":\"#F5E1AA\"," + 103 | // " \"message\":\"Value #val from #total\", \"tickCount\":5, " + 104 | // " \"background\":\"#F8F8D8\"," + 105 | // " \"type\":\"bar\"," + 106 | // " \"style\":\"normal\"" + 107 | // "}"; 108 | 109 | /** bar line - bet index */ 110 | // "{\"data\":[[6205, 6090, 6220, 6350, 6140, 6560, 6230, 6165, 6180, 6175, 6235, 6210]]," + 111 | // " \"lineData\":[[6490, 6455, 6485, 6500, 6550, 6600, 6575, 6510, 6530, 6540, 6560, 6555]],"+ 112 | // " \"labels\":[\"3\",\"6\",\"7\",\"8\",\"9\",\"10\",\"13\",\"14\",\"15\",\"16\",\"17\",\"20\"]," + 113 | // " \"color\":[\"#004CB3\"]," + 114 | // " \"lineColor\":[\"#A04CB3\"]," + 115 | // " \"legend\":[\"Valoare tranzactii (mil RON)\"]," + 116 | // " \"lineLegend\":[\"Indice BET (puncte)\"]," + 117 | // " \"alpha\":1, \"showGridX\":false, \"showGridY\":true, \"colorGridX\":\"red\", \"colorGridY\":\"rgb(0,198,189)\"," + 118 | // " \"message\":\"#val\", \"tickCount\":5, " + 119 | // " \"type\":\"bar\"," + 120 | // " \"style\":\"glass\"" + 121 | // "}"; 122 | 123 | 124 | 125 | private String jsonLine = 126 | "{\"data\":[[16,66,24,30,80,52],[48,50,29,60,60,58],[30,40,28,52,34,50]]," + 127 | " \"labels\":[\"JAN\",\"FEB\",\"MAR\",\"APR\",\"MAY\",\"JUN\"]," + 128 | " \"color\":[\"#004CB3\",\"#A04CB3\",\"rgb(40,75,75)\"]," + 129 | " \"legend\":[\"2011 First year of work\",\"2012 Second year of work\",\"2013 Third year of work\"]," + 130 | " \"alpha\":0.4, \"showGridX\":true, \"showGridY\":true, \"colorGridX\":\"rgb(0,198,189)\", \"colorGridY\":\"rgb(0,198,189)\"," + 131 | " \"message\":\"Value #val\", \"tickCount\":5, " + 132 | " \"title\":{\"text\":\"Financial Analysis\", \"font\":{\"weight\":\"bold\", \"size\":16, \"family\":\"sans-serif\"}, \"color\":\"blue\", \"alignment\":\"center\"}," + 133 | " \"labelOrientation\":\"horizontal\"," + 134 | //" \"background\":\"rgb(231,254,254)\"," + 135 | " \"type\":\"area\"," + 136 | " \"style\":\"soliddot\"," + 137 | " \"yData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 138 | " \"xData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 139 | " \"yLegend\":{\"text\":\"Price\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}," + 140 | " \"xLegend\":{\"text\":\"Month\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}" + 141 | "}"; 142 | 143 | private String jsonPie = 144 | "{\"data\":[[16,66,24,30,80,52]]," + 145 | " \"labels\":[\"JANUARY\",\"FEBRUARY\",\"MARCH\",\"APRIL\",\"MAY\",\"JUNE\"]," + 146 | " \"color\": [\"#004CB3\",\"#A04CB3\", \"#7aa37a\", \"#f18e9f\", \"#90e269\", \"#bc987b\"]," + 147 | " \"alpha\":0.4," + 148 | " \"message\":\"Value #val from #total
#percent% of 100%\"," + 149 | " \"title\":{\"text\":\"Financial Analysis\", \"font\":{\"weight\":\"bold\", \"size\":16, \"family\":\"sans-serif\"}, \"color\":\"blue\", \"alignment\":\"center\"}," + 150 | //" \"background\":\"rgb(231,254,254)\"," + 151 | " \"xData\":{\"font\":{\"weight\":\"bold\",\"size\":16,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 152 | " \"type\":\"pie\"" + 153 | "}"; 154 | 155 | private String jsonBubble = 156 | "{\"data\":[[80.66,79.84,78.6,72.73,80.05,72.49,68.09,81.55,68.6,78.09],[1.67,1.36,1.84,2.78,2,1.7,4.77,2.96,1.54,2.05],[33739900,81902307,5523095,79716203,61801570,73137148,31090763,7485600,141850000,307007000],[\"North America\",\"Europe\",\"Europe\",\"Middle East\",\"Europe\",\"Middle East\",\"Middle East\",\"Middle East\",\"Europe\",\"North America\"]]," + 157 | " \"labels\":[\"CAN\",\"DEU\",\"DNK\",\"EGY\",\"GBN\",\"IRN\",\"IRQ\",\"ISR\",\"RUS\",\"USA\"]," + 158 | " \"color\":[\"#004CB3\",\"#A04CB3\",\"#7aa37a\"]," + 159 | " \"categories\":[\"North America\",\"Europe\",\"Europe\",\"Middle East\",\"Europe\",\"Middle East\",\"Middle East\",\"Middle East\",\"Europe\",\"North America\"]," + 160 | " \"alpha\":0.6," + 161 | " \"showGridX\":true," + 162 | " \"showGridY\":true," + 163 | " \"colorGridX\":\"rgb(0,198,189)\"," + 164 | " \"colorGridY\":\"rgb(0,198,189)\"," + 165 | " \"message\":\"#label
Life Expectancy: #x
Fertility Rate: #val
Region: #c
Population: #z\"," + 166 | " \"tickCount\":4, " + 167 | " \"title\":{\"text\":\"Population Correlation\", \"font\":{\"weight\":\"bold\", \"size\":14, \"family\":\"sans-serif\"}, \"color\":\"blue\", \"alignment\":\"center\"}," + 168 | " \"labelOrientation\":\"horizontal\"," + 169 | " \"type\":\"bubble\"," + 170 | " \"style\":\"normal\"," + 171 | " \"yData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 172 | " \"xData\":{\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"blue\"}," + 173 | " \"yLegend\":{\"text\":\"Fertility Rate\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}," + 174 | " \"xLegend\":{\"text\":\"Life Expectancy\",\"font\":{\"weight\":\"bold\",\"size\":14,\"family\":\"sans-serif\"}, \"color\":\"#993366\"}" + 175 | "}"; 176 | 177 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 178 | throws IOException, ServletException { 179 | 180 | String type = request.getParameter("type"); 181 | response.setContentType("application/json"); 182 | response.setHeader("Access-Control-Allow-Origin", "*"); 183 | response.setStatus(HttpServletResponse.SC_OK); 184 | baseRequest.setHandled(true); 185 | if (type.equals("hbar")) { 186 | response.getWriter().println(jsonHBar); 187 | } else if (type.equals("bar")) { 188 | response.getWriter().println(jsonBar); 189 | } else if (type.equals("stackedbar")) { 190 | response.getWriter().println(jsonStacked); 191 | } else if (type.equals("line")) { 192 | response.getWriter().println(jsonLine); 193 | } else if (type.equals("pie")) { 194 | response.getWriter().println(jsonPie); 195 | } else if (type.equals("bubble")) { 196 | response.getWriter().println(jsonBubble); 197 | } else { 198 | response.getWriter().println(jsonStackLine); 199 | } 200 | } 201 | 202 | public static void main(String[] args) throws Exception { 203 | Server server = new Server(8080); 204 | server.setHandler(new JsonHandler()); 205 | 206 | server.start(); 207 | server.join(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/js/alarm.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Alarm (status) 4 | * 5 | * { 6 | * "text" : "Project is on track.", 7 | * "color" : "green", 8 | * "background" : "white", 9 | * "shadow" : true 10 | * } 11 | * 12 | */ 13 | function alarm(id, myjson, zoom, useParentWidth) { 14 | 15 | if (useParentWidth) { 16 | window.addEventListener('resize', resizeAlarmCanvas, false); 17 | } 18 | 19 | drawAlarm(true); 20 | 21 | function drawAlarm(animate) { 22 | 23 | var can = document.getElementById(id); 24 | if (can == null) { 25 | return; 26 | } 27 | var ctx = can.getContext('2d'); 28 | 29 | if (zoom == true) { 30 | can.width = $(window).width(); 31 | can.height = $(window).height(); 32 | } 33 | 34 | if (useParentWidth) { 35 | can.width = can.parentNode.offsetWidth; 36 | } 37 | 38 | var canWidth = can.width; 39 | var canHeight = can.height; 40 | var textSize = canHeight/10; 41 | 42 | var text = myjson.text; 43 | if (typeof text === "undefined") { 44 | text = ""; 45 | } 46 | 47 | var color = myjson.color; 48 | if (typeof color === "undefined") { 49 | color = "green"; 50 | } 51 | 52 | var background = myjson.background; 53 | if (typeof background === "undefined") { 54 | background = "white"; 55 | } 56 | 57 | var shadow = myjson.shadow; 58 | if (typeof shadow === "undefined") { 59 | shadow = false; 60 | } 61 | 62 | if (shadow) { 63 | ctx.shadowColor = "rgba(0,0,0,0.15)"; 64 | ctx.shadowOffsetX = 3; 65 | ctx.shadowOffsetY = 3; 66 | ctx.shadowBlur = 2; 67 | } 68 | 69 | var size = canWidth; 70 | if (canHeight < canWidth) { 71 | size = canHeight; 72 | } 73 | var left = canWidth/20; // left padding 74 | var x = Math.pow(size,1.1)/8; // top-bottom padding 75 | var d = 2; // distance between circle and border 76 | var radius = (size - 2 * x) / 2; 77 | var fontSize = Math.log(size/20)*9; 78 | 79 | // clear canvas 80 | ctx.clearRect(0, 0, can.width, can.height); 81 | ctx.fillStyle = background; 82 | ctx.fillRect(0, 0, can.width, can.height); 83 | 84 | //text 85 | ctx.fillStyle = "black"; 86 | ctx.font=fontSize + "px Arial"; 87 | 88 | var xText = 3*left/2+ 2*radius; 89 | var yText = canHeight/2+ fontSize/4; 90 | var textWidth = ctx.measureText(text).width + left; 91 | var lines = new Array(); 92 | 93 | if (xText + textWidth > canWidth) { 94 | // text fills multiple lines 95 | var res = text.split(" "); 96 | var words = res.length; 97 | var line = ""; 98 | for (var k=0; k canWidth) { 102 | lines.push(line); 103 | line = res[k] + " "; 104 | } else { 105 | line = sline; 106 | } 107 | } 108 | lines.push(line); 109 | var linesNo = lines.length; 110 | var odd = ((linesNo % 2) != 0); 111 | var mid = Math.floor(linesNo/2); 112 | 113 | if (odd) { 114 | for (var line=0; line 2*d) { 193 | ctx.beginPath(); 194 | ctx.arc(x,y,r-2*d,2* Math.PI , 0, false); 195 | ctx.closePath(); 196 | ctx.stroke(); 197 | ctx.fillStyle = grd; 198 | ctx.fill(); 199 | } 200 | } 201 | 202 | function resizeAlarmCanvas() { 203 | var can = document.getElementById(id); 204 | if (can != null) { 205 | drawAlarm(false); 206 | } 207 | } 208 | 209 | } 210 | 211 | -------------------------------------------------------------------------------- /src/js/bubblechart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Json must contain as mandatory only "data" attribute 3 | * 4 | * type -> bubble 5 | * style -> normal, glass 6 | * labelOrientation -> horizontal, vertical, diagonal, halfdiagonal 7 | * showLabels -> true means X labels are shown on X axis; we can use false if we want to show them in message tooltip with #x 8 | * message -> can have markup #val for value, #x for x value, #z for size value, #c for category value, #label for id label 9 | * -> can contain
to split text on more lines 10 | * title.alignment -> center, left, right 11 | * styleGridX, styleGridY -> line, dot, dash 12 | * onClick -> is a javascript function like 'function doClick(value){ ...}' 13 | * 14 | * data contains : x, y, z 15 | * labels contains ids array (as an exception of other charts, we will not draw them on X axis, but other labels obtained as X ticks from x data) 16 | * categories contains categories array 17 | * color contains colors only for distinct categories 18 | * legend must be computed from categories (unique categories) 19 | * 20 | * { "type": "bubble" 21 | * "style": "normal", 22 | * "background" : "white", 23 | * "data": [[80.66,79.84,78.6,72.73,80.05,72.49,68.09,81.55,68.6,78.09], [ 1.67, 1.36, 1.84, 2.78, 2, 1.7, 4.77, 2.96, 1.54, 2.05 ], [ 33739900, 81902307, 5523095, 79716203, 61801570, 73137148, 31090763, 7485600, 141850000, 307007000 ] ], 24 | * "labels": ["CAN", "DEU", "DNK", "EGY", "GBN", "IRN", "IRQ", "ISR", "RUS", "USA"], 25 | * "categories": [ "North America", "Europe", "Europe", "Middle East", "Europe", "Middle East", "Middle East", "Middle East", "Europe", "North America" ] 26 | * "labelOrientation": "horizontal", 27 | * "color": ["#004CB3","#A04CB3", "#7aa37a"], 28 | * "alpha" : 0.8, 29 | * "colorXaxis": "blue", 30 | * "colorYaxis": "blue", 31 | * "showGridX": true, 32 | * "showGridY": true, 33 | * "showLabels": true, 34 | * "colorGridX": "rgb(248, 248, 216)", 35 | * "colorGridY": "rgb(248, 248, 216)", 36 | * "styleGridX": "line", 37 | * "styleGridY": "line", 38 | * "message" : "Value \: #val", 39 | * "showTicks" : true, 40 | * "tickCount" : 5, 41 | * "startingFromZero" : false, 42 | * "title" : { 43 | * "text": "Correlation between life expectancy, fertility rate and population", 44 | * "font": { 45 | * "weight": "bold", 46 | * "size": "16", 47 | * "family": "sans-serif" 48 | * }, 49 | * "color": "#000000", 50 | * "alignment":"left" 51 | * }, 52 | * "xData" : { 53 | * "font": { 54 | * "weight": "bold", 55 | * "size": "16", 56 | * "family": "sans-serif" 57 | * }, 58 | * "color": "blue" 59 | * }, 60 | * "yData" : { 61 | * "font": { 62 | * "weight": "bold", 63 | * "size": "16", 64 | * "family": "sans-serif" 65 | * }, 66 | * "color": "blue" 67 | * }, 68 | * "xLegend" : { 69 | * "text": "Life Expectancy", 70 | * "font": { 71 | * "weight": "bold", 72 | * "size": "16", 73 | * "family": "sans-serif" 74 | * }, 75 | * "color": "blue" 76 | * }, 77 | * "yLegend" : { 78 | * "text": "Fertility Rate", 79 | * "font": { 80 | * "weight": "bold", 81 | * "size": "16", 82 | * "family": "sans-serif" 83 | * }, 84 | * "color": "blue" 85 | * }, 86 | * "tooltipPattern" : { 87 | * "decimals": 2, 88 | * "decimalSeparator" : ".", 89 | * "thousandSeparator" : "," 90 | * }, 91 | * "onClick" : "function doClick(value){console.log("Call from function: " + value);}" 92 | * } 93 | * 94 | */ 95 | 96 | var bubbleChart = function(myjson, idCan, idTipCan, canWidth, canHeight) { 97 | 98 | var obj; 99 | var data; 100 | var labels = new Array(); 101 | var labelOrientation; 102 | var globalAlpha; 103 | var showGridX; 104 | var showGridY; 105 | var showLabels; 106 | var background; 107 | var message; 108 | var tickCount; 109 | var showTicks; 110 | var startingFromZero; 111 | var chartType; 112 | var chartStyle; 113 | var seriesColor; 114 | var xData, yData; 115 | var series; 116 | var uniqueCategories = new Array(); 117 | var yStep; 118 | var dotsK = new Array(); 119 | var max; 120 | var min; 121 | //space between 2 ticks 122 | var tickStep; 123 | 124 | var minValY; 125 | var maxValY; 126 | // bottom vertical space (to fit X labels and X legend) 127 | var step = 0; 128 | var gap = 40; 129 | // left horizontal space (to fit bigger Y labels and Y legend) 130 | var hStep = 60; 131 | var titleSpace = 0; 132 | var legendSpace = 0; 133 | var xLegendSpace = 0; 134 | var yLegendSpace = 0; 135 | var xaxisY = 0; 136 | var realWidth; 137 | var realHeight; 138 | var canvas; 139 | var c; 140 | var tipCanvas; 141 | var tempCanvas; 142 | var tempCtx; 143 | var H = new Array(); 144 | var dotRadius = 3; 145 | // space between X axis and first tick 146 | var tickInit; 147 | var resizeWidth = false; 148 | var resizeHeight = false; 149 | //by default chart title, legends and axis values strings have a defined font size 150 | //if we want to have font size scaled accordingly with chart width then this property must be true 151 | //(such example may be when we want to show the chart on a big monitor) 152 | var adjustableTextFontSize = false; 153 | var highlighterIndex = -1; 154 | 155 | function drawBubble(myjson, idCan, idTipCan, canWidth, canHeight) { 156 | 157 | canvas = document.getElementById(idCan); 158 | if (canvas == null) { 159 | return; 160 | } 161 | tipCanvas = document.getElementById(idTipCan); 162 | c = canvas.getContext('2d'); 163 | 164 | tempCanvas = document.createElement('canvas'); 165 | tempCtx = tempCanvas.getContext('2d'); 166 | 167 | obj = myjson; 168 | chartType = obj.type; 169 | if (typeof chartType === "undefined") { 170 | chartType = "bubble"; 171 | } 172 | 173 | background = obj.background; 174 | if (typeof background === "undefined") { 175 | background = "white"; 176 | } 177 | 178 | chartStyle = obj.style; 179 | // test for passing a wrong style 180 | if ((typeof chartStyle === "undefined") || (find(['normal', 'glass'],chartStyle) === false)) { 181 | chartStyle = "normal"; 182 | } 183 | 184 | data = obj.data[0]; 185 | for (var i=0;i 0)) { 254 | min = 0; 255 | } 256 | 257 | var objStep = calculateYStep(min, max, tickCount); 258 | yStep = objStep.yStep; 259 | minValY = objStep.minValY; 260 | maxValY = objStep.maxValY; 261 | 262 | // compute X labels 263 | var xmax = Math.max.apply( Math, obj.data[0]); 264 | var xmin = Math.min.apply( Math, obj.data[0]); 265 | var xobjStep = calculateYStep(xmin, xmax, tickCount); 266 | var x_yStep = xobjStep.yStep; 267 | var minValX = xobjStep.minValY; 268 | var maxValX = xobjStep.maxValY; 269 | for(var i=0; i= H[j]) { 432 | test = true; 433 | } 434 | } 435 | if (test) { 436 | stop = false; 437 | } 438 | } else { 439 | stop = false; 440 | } 441 | 442 | var sColor = getSeriesColor(i); 443 | c.strokeStyle = sColor; 444 | 445 | if (chartStyle == "glass") { 446 | var gradient = c.createLinearGradient( dataX-2*H[i], dataY-2*H[i], 4*H[i], 4*H[i] ); 447 | gradient.addColorStop( 0, "#ddd" ); 448 | gradient.addColorStop( 1, sColor ); 449 | c.fillStyle = gradient; 450 | } else { 451 | c.fillStyle = sColor; 452 | } 453 | 454 | c.beginPath(); 455 | c.arc(dataX, dataY, H[i], 0,Math.PI*2); 456 | var fs = c.fillStyle; 457 | if (i == highlighterIndex) { 458 | c.fillStyle = highlightColor(sColor,0.5); 459 | } 460 | if (withFill) { 461 | c.globalAlpha = globalAlpha; 462 | c.fill(); 463 | c.globalAlpha = 1; 464 | var oldStroke = c.strokeStyle; 465 | c.strokeStyle = "#fff"; 466 | c.stroke(); 467 | c.strokeStyle = oldStroke; 468 | } else { 469 | // highlight selection 470 | if (c.isPointInPath(mousePos.x, mousePos.y)) { 471 | hit = true; 472 | } 473 | if (!hit) { 474 | highlighterIndex = -1; 475 | } 476 | 477 | var found; 478 | if (c.isPointInPath(mousePos.x, mousePos.y)) { 479 | if ((smallestRadius == undefined) || (smallestRadius > radius[i])) { 480 | smallestRadius = radius[i]; 481 | smallestIndex = i; 482 | } 483 | if (i == smallestIndex) { 484 | highlighterIndex = i; 485 | } 486 | var tValue = obj.data[1][i]; 487 | if (obj.tooltipPattern !== undefined) { 488 | tValue = formatNumber(tValue, obj.tooltipPattern.decimals, obj.tooltipPattern.decimalSeparator, obj.tooltipPattern.thousandSeparator); 489 | } 490 | var returnValue = obj.data[0][i]; // tValue 491 | if (withClick) { 492 | if (found === undefined) { 493 | found = returnValue; 494 | } 495 | } else { 496 | var mes = String(message).replace('#val', tValue); 497 | mes = mes.replace('#x', returnValue); 498 | mes = mes.replace('#z', obj.data[2][i]); 499 | mes = mes.replace('#c', obj.categories[i]); 500 | mes = mes.replace('#label', obj.labels[i]); 501 | if (obj.onClick !== undefined) { 502 | cursorStyle = 'pointer'; 503 | canvas.style.cursor = cursorStyle; 504 | } 505 | if (found === undefined) { 506 | found = mes; 507 | } 508 | } 509 | } else { 510 | if (cursorStyle != 'pointer') { 511 | canvas.style.cursor = 'default'; 512 | } 513 | } 514 | } 515 | } 516 | 517 | if (found !== undefined) { 518 | return found; 519 | } 520 | 521 | if (withFill) { 522 | unhighlight(); 523 | return stop; 524 | } else { 525 | // empty tooltip message 526 | return ""; 527 | } 528 | } 529 | 530 | function unhighlight(i) { 531 | c.drawImage(tempCanvas, 0, 0); 532 | } 533 | 534 | function getYValue(i, maxValY, yStep) { 535 | var label; 536 | if (obj.tooltipPattern !== undefined) { 537 | // y labels can have more than two decimals 538 | var decimals = obj.tooltipPattern.decimals; 539 | var exp = Math.pow(10, decimals); 540 | label = Math.round((maxValY-i*yStep)*exp)/exp; 541 | } else { 542 | label = Math.round((maxValY-i*yStep)*100)/100; 543 | } 544 | return label; 545 | } 546 | 547 | function getSeriesColor(i) { 548 | var cat = obj.categories[i]; 549 | var ind = find(uniqueCategories,cat); 550 | if (ind === false) { 551 | return "white"; 552 | } 553 | return seriesColor[ind]; 554 | } 555 | 556 | function drawInit() { 557 | 558 | var font = c.font; 559 | 560 | //draw background (clear canvas) 561 | c.fillStyle = background; 562 | c.fillRect(0,0,realWidth,realHeight); 563 | 564 | // adjust step with X label space (x label can have different orientations) and X legend space 565 | var xLabelWidth = computeVStep(); 566 | 567 | //draw title 568 | if (typeof obj.title !== "undefined") { 569 | var titleColor = obj.title.color; 570 | if (titleColor === undefined) { 571 | titleColor = '#000000'; 572 | } 573 | c.fillStyle = titleColor; 574 | var b = " "; 575 | var f = obj.title.font; 576 | if (f === undefined) { 577 | f.weight = "bold"; 578 | f.size = 12; 579 | f.family = "sans-serif"; 580 | } 581 | if (adjustableTextFontSize) { 582 | f.size=getAdjustableTitleFontSize(); 583 | } 584 | c.font = f.weight + b + f.size + "px" + b + f.family; 585 | var titlePadding = 20; 586 | if (adjustableTextFontSize) { 587 | titlePadding = getAdjustableTitleFontSize()/2; 588 | } 589 | titleSpace = +titlePadding + +f.size; 590 | 591 | var alignment = obj.title.alignment; 592 | if (alignment === undefined) { 593 | alignment = "center"; 594 | } 595 | var xTitle; 596 | if (alignment == "left") { 597 | xTitle = hStep; 598 | } else if (alignment == "right") { 599 | xTitle = canvas.width - c.measureText(obj.title.text).width - 10; 600 | } else { 601 | // center 602 | xTitle = canvas.width/2- c.measureText(obj.title.text).width/2; 603 | } 604 | 605 | var titlePadding = 20; 606 | if (adjustableTextFontSize) { 607 | titlePadding = getAdjustableTitleFontSize()/2; 608 | } 609 | c.fillText(obj.title.text, xTitle , titlePadding+titleSpace/2 ); 610 | c.font = font; 611 | } else { 612 | titleSpace = 10; 613 | } 614 | 615 | 616 | //draw X legend 617 | if (typeof obj.xLegend !== "undefined") { 618 | 619 | var xLegendColor = obj.xLegend.color; 620 | if (xLegendColor === undefined) { 621 | xLegendColor = '#000000'; 622 | } 623 | c.fillStyle = xLegendColor; 624 | var b = " "; 625 | var f = obj.xLegend.font; 626 | if (f === undefined) { 627 | f.weight = "bold"; 628 | f.size = 12; 629 | f.family = "sans-serif"; 630 | } 631 | if (adjustableTextFontSize) { 632 | f.size = getAdjustableLabelFontSize(); 633 | } 634 | c.font = f.weight + b + f.size + "px" + b + f.family; 635 | var legendPadding = 20; 636 | if (adjustableTextFontSize) { 637 | legendPadding = getAdjustableLabelFontSize()/2; 638 | } 639 | xLegendSpace = +legendPadding + +f.size; 640 | 641 | c.fillText(obj.xLegend.text, realWidth/2- c.measureText(obj.xLegend.text).width/2 , realHeight - f.size ); 642 | c.font = font; 643 | } else { 644 | xLegendSpace = 0; 645 | } 646 | 647 | //draw Y legend 648 | if (typeof obj.yLegend !== "undefined") { 649 | var yLegendColor = obj.yLegend.color; 650 | if (yLegendColor === undefined) { 651 | yLegendColor = '#000000'; 652 | } 653 | var b = " "; 654 | var f = obj.yLegend.font; 655 | if (f === undefined) { 656 | f.weight = "bold"; 657 | f.size = 12; 658 | f.family = "sans-serif"; 659 | } 660 | if (adjustableTextFontSize) { 661 | f.size = getAdjustableLabelFontSize(); 662 | } 663 | c.font = f.weight + b + f.size + "px" + b + f.family; 664 | c.fillStyle = yLegendColor; 665 | c.save(); 666 | c.translate(10 , realHeight/2); 667 | c.rotate(-Math.PI/2); 668 | c.textAlign = "center"; 669 | c.fillText(obj.yLegend.text,0, f.size); 670 | c.restore(); 671 | c.font = font; 672 | } else { 673 | yLegendSpace = 0; 674 | } 675 | 676 | // draw legend 677 | if (typeof obj.legend !== "undefined") { 678 | var x = hStep; 679 | c.font = "bold 10px sans-serif"; 680 | if (adjustableTextFontSize) { 681 | c.font = "bold " + getAdjustableLabelFontSize() + "px sans-serif"; 682 | } 683 | var legendPadding = 20; 684 | if (adjustableTextFontSize) { 685 | legendPadding = getAdjustableLabelFontSize(); 686 | } 687 | legendSpace = legendPadding; 688 | var legendY = titleSpace+legendPadding; 689 | c.globalAlpha = globalAlpha; 690 | for (var k=0; k realWidth) { 695 | // draw legend on next line if does not fit on current one 696 | x = hStep; 697 | var lineSpace = 14; 698 | if (adjustableTextFontSize) { 699 | lineSpace = getAdjustableLabelFontSize(); 700 | } 701 | legendY = legendY + lineSpace; 702 | var legendPadding = 20; 703 | if (adjustableTextFontSize) { 704 | legendPadding = getAdjustableLabelFontSize(); 705 | } 706 | legendSpace += legendPadding; 707 | } 708 | 709 | c.fillText("---- " + obj.legend[k], x, legendY); 710 | 711 | x = x + legendWidth; 712 | } 713 | c.globalAlpha = 1; 714 | c.font = font; 715 | } 716 | 717 | 718 | 719 | c.font = font; 720 | 721 | 722 | // adjust tickStep depending if title or legend are present or not 723 | tickStep = (realHeight-step-titleSpace-legendSpace-tickInit)/tickCount; 724 | 725 | // compute Y value for xAxis 726 | xaxisY = tickCount*tickStep+tickInit+titleSpace+legendSpace; 727 | 728 | drawLabels(xLabelWidth); 729 | } 730 | 731 | 732 | function drawLabels(xLabelWidth) { 733 | 734 | var font = c.font; 735 | 736 | //draw Y labels and small lines 737 | if (showTicks) { 738 | c.fillStyle = "black"; 739 | if (obj.yData !== undefined) { 740 | c.fillStyle = obj.yData.color; 741 | var b = " "; 742 | var yfont = obj.yData.font; 743 | if (adjustableTextFontSize) { 744 | yfont.size=getAdjustableLabelFontSize(); 745 | } 746 | c.font = yfont.weight + b + yfont.size + "px" + b + yfont.family; 747 | } 748 | for(var i=0; i maxLabelWidth) { 963 | maxLabelWidth = labelWidth; 964 | } 965 | } 966 | result = maxLabelWidth + 20; 967 | } else { 968 | result = 20; 969 | } 970 | c.font = font; 971 | if (typeof obj.yLegend !== "undefined") { 972 | var b = " "; 973 | var f = obj.yLegend.font; 974 | if (f === undefined) { 975 | f.weight = "bold"; 976 | f.size = 12; 977 | f.family = "sans-serif"; 978 | } 979 | if (adjustableTextFontSize) { 980 | f.size = getAdjustableLabelFontSize(); 981 | } 982 | c.font = f.weight + b + f.size + "px" + b + f.family; 983 | var legendPadding = 20; 984 | if (adjustableTextFontSize) { 985 | legendPadding = getAdjustableLabelFontSize(); 986 | } 987 | yLegendSpace = +legendPadding + +f.size; 988 | c.font = font; 989 | result += yLegendSpace; 990 | } 991 | 992 | // take care for halfdiagonal, diagonal long labels 993 | // if they are too long hStep must be increased accordingly 994 | var cf = c.font; 995 | if (obj.xData !== undefined) { 996 | var b = " "; 997 | var xfont = obj.xData.font; 998 | if (adjustableTextFontSize) { 999 | xfont.size=getAdjustableLabelFontSize(); 1000 | } 1001 | c.font = xfont.weight + b + xfont.size + "px" + b + xfont.family; 1002 | } 1003 | var minPos = new Array(); 1004 | for(var i=0; i 0) { 1020 | if (len < 10) { 1021 | result += (10 - len); 1022 | } 1023 | } 1024 | 1025 | return result; 1026 | } 1027 | 1028 | // computes vertical step needed 1029 | // returns maximum width for x labels 1030 | function computeVStep() { 1031 | var xLabelWidth = 0; 1032 | if (typeof obj.xData !== "undefined") { 1033 | var xfont = obj.xData.font; 1034 | if (adjustableTextFontSize) { 1035 | xfont.size=getAdjustableLabelFontSize(); 1036 | } 1037 | var b = " "; 1038 | c.font = xfont.weight + b + xfont.size + "px" + b + xfont.family; 1039 | } 1040 | if (showLabels) { 1041 | for(var i=0; i xLabelWidth) { 1044 | xLabelWidth = labelWidth; 1045 | } 1046 | } 1047 | } 1048 | var _xLegendSpace = 0; 1049 | if (typeof obj.xLegend !== "undefined") { 1050 | var f = obj.xLegend.font; 1051 | if (f === undefined) { 1052 | f.weight = "bold"; 1053 | f.size = 12; 1054 | f.family = "sans-serif"; 1055 | } 1056 | if (adjustableTextFontSize) { 1057 | f.size = getAdjustableLabelFontSize(); 1058 | } 1059 | var legendPadding = 20; 1060 | if (adjustableTextFontSize) { 1061 | legendPadding = getAdjustableLabelFontSize(); 1062 | } 1063 | _xLegendSpace = +legendPadding + +f.size; 1064 | } 1065 | if ((step < xLabelWidth+_xLegendSpace) || adjustableTextFontSize) { 1066 | step = xLabelWidth+_xLegendSpace; 1067 | } 1068 | return xLabelWidth; 1069 | } 1070 | 1071 | // depends on label orientation 1072 | function computeXLabelSpace(label) { 1073 | var _labelWidth = c.measureText(label).width + 10; // vertical 1074 | 1075 | if (labelOrientation === "horizontal") { 1076 | if (typeof obj.xData !== "undefined") { 1077 | _labelWidth = obj.xData.font.size + 20; 1078 | if (adjustableTextFontSize) { 1079 | _labelWidth=getAdjustableLabelFontSize() + 20; 1080 | } 1081 | } else { 1082 | _labelWidth = 12 + 20; 1083 | } 1084 | } else if (labelOrientation === "halfdiagonal") { 1085 | _labelWidth = c.measureText(label).width * Math.sin(Math.PI/8) + 20; 1086 | } else if (labelOrientation === "diagonal") { 1087 | _labelWidth = c.measureText(label).width * Math.sin(Math.PI/4) + 20; 1088 | } 1089 | 1090 | return _labelWidth; 1091 | } 1092 | 1093 | function getTitleSpace() { 1094 | var space = 10; 1095 | if (typeof obj.title !== "undefined") { 1096 | var f = obj.title.font; 1097 | if (f === undefined) { 1098 | f.size = 12; 1099 | } 1100 | if (adjustableTextFontSize) { 1101 | f.size=getAdjustableTitleFontSize(); 1102 | } 1103 | var titlePadding = 20; 1104 | if (adjustableTextFontSize) { 1105 | titlePadding = getAdjustableTitleFontSize(); 1106 | } 1107 | space = +titlePadding + +f.size; 1108 | } 1109 | return space; 1110 | } 1111 | 1112 | function getXLegendSpace() { 1113 | var _xLegendSpace = 0; 1114 | if (typeof obj.xLegend !== "undefined") { 1115 | var f = obj.xLegend.font; 1116 | if (f === undefined) { 1117 | f.size = 12; 1118 | } 1119 | var legendPadding = 20; 1120 | if (adjustableTextFontSize) { 1121 | f.size = getAdjustableLabelFontSize(); 1122 | legendPadding = getAdjustableLabelFontSize(); 1123 | } 1124 | _xLegendSpace = +legendPadding + +f.size; 1125 | } 1126 | return _xLegendSpace; 1127 | } 1128 | 1129 | function getLegendSpace() { 1130 | var font = c.font; 1131 | var _legendSpace = 20; 1132 | if (typeof obj.legend !== "undefined") { 1133 | var x = hStep; 1134 | c.font = "bold 10px sans-serif"; 1135 | if (adjustableTextFontSize) { 1136 | c.font ="bold " + getAdjustableLabelFontSize() + "px sans-serif"; 1137 | } 1138 | var legendPadding = 20; 1139 | if (adjustableTextFontSize) { 1140 | legendPadding = getAdjustableLabelFontSize()/2; 1141 | } 1142 | var legendY = getTitleSpace()+legendPadding; 1143 | for (var k=0; k realWidth) { 1147 | // draw legend on next line if does not fit on current one 1148 | x = hStep; 1149 | var lineSpace = 14; 1150 | if (adjustableTextFontSize) { 1151 | lineSpace = getAdjustableLabelFontSize(); 1152 | } 1153 | legendY = legendY + lineSpace; 1154 | var pad = 20; 1155 | if (adjustableTextFontSize) { 1156 | pad = getAdjustableLabelFontSize(); 1157 | } 1158 | _legendSpace += pad; 1159 | } 1160 | 1161 | x = x + legendWidth; 1162 | } 1163 | } 1164 | c.font = font; 1165 | return _legendSpace; 1166 | } 1167 | 1168 | function resizeCanvas() { 1169 | var can = document.getElementById(idCan); 1170 | if (can != null) { 1171 | var w = canWidth; 1172 | if (resizeWidth) { 1173 | if (!isPercent(w)) { 1174 | w = "100%"; 1175 | } 1176 | } 1177 | var h = canHeight; 1178 | if (resizeHeight) { 1179 | if (!isPercent(h)) { 1180 | h = "100%"; 1181 | } 1182 | } 1183 | updateSize(w, h); 1184 | drawChart(); 1185 | } 1186 | } 1187 | 1188 | function getAdjustableTitleFontSize() { 1189 | return canvas.width/25; 1190 | } 1191 | 1192 | function getAdjustableLabelFontSize() { 1193 | return canvas.width/45; 1194 | } 1195 | 1196 | drawBubble(myjson, idCan, idTipCan, canWidth, canHeight); 1197 | 1198 | }; 1199 | 1200 | -------------------------------------------------------------------------------- /src/js/chart-util.js: -------------------------------------------------------------------------------- 1 | // This function starts by creating a dummy element which is never 2 | // attached to the page, so no one will ever see it. 3 | // As soon as we create the dummy element, we test for the presence 4 | // of a getContext() method. This method will only exist if browser supports the canvas API. 5 | // Finally, we use the double-negative trick to force the result to a Boolean value (true or false). 6 | function isCanvasEnabled() { 7 | return !!document.createElement('canvas').getContext; 8 | } 9 | 10 | 11 | function getParentWidth(id) { 12 | return document.getElementById(id).parentNode.offsetWidth; 13 | } 14 | 15 | function getWidth(element) { 16 | return element.offsetWidth; 17 | } 18 | 19 | 20 | function niceNum(range, round) { 21 | var exponent; /** exponent of range */ 22 | var fraction; /** fractional part of range */ 23 | var niceFraction; /** nice, rounded fraction */ 24 | 25 | exponent = Math.floor(getBaseLog(10, range)); 26 | fraction = range / Math.pow(10, exponent); 27 | 28 | if (round) { 29 | if (fraction < 1.5) 30 | niceFraction = 1; 31 | else if (fraction < 3) 32 | niceFraction = 2; 33 | else if (fraction < 7) 34 | niceFraction = 5; 35 | else 36 | niceFraction = 10; 37 | } else { 38 | if (fraction <= 1) 39 | niceFraction = 1; 40 | else if (fraction <= 2) 41 | niceFraction = 2; 42 | else if (fraction <= 5) 43 | niceFraction = 5; 44 | else 45 | niceFraction = 10; 46 | } 47 | 48 | return niceFraction * Math.pow(10, exponent); 49 | } 50 | 51 | //returns the logarithm of y with base x 52 | function getBaseLog(x, y) { 53 | return Math.log(y) / Math.log(x); 54 | } 55 | 56 | function calculateYStep(min, max, tickCount) { 57 | if (min == max) { 58 | max = min + 1; 59 | } 60 | var range = niceNum(max - min, false); 61 | var tickSpacing = niceNum(range / (tickCount - 1), true); 62 | var minValY = Math.floor(min / tickSpacing) * tickSpacing; 63 | var maxValY = Math.ceil(max / tickSpacing) * tickSpacing; 64 | if ((minValY == min) && (minValY != 0)) { 65 | minValY = minValY - tickSpacing; 66 | } 67 | return { minValY: minValY, maxValY: maxValY, yStep:(maxValY - minValY)/tickCount}; 68 | } 69 | 70 | function getMousePos(canvas, evt) { 71 | // use jquery to be browser independent when computing scrollLeft and scrollTop 72 | var root = $(window); 73 | // return relative mouse position 74 | var parent = $(canvas).parent(); 75 | var mouseX, mouseY; 76 | if (parent.is('body')) { 77 | mouseX = evt.clientX - canvas.offsetLeft + root.scrollLeft(); 78 | mouseY = evt.clientY - canvas.offsetTop + root.scrollTop(); 79 | } else { 80 | mouseX = evt.clientX - parent.offset().left + root.scrollLeft(); 81 | mouseY = evt.clientY - parent.offset().top + root.scrollTop(); 82 | } 83 | 84 | return { 85 | x: mouseX, 86 | y: mouseY 87 | }; 88 | } 89 | 90 | function posTop() { 91 | return typeof window.pageYOffset != 'undefined' ? window.pageYOffset: document.documentElement.scrollTop? document.documentElement.scrollTop: document.body.scrollTop? document.body.scrollTop:0; 92 | } 93 | 94 | 95 | function posLeft() { 96 | return typeof window.pageYOffset != 'undefined' ? window.pageYOffset: document.documentElement.scrollTop? document.documentElement.scrollTop: document.body.scrollTop? document.body.scrollTop:0; 97 | } 98 | 99 | 100 | // mousePos and tooltip are defined outside function implementation 101 | var handleMouseEvent = { 102 | 103 | execute:function (canvas, tipCanvas, evt) { 104 | 105 | var tipCtx = tipCanvas.getContext('2d'); 106 | 107 | tipCanvas.style.left = (canvas.offsetLeft + this.mousePos.x) + "px"; 108 | tipCtx.clearRect(0, 0, tipCanvas.width, tipCanvas.height); 109 | var textWidth = tipCtx.measureText(this.tooltip).width; 110 | 111 | var x = tipCanvas.width/2 - textWidth/2; 112 | // gap between mouse cursor and tooltip 113 | var hgap = 40; 114 | var lines = this.tooltip.split("
"); 115 | if (lines.length == 1) { 116 | // a single line of text (do not need to modify tipCanvas height) 117 | tipCanvas.width = textWidth + 20; 118 | tipCanvas.height = 25; 119 | x = tipCanvas.width/2 - textWidth/2; 120 | tipCtx.fillText(this.tooltip, x, 15); 121 | } else { 122 | // more lines inside tooltip we should compute canvas width and height 123 | var rowsH = (lines.length-1)*12; 124 | tipCanvas.height = 25 + rowsH; 125 | hgap += rowsH; 126 | var lineMaxWidth = 0; 127 | for(var i=0; i < lines.length; i++) { 128 | var lineWidth = tipCtx.measureText(lines[i]).width; 129 | if (lineWidth > lineMaxWidth) { 130 | lineMaxWidth = lineWidth; 131 | } 132 | } 133 | if (tipCanvas.width <= lineMaxWidth) { 134 | tipCanvas.width = lineMaxWidth + 20; 135 | } 136 | x = tipCanvas.width/2 - lineMaxWidth/2; 137 | for(var i=0; i < lines.length; i++) { 138 | tipCtx.fillText(lines[i], x, 15 + i*12); 139 | } 140 | } 141 | // if tipCanvas surpasses canvas space then make it not to 142 | var endTipCanvas = tipCanvas.offsetLeft + tipCanvas.width; 143 | var endCanvas = canvas.offsetLeft + canvas.width; 144 | if (endTipCanvas > endCanvas) { 145 | tipCanvas.style.left = tipCanvas.offsetLeft - (endTipCanvas-endCanvas) + "px"; 146 | } 147 | 148 | tipCanvas.style.top = (canvas.offsetTop + this.mousePos.y - hgap) + "px"; 149 | if (this.tooltip == "") { 150 | tipCanvas.style.left = "-2000px"; 151 | } 152 | 153 | } 154 | }; 155 | 156 | function drawEllipseByCenter(ctx, cx, cy, w, h) { 157 | drawEllipse(ctx, cx - w/2.0, cy - h/2.0, w, h); 158 | } 159 | 160 | function drawUpperEllipseByCenter(ctx, cx, cy, w, h) { 161 | drawUpperEllipse(ctx, cx - w/2.0, cy - h/2.0, w, h); 162 | } 163 | 164 | function drawEllipse(ctx, x, y, w, h) { 165 | var kappa = .5522848, 166 | ox = (w / 2) * kappa, // control point offset horizontal 167 | oy = (h / 2) * kappa, // control point offset vertical 168 | xe = x + w, // x-end 169 | ye = y + h, // y-end 170 | xm = x + w / 2, // x-middle 171 | ym = y + h / 2; // y-middle 172 | 173 | ctx.moveTo(x, ym); 174 | ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); 175 | ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); 176 | ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); 177 | ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); 178 | } 179 | 180 | function drawUpperEllipse(ctx, x, y, w, h) { 181 | var kappa = .5522848, 182 | ox = (w / 2) * kappa, // control point offset horizontal 183 | oy = (h / 2) * kappa, // control point offset vertical 184 | xe = x + w, // x-end 185 | ye = y + h, // y-end 186 | xm = x + w / 2, // x-middle 187 | ym = y + h / 2; // y-middle 188 | 189 | ctx.moveTo(x, ym); 190 | ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); 191 | ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); 192 | } 193 | 194 | function findControlPoint(s1, s2, s3) { 195 | var // Unit vector, length of line s1,s3 196 | ux1 = s3.x - s1.x, 197 | uy1 = s3.y - s1.y, 198 | ul1 = Math.sqrt(ux1*ux1 + uy1*uy1), 199 | u1 = { x: ux1/ul1, y: uy1/ul1 }, 200 | 201 | // Unit vector, length of line s1,s2 202 | ux2 = s2.x - s1.x, 203 | uy2 = s2.y - s1.y, 204 | ul2 = Math.sqrt(ux2*ux2 + uy2*uy2), 205 | u2 = { x: ux2/ul2, y: uy2/ul2 }, 206 | 207 | // Dot product 208 | k = u1.x*u2.x + u1.y*u2.y, 209 | 210 | // Project s2 onto s1,s3 211 | il1 = { x: s1.x+u1.x*k*ul2, y: s1.y+u1.y*k*ul2 }, 212 | 213 | // Unit vector, length of s2,il1 214 | dx1 = s2.x - il1.x, 215 | dy1 = s2.y - il1.y, 216 | dl1 = Math.sqrt(dx1*dx1 + dy1*dy1), 217 | d1 = { x: dx1/dl1, y: dy1/dl1 }, 218 | 219 | // Midpoint 220 | mp = { x: (s1.x+s3.x)/2, y: (s1.y+s3.y)/2 }, 221 | 222 | // Control point on s2,il1 223 | cpm = { x: s2.x+d1.x*dl1, y: s2.y+d1.y*dl1 }, 224 | 225 | // Translate based on distance from midpoint 226 | tx = il1.x - mp.x, 227 | ty = il1.y - mp.y, 228 | cp = { x: cpm.x+tx, y: cpm.y+ty }; 229 | 230 | return cp; 231 | } 232 | 233 | // draw a curve through three points 234 | function drawCurve(ctx, p1, p2, p3) { 235 | var cp = findControlPoint(p1, p2, p3); 236 | ctx.moveTo(p1.x, p1.y); 237 | ctx.quadraticCurveTo(cp.x,cp.y,p3.x,p3.y); 238 | } 239 | 240 | function formatNumber(num, decimals, decimalSeparator, thousandSeparator) { 241 | var nStr = num.toFixed(decimals); 242 | nStr = nStr.replace('.', decimalSeparator); 243 | return addThousandSeparator(nStr, decimalSeparator, thousandSeparator); 244 | } 245 | 246 | function addThousandSeparator(nStr, decimalSeparator, thousandSeparator) { 247 | nStr += ''; 248 | x = nStr.split(decimalSeparator); 249 | x1 = x[0]; 250 | x2 = x.length > 1 ? decimalSeparator + x[1] : ''; 251 | var rgx = /(\d+)(\d{3})/; 252 | while (rgx.test(x1)) { 253 | x1 = x1.replace(rgx, '$1' + thousandSeparator + '$2'); 254 | } 255 | return x1 + x2; 256 | } 257 | 258 | function getParameterValueFromURL(url, name ){ 259 | name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); 260 | var regexS = "[\\?&]"+name+"=([^&#]*)"; 261 | var regex = new RegExp( regexS ); 262 | var results = regex.exec( url ); 263 | if( results == null ) return ""; 264 | else return results[1]; 265 | } 266 | 267 | function isPercent(value) { 268 | if (value === undefined) { 269 | return false; 270 | } 271 | var index = value.indexOf("%"); 272 | if (index == value.length-1) { 273 | return true; 274 | } 275 | return false; 276 | } 277 | 278 | function find(array, v) { 279 | for (i=0;i 160) { 161 | // too bright, need to darken it 162 | if (lum > 0) { 163 | lum = -lum; 164 | } 165 | } else { 166 | if (lum < 0) { 167 | lum = -lum; 168 | } 169 | } 170 | var result = colorLuminance(color, lum); 171 | if (color == result) { 172 | // avoid same color 173 | result = colorLuminance2(color, lum); 174 | } 175 | return result; 176 | } 177 | -------------------------------------------------------------------------------- /src/js/display.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Display a formatted value 4 | * 5 | * Only value is mandatory to display it. 6 | * If previous is defined, an arrow up/down will be shown if value is bigger/less than previous. 7 | * Arrow's color is green or red, depending on shouldRise parameter: 8 | * shouldRise=true -> up=green and down=red 9 | * shouldRise=false -> up=red and down=green 10 | * 11 | * up=true (value>previous) 12 | * up=false (value canWidth) { 60 | canHeight = canWidth; 61 | } 62 | var valSize = canHeight/5; 63 | var titleSize = canHeight/10; 64 | 65 | var background = myjson.background; 66 | if (typeof background === "undefined") { 67 | background = "white"; 68 | } 69 | var shadow = myjson.shadow; 70 | if (typeof shadow === "undefined") { 71 | shadow = false; 72 | } 73 | var value = myjson.value; 74 | if (typeof value === "undefined") { 75 | value = "NA"; 76 | } 77 | var valueColor = myjson.valueColor; 78 | if (typeof valueColor === "undefined") { 79 | valueColor = "black"; 80 | } 81 | var title = myjson.title; 82 | var titleColor = myjson.titleColor; 83 | if (typeof titleColor === "undefined") { 84 | titleColor = "black"; 85 | } 86 | 87 | var titleAlignment = myjson.titleAlignment; 88 | if (typeof titleAlignment === "undefined") { 89 | titleAlignment = "alignToValue"; 90 | } 91 | 92 | var previous = myjson.previous; 93 | 94 | ctx.clearRect(0, 0, can.width, can.height); 95 | ctx.fillStyle = background; 96 | ctx.fillRect(0,0,can.width, can.height); 97 | 98 | if (shadow) { 99 | ctx.shadowColor = "rgba(0,0,0,0.15)"; 100 | ctx.shadowOffsetX = 3; 101 | ctx.shadowOffsetY = 3; 102 | ctx.shadowBlur = 2; 103 | } 104 | 105 | // draw value 106 | ctx.fillStyle = valueColor; 107 | ctx.font="bold " + valSize + "px Arial"; 108 | var xValue = canWidth/2-ctx.measureText(value).width/2; 109 | ctx.fillText(value,xValue,canHeight/2+valSize/4); 110 | 111 | // draw title 112 | if (typeof title !== "undefined") { 113 | ctx.fillStyle = titleColor; 114 | ctx.font="bold " + titleSize + "px Arial"; 115 | var xTitle = xValue; 116 | if (titleAlignment === "center") { 117 | xTitle = canWidth/2-ctx.measureText(title).width/2; 118 | } 119 | ctx.fillText(title,xTitle,2*titleSize); 120 | } 121 | 122 | // draw previous 123 | if (typeof previous !== "undefined") { 124 | var previousColor = myjson.previousColor; 125 | if (typeof previousColor === "undefined") { 126 | previousColor = "gray"; 127 | } 128 | var up = myjson.up; 129 | if (typeof up === "undefined") { 130 | up = true; 131 | } 132 | var shouldRise = myjson.shouldRise; 133 | if (typeof shouldRise === "undefined") { 134 | shouldRise = true; 135 | } 136 | drawDisplayArrow(ctx, xValue+valSize/4, canHeight-2*titleSize, up, valSize, shouldRise); 137 | 138 | ctx.fillStyle = previousColor; 139 | ctx.font="bold " + valSize/2 + "px Arial"; 140 | ctx.fillText(previous,xValue+valSize/1.5,canHeight-2*titleSize+valSize/16); 141 | } 142 | } 143 | 144 | function resizeDisplayCanvas() { 145 | var can = document.getElementById(id); 146 | if (can != null) { 147 | drawDisplay(false); 148 | } 149 | } 150 | 151 | } 152 | 153 | function drawDisplayArrow(c, dotX, dotY, up, size, shouldRise) { 154 | var d = size/1.5; 155 | c.beginPath(); 156 | if (up) { 157 | c.moveTo(dotX-d/2, dotY+Math.sqrt(3)*d/6); 158 | c.lineTo(dotX, dotY-2*Math.sqrt(3)*d/6); 159 | c.lineTo(dotX+d/2, dotY+Math.sqrt(3)*d/6); 160 | c.lineTo(dotX-d/2, dotY+Math.sqrt(3)*d/6); 161 | } else { 162 | c.moveTo(dotX-d/2, dotY-2*Math.sqrt(3)*d/6); 163 | c.lineTo(dotX+d/2, dotY-2*Math.sqrt(3)*d/6); 164 | c.lineTo(dotX, dotY+Math.sqrt(3)*d/6); 165 | c.lineTo(dotX-d/2, dotY-2*Math.sqrt(3)*d/6); 166 | } 167 | c.closePath(); 168 | 169 | var color = "red"; 170 | if ((shouldRise && up) || (!shouldRise && !up)) { 171 | color = "green"; 172 | } 173 | c.fillStyle=color; 174 | c.fill(); 175 | c.strokeStyle="gray"; 176 | c.stroke(); 177 | } -------------------------------------------------------------------------------- /src/js/indicator.js: -------------------------------------------------------------------------------- 1 | // data object to keep: 2 | // component size 3 | // component radix 4 | // component arc width 5 | // x, y coordinates of the center of arcs 6 | function data(size, radix, arcWidth, x, y) { 7 | this.size=size; 8 | this.radix=radix; 9 | this.arcWidth=arcWidth; 10 | this.x=x; 11 | this.y=y; 12 | } 13 | 14 | // compute a data object from canvas size 15 | // zoom means full window size 16 | var getData = function(id, zoom, useParentWidth) { 17 | var can = document.getElementById(id); 18 | var ctx = can.getContext('2d'); 19 | 20 | if (zoom == true) { 21 | can.width = $(window).width(); 22 | can.height = $(window).height(); 23 | } 24 | 25 | if (useParentWidth) { 26 | can.width = can.parentNode.offsetWidth; 27 | } 28 | 29 | var canWidth = can.width; 30 | var canHeight = can.height; 31 | var size = canHeight; 32 | if (8*canWidth/13 <= canHeight) { 33 | size = 8*canWidth/13; 34 | } 35 | 36 | var radix = 10*size/13; 37 | var arcWidth = (size/3) >> 0; // take integer value 38 | var x = canWidth/2; 39 | var y = 11*size/13; 40 | 41 | return new data(size, radix, arcWidth, x, y); 42 | } 43 | 44 | // draw texts: title, description, min, max, value 45 | var drawIndicatorText = function(id, title, description, unit, min, max, value, showMinMax, d, shadow) { 46 | var can = document.getElementById(id); 47 | var ctx = can.getContext('2d'); 48 | 49 | // clear canvas 50 | ctx.clearRect(0, 0, can.width, can.height); 51 | 52 | if (shadow) { 53 | ctx.shadowColor = "rgba(0,0,0,0.15)"; 54 | ctx.shadowOffsetX = 3; 55 | ctx.shadowOffsetY = 3; 56 | ctx.shadowBlur = 2; 57 | } 58 | 59 | ctx.fillStyle = "black"; 60 | ctx.font="bold " + d.size/10 + "px Arial"; 61 | ctx.fillText(title,d.x-ctx.measureText(title).width/2,d.y-d.radix+d.arcWidth/2 + d.size/10); 62 | 63 | ctx.fillStyle = "gray"; 64 | if (showMinMax == true) { 65 | ctx.font=d.size/10 + "px Arial"; 66 | if (unit != "") { 67 | min = min + unit; 68 | max = max + unit; 69 | } 70 | ctx.fillText(min,d.x-d.radix+d.arcWidth/2-ctx.measureText(min).width/2,d.y + d.size/10 ); 71 | ctx.fillText(max,d.x+d.radix-d.arcWidth/2-ctx.measureText(max).width/2,d.y + d.size/10 ); 72 | } 73 | if (description != "") { 74 | ctx.font=d.size/12 + "px Arial"; 75 | ctx.fillText(description,d.x-ctx.measureText(description).width/2,d.y + d.size/12 ); 76 | } 77 | 78 | if (unit != "") { 79 | value = value + unit; 80 | } 81 | ctx.fillStyle = "black"; 82 | ctx.font="bold " + d.size/6 + "px Arial"; 83 | ctx.fillText(value,d.x-ctx.measureText(value).width/2,d.y ); 84 | 85 | ctx.shadowBlur = 0; 86 | ctx.shadowOffsetX = 0; 87 | ctx.shadowOffsetY = 0; 88 | } 89 | 90 | // fill component with color 91 | var drawIndicatorColor = function(id, angle, color, title, d, shadow) { 92 | 93 | if ((d.y == 0) || (d.radix == 0)) { 94 | return; 95 | } 96 | 97 | var can = document.getElementById(id); 98 | var ctx = can.getContext('2d'); 99 | 100 | // clear all to empty string title 101 | ctx.beginPath() 102 | ctx.arc(d.x,d.y-1,d.radix-2,Math.PI , 0, false); // outer (filled) 103 | ctx.arc(d.x,d.y-1,d.radix+2-d.arcWidth,0, Math.PI, true); // inner (unfills it) 104 | ctx.closePath(); 105 | ctx.fillStyle = "white"; 106 | ctx.fill(); 107 | 108 | // paint with a linear gradient 109 | ctx.beginPath() 110 | //ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise); 111 | ctx.arc(d.x,d.y-1,d.radix-1.5,Math.PI , angle-Math.PI, false); // outer (filled) 112 | ctx.arc(d.x,d.y-1,d.radix-d.arcWidth-0.5,angle-Math.PI, Math.PI, true); // inner (unfills it) 113 | ctx.closePath(); 114 | var grd = ctx.createLinearGradient(d.x-d.radix, d.y, d.x, d.y); 115 | grd.addColorStop(0, "white"); 116 | grd.addColorStop(1, color); 117 | ctx.fillStyle = grd; 118 | ctx.fill(); 119 | 120 | // draw title again to be over the fill color 121 | if (shadow) { 122 | ctx.shadowColor = "rgba(0,0,0,0.15)"; 123 | ctx.shadowOffsetX = 3; 124 | ctx.shadowOffsetY = 3; 125 | ctx.shadowBlur = 2; 126 | } 127 | ctx.fillStyle = "black"; 128 | ctx.font="bold " + d.size/10 + "px Arial"; 129 | ctx.fillText(title,d.x-ctx.measureText(title).width/2,d.y-d.radix+d.arcWidth/2 + d.size/10); 130 | ctx.shadowBlur = 0; 131 | ctx.shadowOffsetX = 0; 132 | ctx.shadowOffsetY = 0; 133 | 134 | }; 135 | 136 | // draw the component frame 137 | var drawIndicatorArc = function(id, d) { 138 | 139 | if (d.radix <= d.arcWidth + 0.5) { 140 | return; 141 | } 142 | 143 | var can = document.getElementById(id); 144 | var ctx = can.getContext('2d'); 145 | 146 | ctx.strokeStyle = "gray"; 147 | ctx.beginPath(); 148 | ctx.arc(d.x,d.y,d.radix,Math.PI , 0, false); // outer (filled) 149 | ctx.arc(d.x,d.y,d.radix-d.arcWidth-0.5,0, Math.PI, true); // inner (unfills it) 150 | ctx.lineTo(d.x-d.radix,d.y); 151 | ctx.closePath(); 152 | ctx.stroke(); 153 | } 154 | 155 | function indicatorP(id, color, title, description, unit, min, max, value, showMinMax, shadow, zoom, useParentWidth) { 156 | 157 | var can = document.getElementById(id); 158 | if (can == null) { 159 | return; 160 | } 161 | 162 | if (useParentWidth) { 163 | window.addEventListener('resize', resizeIndicatorCanvas, false); 164 | } 165 | drawIndicator(true); 166 | 167 | function drawIndicator(animate) { 168 | var d = getData(id, zoom, useParentWidth); 169 | drawIndicatorText(id, title, description, unit, min, max, value, showMinMax, d, shadow); 170 | 171 | if (value > max) { 172 | value = max; 173 | } 174 | var range = Math.abs(max - min); 175 | var delta = Math.abs(min - value); 176 | var f = (range - delta) /range; 177 | var from = 1; 178 | var to = 180 * (1-f) ; 179 | 180 | if (value > min) { 181 | // animate using jQuery 182 | if (animate) { 183 | $({ n: from }).animate({ n: to}, { 184 | duration: 1000, 185 | step: function(now, fx) { 186 | drawIndicatorColor(id, now*Math.PI/180, color, title, d, shadow); 187 | } 188 | }); 189 | } else { 190 | drawIndicatorColor(id, to*Math.PI/180, color, title, d, shadow); 191 | } 192 | } 193 | // draw component frame at the end 194 | drawIndicatorArc(id, d); 195 | } 196 | 197 | function resizeIndicatorCanvas() { 198 | var can = document.getElementById(id); 199 | if (can != null) { 200 | drawIndicator(false); 201 | } 202 | } 203 | } 204 | 205 | /* With json object 206 | * 207 | * { "title": "Balance", 208 | * "description": "monthly", 209 | * "unit" : "$", 210 | * "value" : 200, 211 | * "min" : 0, 212 | * "max" : 1000, 213 | * "showMinMax" : true, 214 | * "color" : "blue" 215 | * } 216 | */ 217 | function indicator(id, myjson, zoom, useParentWidth) { 218 | 219 | var title = myjson.title; 220 | if (typeof title === "undefined") { 221 | title = ""; 222 | } 223 | 224 | var description = myjson.description; 225 | if (typeof description === "undefined") { 226 | description = ""; 227 | } 228 | 229 | var unit = myjson.unit; 230 | if (typeof unit === "undefined") { 231 | unit = ""; 232 | } 233 | 234 | var value = myjson.value; 235 | if (typeof value === "undefined") { 236 | value = 0; 237 | } 238 | 239 | var min = myjson.min; 240 | if (typeof min === "undefined") { 241 | min = 0; 242 | } 243 | 244 | var max = myjson.max; 245 | if (typeof max === "undefined") { 246 | max = 100; 247 | } 248 | 249 | var showMinMax = myjson.showMinMax; 250 | if (typeof showMinMax === "undefined") { 251 | showMinMax = true; 252 | } 253 | 254 | var color = myjson.color; 255 | if (typeof color === "undefined") { 256 | color = "blue"; 257 | } 258 | 259 | var shadow = myjson.shadow; 260 | if (typeof shadow === "undefined") { 261 | shadow = false; 262 | } 263 | 264 | indicatorP(id, color, title, description, unit, min, max, value, showMinMax, shadow, zoom, useParentWidth); 265 | 266 | } 267 | 268 | function getIndicatorHeight() { 269 | return $(window).height(); 270 | } 271 | 272 | 273 | //indicator("canvas", "orange", "Visitors", "per minute", "", 0, 100, 120, true, false); 274 | //indicator("canvas", "blue", "Memory", "", "%", 0, 100, 80, false, false); 275 | //indicator("canvas", "red", "System Indicator", "average", "T", -1, 1, 0.83, true, false); 276 | 277 | -------------------------------------------------------------------------------- /src/js/linechart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Json must contain as mandatory only "data" attribute 3 | * 4 | * type -> line, area 5 | * style -> normal, soliddot, hollowdot, anchordot, bowdot, stardot 6 | * labelOrientation -> horizontal, vertical, diagonal, halfdiagonal 7 | * showLabels -> true means X labels are shown on X axis; we can use false if we want to show them in message tooltip with #x 8 | * message -> can have markup #val for value, , #x for x label 9 | * -> can contain
to split text on more lines 10 | * title.alignment -> center, left, right 11 | * y2Count -> number of last series represented on dual y2 axis 12 | * styleGridX, styleGridY -> line, dot, dash 13 | * onClick -> is a javascript function like 'function doClick(value){ ...}' * 14 | * 15 | * { "type": "line", "area" 16 | * "style": "normal", 17 | * "background" : "white", 18 | * "data": [[ 16, 66, 24, 30, 80, 52 ], [ 48, 50, 29, 60, 70, 58 ], [ 30, 40, 28, 52, 74, 50 ]], 19 | * "labels": ["JAN","FEB","MAR","APR","MAY", "JUN"], 20 | * "labelOrientation": "horizontal", 21 | * "color": ["#004CB3","#A04CB3", "#7aa37a"], 22 | * "legend": ["2011 First year of work" , "2012 Second year of work", "2013 Third year of work"], 23 | * "alpha" : 0.8, 24 | * "colorXaxis": "blue", 25 | * "colorYaxis": "blue", 26 | * "showGridX": true, 27 | * "showGridY": true, 28 | * "showLabels": true, 29 | * "colorGridX": "rgb(248, 248, 216)", 30 | * "colorGridY": "rgb(248, 248, 216)", 31 | * "styleGridX": "line", 32 | * "styleGridY": "line", 33 | * "message" : "Value \: #val", 34 | * "showTicks" : true, 35 | * "tickCount" : 5, 36 | * "startingFromZero" : false, 37 | * "title" : { 38 | * "text": "Analiza financiara", 39 | * "font": { 40 | * "weight": "bold", 41 | * "size": "16", 42 | * "family": "sans-serif" 43 | * }, 44 | * "color": "#000000", 45 | * "alignment":"left" 46 | * }, 47 | * "xData" : { 48 | * "font": { 49 | * "weight": "bold", 50 | * "size": "16", 51 | * "family": "sans-serif" 52 | * }, 53 | * "color": "blue" 54 | * }, 55 | * "yData" : { 56 | * "font": { 57 | * "weight": "bold", 58 | * "size": "16", 59 | * "family": "sans-serif" 60 | * }, 61 | * "color": "blue" 62 | * }, 63 | * "xLegend" : { 64 | * "text": "Month", 65 | * "font": { 66 | * "weight": "bold", 67 | * "size": "16", 68 | * "family": "sans-serif" 69 | * }, 70 | * "color": "blue" 71 | * }, 72 | * "yLegend" : { 73 | * "text": "Sales", 74 | * "font": { 75 | * "weight": "bold", 76 | * "size": "16", 77 | * "family": "sans-serif" 78 | * }, 79 | * "color": "blue" 80 | * }, 81 | * "tooltipPattern" : { 82 | * "decimals": 2, 83 | * "decimalSeparator" : ".", 84 | * "thousandSeparator" : "," 85 | * }, 86 | * "dualYaxis": true, 87 | * "colorY2axis": "blue", 88 | * "y2Legend" : { 89 | * "text": "Sales", 90 | * "font": { 91 | * "weight": "bold", 92 | * "size": "16", 93 | * "family": "sans-serif" 94 | * }, 95 | * "color": "blue" 96 | * }, 97 | * "y2Count" : 1, 98 | * "onClick" : "function doClick(value){console.log("Call from function: " + value);}" 99 | * } 100 | * 101 | */ 102 | 103 | var lineChart = function(myjson, idCan, idTipCan, canWidth, canHeight) { 104 | 105 | var obj; 106 | var data; 107 | var labels; 108 | var labelOrientation; 109 | var globalAlpha; 110 | var showGridX; 111 | var showGridY; 112 | var showLabels; 113 | var background; 114 | var message; 115 | var tickCount; 116 | var showTicks; 117 | var startingFromZero; 118 | var chartType; 119 | var chartStyle; 120 | var seriesColor; 121 | var xData, yData; 122 | var series; 123 | var yStep; 124 | var maxK = new Array(); 125 | var minK = new Array(); 126 | var dotsK = new Array(); 127 | var max; 128 | var min; 129 | //space between 2 ticks 130 | var tickStep; 131 | var minValY; 132 | var maxValY; 133 | // for dual Y axis 134 | var y2Step; 135 | var maxK2 = new Array(); 136 | var minK2 = new Array(); 137 | var max2; 138 | var min2; 139 | var minValY2; 140 | var maxValY2; 141 | var hStep2 = 0; 142 | var y2LegendSpace = 0; 143 | var y2Count = 1; 144 | // bottom vertical space (to fit X labels and X legend) 145 | var step = 0; 146 | var gap = 40; 147 | // left horizontal space (to fit bigger Y labels and Y legend) 148 | var hStep = 60; 149 | var titleSpace = 0; 150 | var legendSpace = 0; 151 | var xLegendSpace = 0; 152 | var yLegendSpace = 0; 153 | var xaxisY = 0; 154 | var realWidth; 155 | var realHeight; 156 | var canvas; 157 | var c; 158 | var tipCanvas; 159 | var tempCanvas; 160 | var tempCtx; 161 | var H = 6; 162 | var dotRadius = 4; 163 | // space between X axis and first tick 164 | var tickInit; 165 | var resizeWidth = false; 166 | var resizeHeight = false; 167 | //by default chart title, legends and axis values strings have a defined font size 168 | //if we want to have font size scaled accordingly with chart width then this property must be true 169 | //(such example may be when we want to show the chart on a big monitor) 170 | var adjustableTextFontSize = false; 171 | var highlighterSerie = -1; 172 | var highlighterSerieIndex = -1; 173 | 174 | function drawLine(myjson, idCan, idTipCan, canWidth, canHeight) { 175 | 176 | canvas = document.getElementById(idCan); 177 | if (canvas == null) { 178 | return; 179 | } 180 | tipCanvas = document.getElementById(idTipCan); 181 | c = canvas.getContext('2d'); 182 | 183 | tempCanvas = document.createElement('canvas'); 184 | tempCtx = tempCanvas.getContext('2d'); 185 | 186 | obj = myjson; 187 | chartType = obj.type; 188 | if (typeof chartType === "undefined") { 189 | chartType = "line"; 190 | } 191 | 192 | background = obj.background; 193 | if (typeof background === "undefined") { 194 | background = "white"; 195 | } 196 | 197 | 198 | chartStyle = obj.style; 199 | // test for passing a wrong style 200 | if ((typeof chartStyle === "undefined") || (find(['normal', 'soliddot', 'hollowdot', 'anchordot', 'bowdot', 'stardot'],chartStyle) === false)) { 201 | chartStyle = "normal"; 202 | } 203 | 204 | data = obj.data[0]; 205 | series = obj.data.length; 206 | 207 | labels = obj.labels; 208 | if (typeof labels === "undefined") { 209 | labels = []; 210 | for (var i=0; i= series) { 276 | y2Count = series-1; 277 | } 278 | 279 | // compute min ,max values 280 | var countNo = series; 281 | if ((countNo > 1) && obj.dualYaxis) { 282 | countNo = series-y2Count; 283 | } 284 | for (var k=0; k 0)) { 291 | min = 0; 292 | } 293 | 294 | var objStep = calculateYStep(min, max, tickCount); 295 | yStep = objStep.yStep; 296 | minValY = objStep.minValY; 297 | maxValY = objStep.maxValY; 298 | 299 | // compute hStep (for vertical labels and yLegend to fit) 300 | hStep = computeHStep(maxValY, yStep, true); 301 | 302 | // for y dual axis 303 | if (obj.dualYaxis && (countNo > 0)) { 304 | 305 | if (countNo == series) { 306 | countNo = series-1; 307 | } 308 | 309 | for (var k=countNo; k 0)) { 316 | min2 = 0; 317 | } 318 | 319 | var objStep2 = calculateYStep(min2, max2, tickCount); 320 | y2Step = objStep2.yStep; 321 | minValY2 = objStep2.minValY; 322 | maxValY2 = objStep2.maxValY; 323 | 324 | hStep2 = computeHStep(maxValY2, y2Step, false); 325 | } 326 | 327 | updateSize(canWidth, canHeight); 328 | 329 | canvas.addEventListener('mousemove', 330 | function(evt) { 331 | var hme = Object.create(handleMouseEvent); 332 | hme.mousePos = getMousePos(canvas, evt); 333 | hme.tooltip = getTooltip(hme.mousePos); 334 | hme.execute(canvas, tipCanvas, evt); 335 | }, 336 | false); 337 | 338 | canvas.addEventListener ("mouseout", 339 | function(evt) { 340 | tipCanvas.style.left = "-2000px"; 341 | }, 342 | false); 343 | 344 | canvas.addEventListener("click", onClick, false); 345 | 346 | if (resizeWidth || resizeHeight) { 347 | window.addEventListener('resize', resizeCanvas, false); 348 | } 349 | 350 | drawChart(); 351 | } 352 | 353 | function updateSize(canWidth, canHeight) { 354 | if (canWidth !== undefined) { 355 | if (isPercent(canWidth)) { 356 | var percent = canWidth.substring(0, canWidth.length-1); 357 | //canvas.width = window.innerWidth*percent/100; 358 | var cl = canvas.parentNode; 359 | canvas.width = cl.offsetWidth*percent/100; 360 | resizeWidth = true; 361 | } else { 362 | canvas.width = canWidth; 363 | } 364 | } 365 | if (canHeight !== undefined) { 366 | if (isPercent(canHeight)) { 367 | var percent = canHeight.substring(0, canHeight.length-1); 368 | canvas.height = window.innerHeight*percent/100; 369 | //var cl = canvas.parentNode; 370 | //canvas.height = cl.offsetHeight*percent/100; 371 | resizeHeight = true; 372 | } else { 373 | canvas.height = canHeight; 374 | } 375 | } 376 | 377 | tempCanvas.width = canvas.width; 378 | tempCanvas.height = canvas.height; 379 | 380 | realWidth = canvas.width; 381 | realHeight = canvas.height; 382 | 383 | // adjust the x gap between elements (should be smaller for smaller widths) 384 | gap = realWidth/10 - 10; 385 | 386 | 387 | tickInit = realHeight/12; 388 | 389 | // space between 2 ticks 390 | tickStep = (realHeight-step-tickInit)/tickCount; 391 | 392 | if (adjustableTextFontSize) { 393 | var objStep = calculateYStep(min, max, tickCount); 394 | yStep = objStep.yStep; 395 | minValY = objStep.minValY; 396 | maxValY = objStep.maxValY; 397 | 398 | // compute hStep (for vertical labels and yLegend to fit) 399 | hStep = computeHStep(maxValY, yStep, true); 400 | 401 | if (obj.dualYaxis) { 402 | var objStep2 = calculateYStep(min2, max2, tickCount); 403 | y2Step = objStep2.yStep; 404 | minValY2 = objStep2.minValY; 405 | maxValY2 = objStep2.maxValY; 406 | 407 | hStep2 = computeHStep(maxValY2, y2Step, false); 408 | } 409 | } 410 | } 411 | 412 | 413 | function animDraw() { 414 | if (drawIt(H)) { 415 | tempCtx.drawImage(canvas, 0, 0); 416 | return false; 417 | } 418 | H += 1+(realHeight-step-titleSpace-legendSpace)/30; 419 | window.requestAnimFrame(animDraw); 420 | } 421 | 422 | 423 | // function called repetitive by animation 424 | function drawIt(H) { 425 | 426 | // to redraw the entire canvas (it extends under drawn x axis) 427 | drawInit(); 428 | 429 | // if we clear only the rectangle containing the chart (without title , legends, labels) 430 | // clear the drawn area (if drawInit is called in drawChart) 431 | // c.fillStyle = background; 432 | // c.fillRect(hStep-10,titleSpace+legendSpace,width, realHeight-step-titleSpace-legendSpace-2); 433 | 434 | var stop = drawData(true, false, ""); 435 | 436 | drawGrid(); 437 | drawAxis(); 438 | 439 | return stop; 440 | } 441 | 442 | // withFill = false means to construct just the path needed for tooltip purposes 443 | function drawData(withFill, withClick, mousePos) { 444 | var font = c.font; 445 | 446 | //draw data 447 | c.lineWidth = 1.0; 448 | var stop = true; 449 | 450 | var cursorStyle = 'default'; 451 | for(var k=0; k 0) && (k >= series-y2Count)) { 459 | Yheight = realHeight-step-(dp-minValY2)*tickStep/y2Step; 460 | } 461 | var dotY = realHeight-step-H; 462 | var dotX2 = dotX; 463 | var dotY2 = dotY; 464 | var Yheight2 = Yheight; 465 | if (i < data.length-1) { 466 | dotX2 = hStep + (i+1)*(realWidth-hStep-hStep2)/data.length + width/2; 467 | dotY2 = realHeight-step-H; 468 | Yheight2 = realHeight-step-(obj.data[k][i+1]-minValY)*tickStep/yStep; 469 | if (obj.dualYaxis && (y2Count > 0) && (k >= series-y2Count)) { 470 | Yheight2 = realHeight-step-(obj.data[k][i+1]-minValY2)*tickStep/y2Step; 471 | } 472 | } 473 | 474 | if (dotY <= Yheight) { 475 | dotY = Yheight; 476 | } else { 477 | stop = false; 478 | } 479 | if (dotY+2 > xaxisY) { 480 | dotY = xaxisY-1; 481 | } 482 | if (i < data.length-1) { 483 | if (dotY2 <= Yheight2) { 484 | dotY2 = Yheight2; 485 | } else { 486 | stop = false; 487 | } 488 | if (dotY2+2 > xaxisY) { 489 | dotY2 = xaxisY-1; 490 | } 491 | } 492 | 493 | dotsK[k].push({x:dotX, y:dotY}); 494 | 495 | var savedStroke = c.strokeStyle; 496 | if (chartStyle == "normal") { 497 | dotRadius = 3; 498 | } 499 | drawLineElements(c, chartStyle, chartType, dotRadius, seriesColor, dotsK, data.length, xaxisY, globalAlpha, k, i, dotX, dotY, dotX2, dotY2, withFill); 500 | c.strokeStyle = savedStroke; 501 | 502 | var found; 503 | if (!withFill) { 504 | 505 | // highlight selection 506 | if ((k == highlighterSerie) && (i == highlighterSerieIndex)) { 507 | highlight(k, 0.5); 508 | } else { 509 | unhighlight(k, i); 510 | } 511 | 512 | if (c.isPointInPath(mousePos.x, mousePos.y)) { 513 | highlighterSerie = k; 514 | highlighterSerieIndex = i; 515 | var tValue = obj.data[k][i]; 516 | if (obj.tooltipPattern !== undefined) { 517 | tValue = formatNumber(tValue, obj.tooltipPattern.decimals, obj.tooltipPattern.decimalSeparator, obj.tooltipPattern.thousandSeparator); 518 | } 519 | var returnValue = labels[i]; // tValue 520 | if (withClick) { 521 | if (found === undefined) { 522 | found = returnValue; 523 | } 524 | } else { 525 | var mes = String(message).replace('#val', tValue); 526 | mes = mes.replace('#x', returnValue); 527 | if (obj.onClick !== undefined) { 528 | cursorStyle = 'pointer'; 529 | canvas.style.cursor = cursorStyle; 530 | } 531 | if (found === undefined) { 532 | found = mes; 533 | } 534 | } 535 | } else { 536 | if (cursorStyle != 'pointer') { 537 | canvas.style.cursor = 'default'; 538 | } 539 | } 540 | } 541 | } 542 | } 543 | 544 | if (found !== undefined) { 545 | return found; 546 | } 547 | 548 | if (withFill) { 549 | return stop; 550 | } else { 551 | // empty tooltip message 552 | return ""; 553 | } 554 | } 555 | 556 | function highlight(k, luminance) { 557 | c.fillStyle = background; 558 | c.fill(); 559 | 560 | var lColor = highlightColor(seriesColor[k],luminance); 561 | c.fillStyle = lColor; 562 | c.globalAlpha = globalAlpha; 563 | c.fill(); 564 | c.globalAlpha = 1; 565 | 566 | highlighterSerie = -1; 567 | highlighterSerieIndex = -1; 568 | } 569 | 570 | function unhighlight(k, i) { 571 | if ((i == 0) && (k == 0)) { 572 | c.drawImage(tempCanvas, 0, 0); 573 | } 574 | } 575 | 576 | function drawInit() { 577 | 578 | var font = c.font; 579 | 580 | //draw background (clear canvas) 581 | c.fillStyle = background; 582 | c.fillRect(0,0,realWidth,realHeight); 583 | 584 | // adjust step with X label space (x label can have different orientations) and X legend space 585 | var xLabelWidth = computeVStep(); 586 | 587 | //draw title 588 | if (typeof obj.title !== "undefined") { 589 | var titleColor = obj.title.color; 590 | if (titleColor === undefined) { 591 | titleColor = '#000000'; 592 | } 593 | c.fillStyle = titleColor; 594 | var b = " "; 595 | var f = obj.title.font; 596 | if (f === undefined) { 597 | f.weight = "bold"; 598 | f.size = 12; 599 | f.family = "sans-serif"; 600 | } 601 | if (adjustableTextFontSize) { 602 | f.size=getAdjustableTitleFontSize(); 603 | } 604 | c.font = f.weight + b + f.size + "px" + b + f.family; 605 | var titlePadding = 20; 606 | if (adjustableTextFontSize) { 607 | titlePadding = getAdjustableTitleFontSize()/2; 608 | } 609 | titleSpace = +titlePadding + +f.size; 610 | 611 | var alignment = obj.title.alignment; 612 | if (alignment === undefined) { 613 | alignment = "center"; 614 | } 615 | var xTitle; 616 | if (alignment == "left") { 617 | xTitle = hStep; 618 | } else if (alignment == "right") { 619 | xTitle = canvas.width - c.measureText(obj.title.text).width - 10; 620 | } else { 621 | // center 622 | xTitle = canvas.width/2- c.measureText(obj.title.text).width/2; 623 | } 624 | 625 | var titlePadding = 20; 626 | if (adjustableTextFontSize) { 627 | titlePadding = getAdjustableTitleFontSize()/2; 628 | } 629 | c.fillText(obj.title.text, xTitle , titlePadding+titleSpace/2 ); 630 | c.font = font; 631 | } else { 632 | titleSpace = 10; 633 | } 634 | 635 | 636 | //draw X legend 637 | if (typeof obj.xLegend !== "undefined") { 638 | 639 | var xLegendColor = obj.xLegend.color; 640 | if (xLegendColor === undefined) { 641 | xLegendColor = '#000000'; 642 | } 643 | c.fillStyle = xLegendColor; 644 | var b = " "; 645 | var f = obj.xLegend.font; 646 | if (f === undefined) { 647 | f.weight = "bold"; 648 | f.size = 12; 649 | f.family = "sans-serif"; 650 | } 651 | if (adjustableTextFontSize) { 652 | f.size = getAdjustableLabelFontSize(); 653 | } 654 | c.font = f.weight + b + f.size + "px" + b + f.family; 655 | var legendPadding = 20; 656 | if (adjustableTextFontSize) { 657 | legendPadding = getAdjustableLabelFontSize()/2; 658 | } 659 | xLegendSpace = +legendPadding + +f.size; 660 | 661 | c.fillText(obj.xLegend.text, realWidth/2- c.measureText(obj.xLegend.text).width/2 , realHeight - f.size ); 662 | c.font = font; 663 | } else { 664 | xLegendSpace = 0; 665 | } 666 | 667 | //draw Y legend 668 | if (typeof obj.yLegend !== "undefined") { 669 | var yLegendColor = obj.yLegend.color; 670 | if (yLegendColor === undefined) { 671 | yLegendColor = '#000000'; 672 | } 673 | var b = " "; 674 | var f = obj.yLegend.font; 675 | if (f === undefined) { 676 | f.weight = "bold"; 677 | f.size = 12; 678 | f.family = "sans-serif"; 679 | } 680 | if (adjustableTextFontSize) { 681 | f.size = getAdjustableLabelFontSize(); 682 | } 683 | c.font = f.weight + b + f.size + "px" + b + f.family; 684 | c.fillStyle = yLegendColor; 685 | c.save(); 686 | c.translate(10 , realHeight/2); 687 | c.rotate(-Math.PI/2); 688 | c.textAlign = "center"; 689 | c.fillText(obj.yLegend.text,0, f.size); 690 | c.restore(); 691 | c.font = font; 692 | } else { 693 | yLegendSpace = 0; 694 | } 695 | 696 | // draw Y2 legend 697 | if (obj.dualYaxis && (typeof obj.y2Legend !== "undefined")) { 698 | var y2LegendColor = obj.y2Legend.color; 699 | if (y2LegendColor === undefined) { 700 | y2LegendColor = '#000000'; 701 | } 702 | var b = " "; 703 | var f = obj.y2Legend.font; 704 | if (f === undefined) { 705 | f.weight = "bold"; 706 | f.size = 12; 707 | f.family = "sans-serif"; 708 | } 709 | if (adjustableTextFontSize) { 710 | f.size = getAdjustableLabelFontSize(); 711 | } 712 | c.font = f.weight + b + f.size + "px" + b + f.family; 713 | c.fillStyle = y2LegendColor; 714 | c.save(); 715 | c.translate(realWidth - f.size - 10 , realHeight/2); 716 | c.rotate(-Math.PI/2); 717 | c.textAlign = "center"; 718 | c.fillText(obj.y2Legend.text,0, f.size); 719 | c.restore(); 720 | c.font = font; 721 | } else { 722 | y2LegendSpace = 0; 723 | } 724 | 725 | 726 | // draw legend 727 | if (typeof obj.legend !== "undefined") { 728 | var x = hStep; 729 | c.font = "bold 10px sans-serif"; 730 | if (adjustableTextFontSize) { 731 | c.font = "bold " + getAdjustableLabelFontSize() + "px sans-serif"; 732 | } 733 | var legendPadding = 20; 734 | if (adjustableTextFontSize) { 735 | legendPadding = getAdjustableLabelFontSize(); 736 | } 737 | legendSpace = legendPadding; 738 | var legendY = titleSpace+legendPadding; 739 | c.globalAlpha = globalAlpha; 740 | for (var k=0; k realWidth) { 745 | // draw legend on next line if does not fit on current one 746 | x = hStep; 747 | var lineSpace = 14; 748 | if (adjustableTextFontSize) { 749 | lineSpace = getAdjustableLabelFontSize(); 750 | } 751 | legendY = legendY + lineSpace; 752 | var legendPadding = 20; 753 | if (adjustableTextFontSize) { 754 | legendPadding = getAdjustableLabelFontSize(); 755 | } 756 | legendSpace += legendPadding; 757 | } 758 | 759 | c.fillText("---- " + obj.legend[k], x, legendY); 760 | 761 | x = x + legendWidth; 762 | } 763 | c.globalAlpha = 1; 764 | c.font = font; 765 | } 766 | 767 | 768 | 769 | c.font = font; 770 | 771 | 772 | // adjust tickStep depending if title or legend are present or not 773 | tickStep = (realHeight-step-titleSpace-legendSpace-tickInit)/tickCount; 774 | 775 | // compute Y value for xAxis 776 | xaxisY = tickCount*tickStep+tickInit+titleSpace+legendSpace; 777 | 778 | drawLabels(xLabelWidth); 779 | } 780 | 781 | 782 | function drawLabels(xLabelWidth) { 783 | 784 | var font = c.font; 785 | 786 | //draw Y labels and small lines 787 | if (showTicks) { 788 | c.fillStyle = "black"; 789 | if (obj.yData !== undefined) { 790 | c.fillStyle = obj.yData.color; 791 | var b = " "; 792 | var yfont = obj.yData.font; 793 | if (adjustableTextFontSize) { 794 | yfont.size=getAdjustableLabelFontSize(); 795 | } 796 | c.font = yfont.weight + b + yfont.size + "px" + b + yfont.family; 797 | } 798 | for(var i=0; i maxLabelWidth) { 1052 | maxLabelWidth = labelWidth; 1053 | } 1054 | } 1055 | result = maxLabelWidth + 20; 1056 | } else { 1057 | result = 20; 1058 | } 1059 | c.font = font; 1060 | 1061 | if (obj.dualYaxis && !takeCare) { 1062 | if (typeof obj.y2Legend !== "undefined") { 1063 | var b = " "; 1064 | var f = obj.y2Legend.font; 1065 | if (f === undefined) { 1066 | f.weight = "bold"; 1067 | f.size = 12; 1068 | f.family = "sans-serif"; 1069 | } 1070 | if (adjustableTextFontSize) { 1071 | f.size = getAdjustableLabelFontSize(); 1072 | } 1073 | c.font = f.weight + b + f.size + "px" + b + f.family; 1074 | y2LegendSpace = f.size; 1075 | c.font = font; 1076 | result += y2LegendSpace; 1077 | } 1078 | } 1079 | 1080 | // take care for halfdiagonal, diagonal long labels 1081 | // if they are too long hStep must be increased accordingly 1082 | // for dual Y axis we do not need this 1083 | if (takeCare) { 1084 | if (typeof obj.yLegend !== "undefined") { 1085 | var b = " "; 1086 | var f = obj.yLegend.font; 1087 | if (f === undefined) { 1088 | f.weight = "bold"; 1089 | f.size = 12; 1090 | f.family = "sans-serif"; 1091 | } 1092 | if (adjustableTextFontSize) { 1093 | f.size = getAdjustableLabelFontSize(); 1094 | } 1095 | c.font = f.weight + b + f.size + "px" + b + f.family; 1096 | var legendPadding = 20; 1097 | if (adjustableTextFontSize) { 1098 | legendPadding = getAdjustableLabelFontSize(); 1099 | } 1100 | yLegendSpace = +legendPadding + +f.size; 1101 | c.font = font; 1102 | result += yLegendSpace; 1103 | } 1104 | 1105 | var cf = c.font; 1106 | if (obj.xData !== undefined) { 1107 | var b = " "; 1108 | var xfont = obj.xData.font; 1109 | if (adjustableTextFontSize) { 1110 | xfont.size=getAdjustableLabelFontSize(); 1111 | } 1112 | c.font = xfont.weight + b + xfont.size + "px" + b + xfont.family; 1113 | } 1114 | var minPos = new Array(); 1115 | for(var i=0; i 0) { 1131 | if (len < 10) { 1132 | result += (10 - len); 1133 | } 1134 | } 1135 | } 1136 | 1137 | return result; 1138 | } 1139 | 1140 | // computes vertical step needed 1141 | // returns maximum width for x labels 1142 | function computeVStep() { 1143 | var xLabelWidth = 0; 1144 | if (typeof obj.xData !== "undefined") { 1145 | var xfont = obj.xData.font; 1146 | if (adjustableTextFontSize) { 1147 | xfont.size=getAdjustableLabelFontSize(); 1148 | } 1149 | var b = " "; 1150 | c.font = xfont.weight + b + xfont.size + "px" + b + xfont.family; 1151 | } 1152 | if (showLabels) { 1153 | for(var i=0; i xLabelWidth) { 1156 | xLabelWidth = labelWidth; 1157 | } 1158 | } 1159 | } 1160 | var _xLegendSpace = 0; 1161 | if (typeof obj.xLegend !== "undefined") { 1162 | var f = obj.xLegend.font; 1163 | if (f === undefined) { 1164 | f.weight = "bold"; 1165 | f.size = 12; 1166 | f.family = "sans-serif"; 1167 | } 1168 | if (adjustableTextFontSize) { 1169 | f.size = getAdjustableLabelFontSize(); 1170 | } 1171 | var legendPadding = 20; 1172 | if (adjustableTextFontSize) { 1173 | legendPadding = getAdjustableLabelFontSize(); 1174 | } 1175 | _xLegendSpace = +legendPadding + +f.size; 1176 | } 1177 | if ((step < xLabelWidth+_xLegendSpace) || adjustableTextFontSize) { 1178 | step = xLabelWidth+_xLegendSpace; 1179 | } 1180 | return xLabelWidth; 1181 | } 1182 | 1183 | // depends on label orientation 1184 | function computeXLabelSpace(label) { 1185 | var _labelWidth = c.measureText(label).width + 10; // vertical 1186 | 1187 | if (labelOrientation === "horizontal") { 1188 | if (typeof obj.xData !== "undefined") { 1189 | _labelWidth = obj.xData.font.size + 20; 1190 | if (adjustableTextFontSize) { 1191 | _labelWidth=getAdjustableLabelFontSize() + 20; 1192 | } 1193 | } else { 1194 | _labelWidth = 12 + 20; 1195 | } 1196 | } else if (labelOrientation === "halfdiagonal") { 1197 | _labelWidth = c.measureText(label).width * Math.sin(Math.PI/8) + 20; 1198 | } else if (labelOrientation === "diagonal") { 1199 | _labelWidth = c.measureText(label).width * Math.sin(Math.PI/4) + 20; 1200 | } 1201 | 1202 | return _labelWidth; 1203 | } 1204 | 1205 | function getTitleSpace() { 1206 | var space = 10; 1207 | if (typeof obj.title !== "undefined") { 1208 | var f = obj.title.font; 1209 | if (f === undefined) { 1210 | f.size = 12; 1211 | } 1212 | if (adjustableTextFontSize) { 1213 | f.size=getAdjustableTitleFontSize(); 1214 | } 1215 | var titlePadding = 20; 1216 | if (adjustableTextFontSize) { 1217 | titlePadding = getAdjustableTitleFontSize(); 1218 | } 1219 | space = +titlePadding + +f.size; 1220 | } 1221 | return space; 1222 | } 1223 | 1224 | function getXLegendSpace() { 1225 | var _xLegendSpace = 0; 1226 | if (typeof obj.xLegend !== "undefined") { 1227 | var f = obj.xLegend.font; 1228 | if (f === undefined) { 1229 | f.size = 12; 1230 | } 1231 | var legendPadding = 20; 1232 | if (adjustableTextFontSize) { 1233 | f.size = getAdjustableLabelFontSize(); 1234 | legendPadding = getAdjustableLabelFontSize(); 1235 | } 1236 | _xLegendSpace = +legendPadding + +f.size; 1237 | } 1238 | return _xLegendSpace; 1239 | } 1240 | 1241 | function getLegendSpace() { 1242 | var font = c.font; 1243 | var _legendSpace = 20; 1244 | if (typeof obj.legend !== "undefined") { 1245 | var x = hStep; 1246 | c.font = "bold 10px sans-serif"; 1247 | if (adjustableTextFontSize) { 1248 | c.font ="bold " + getAdjustableLabelFontSize() + "px sans-serif"; 1249 | } 1250 | var legendPadding = 20; 1251 | if (adjustableTextFontSize) { 1252 | legendPadding = getAdjustableLabelFontSize()/2; 1253 | } 1254 | var legendY = getTitleSpace()+legendPadding; 1255 | for (var k=0; k realWidth) { 1259 | // draw legend on next line if does not fit on current one 1260 | x = hStep; 1261 | var lineSpace = 14; 1262 | if (adjustableTextFontSize) { 1263 | lineSpace = getAdjustableLabelFontSize(); 1264 | } 1265 | legendY = legendY + lineSpace; 1266 | var pad = 20; 1267 | if (adjustableTextFontSize) { 1268 | pad = getAdjustableLabelFontSize(); 1269 | } 1270 | _legendSpace += pad; 1271 | } 1272 | 1273 | x = x + legendWidth; 1274 | } 1275 | } 1276 | c.font = font; 1277 | return _legendSpace; 1278 | } 1279 | 1280 | function resizeCanvas() { 1281 | var can = document.getElementById(idCan); 1282 | if (can != null) { 1283 | var w = canWidth; 1284 | if (resizeWidth) { 1285 | if (!isPercent(w)) { 1286 | w = "100%"; 1287 | } 1288 | } 1289 | var h = canHeight; 1290 | if (resizeHeight) { 1291 | if (!isPercent(h)) { 1292 | h = "100%"; 1293 | } 1294 | } 1295 | updateSize(w, h); 1296 | drawChart(); 1297 | } 1298 | } 1299 | 1300 | function getAdjustableTitleFontSize() { 1301 | return canvas.width/25; 1302 | } 1303 | 1304 | function getAdjustableLabelFontSize() { 1305 | return canvas.width/45; 1306 | } 1307 | 1308 | drawLine(myjson, idCan, idTipCan, canWidth, canHeight); 1309 | 1310 | }; 1311 | 1312 | /**** line chart utilities ****/ 1313 | //return Y coordinates of the two ends of a line 1314 | //when we draw small dots with space between lines 1315 | function getLineY(dotY, dotY2, alpha, space) { 1316 | var y, y2; 1317 | if (dotY == dotY2) { 1318 | y = dotY; 1319 | y2 = dotY2; 1320 | } else if (dotY > dotY2) { 1321 | y = dotY-space*Math.sin(alpha); 1322 | y2 = dotY2 + space*Math.sin(alpha); 1323 | } else if (dotY < dotY2){ 1324 | y = dotY+space*Math.sin(alpha); 1325 | y2 = dotY2 - space*Math.sin(alpha); 1326 | } 1327 | return {y:y, y2:y2}; 1328 | } 1329 | 1330 | function drawDot(c, chartStyle, dotX, dotY, dotRadius, withFill) { 1331 | c.beginPath(); 1332 | c.arc(dotX, dotY, dotRadius, 0, Math.PI * 2, true); 1333 | if (withFill) { 1334 | if (chartStyle == "hollowdot") { 1335 | c.stroke(); 1336 | } else { 1337 | c.fill(); 1338 | } 1339 | } 1340 | } 1341 | 1342 | function drawBow(c, dotX, dotY, withFill) { 1343 | var d = 4; 1344 | c.beginPath(); 1345 | c.moveTo(dotX, dotY); 1346 | c.lineTo(dotX-d, dotY-d); 1347 | c.lineTo(dotX+d, dotY-d); 1348 | c.lineTo(dotX-d, dotY+d); 1349 | c.lineTo(dotX+d, dotY+d); 1350 | c.lineTo(dotX, dotY); 1351 | if (withFill) { 1352 | c.stroke(); 1353 | } 1354 | } 1355 | 1356 | function drawAnchor(c, dotX, dotY, withFill) { 1357 | var d = 8; 1358 | c.beginPath(); 1359 | c.moveTo(dotX-d/2, dotY+Math.sqrt(3)*d/6); 1360 | c.lineTo(dotX, dotY-2*Math.sqrt(3)*d/6); 1361 | c.lineTo(dotX+d/2, dotY+Math.sqrt(3)*d/6); 1362 | c.lineTo(dotX-d/2, dotY+Math.sqrt(3)*d/6); 1363 | if (withFill) { 1364 | c.stroke(); 1365 | } 1366 | } 1367 | 1368 | //x of center, y of center, radius, number of points, fraction of radius for inset 1369 | function drawStar(c, x, y, r, p, m, withFill) { 1370 | c.save(); 1371 | c.beginPath(); 1372 | c.translate(x, y); 1373 | c.moveTo(0,0-r); 1374 | for (var i = 0; i < p; i++) { 1375 | c.rotate(Math.PI / p); 1376 | c.lineTo(0, 0 - (r*m)); 1377 | c.rotate(Math.PI / p); 1378 | c.lineTo(0, 0 - r); 1379 | } 1380 | if (withFill) { 1381 | c.stroke(); 1382 | } 1383 | c.restore(); 1384 | } 1385 | 1386 | function drawLineElements(c, chartStyle, chartType, dotRadius, seriesColor, dotsK, dataLength, xaxisY, globalAlpha, k, i, dotX, dotY, dotX2, dotY2, withFill) { 1387 | 1388 | var space = dotRadius+2; 1389 | var y, y2, alpha; 1390 | if (i < dataLength-1) { 1391 | alpha = Math.atan(Math.abs(dotY2-dotY)/Math.abs(dotX2-dotX)); 1392 | } 1393 | var yy = getLineY(dotY, dotY2, alpha, space); 1394 | c.strokeStyle = seriesColor[k]; 1395 | c.fillStyle = seriesColor[k]; 1396 | if (i < dataLength-1) { 1397 | c.beginPath(); 1398 | c.moveTo(dotX+space*Math.cos(alpha), yy.y); 1399 | c.lineTo(dotX2-space*Math.cos(alpha), yy.y2); 1400 | } else { 1401 | if (chartType == "area") { 1402 | c.strokeStyle = highlightColor(seriesColor[k],1.3); 1403 | c.beginPath(); 1404 | for (var i = 0; i < dotsK[k].length; i++) { 1405 | var dot = dotsK[k][i]; 1406 | if (i == 0) { 1407 | c.moveTo(dot.x, dot.y); 1408 | } else { 1409 | c.lineTo(dot.x, dot.y); 1410 | } 1411 | 1412 | } 1413 | c.lineTo(dotX, xaxisY); 1414 | c.lineTo(dotsK[k][0].x ,xaxisY); 1415 | c.lineTo(dotsK[k][0].x, dotsK[k][0].y); 1416 | c.closePath(); 1417 | if (withFill) { 1418 | c.globalAlpha = globalAlpha; 1419 | c.fill(); 1420 | c.globalAlpha = 1; 1421 | } 1422 | } 1423 | } 1424 | 1425 | if (withFill) { 1426 | // dotX = dotX2 for last interval when we have no line to draw between two points 1427 | if (dotX != dotX2) { 1428 | c.lineWidth = 2; 1429 | c.stroke(); 1430 | } 1431 | } 1432 | 1433 | c.lineWidth = 1; 1434 | if ((chartStyle == "normal") || (chartStyle == "soliddot") || (chartStyle == "hollowdot")) { 1435 | drawDot(c, chartStyle, dotX, dotY, dotRadius, withFill); 1436 | } else if (chartStyle == "bowdot") { 1437 | drawBow(c, dotX, dotY, withFill); 1438 | } else if (chartStyle == "anchordot") { 1439 | drawAnchor(c, dotX, dotY, withFill); 1440 | } else if (chartStyle == "stardot") { 1441 | drawStar(c, dotX, dotY, 5, 5, 0.5, withFill); 1442 | } 1443 | 1444 | } 1445 | 1446 | /**** end line chart utilities****/ -------------------------------------------------------------------------------- /src/js/nextchart.js: -------------------------------------------------------------------------------- 1 | function nextChart(myjson, idCan, idTipCan) { 2 | var can = document.getElementById(idCan); 3 | var tipCan = document.getElementById(idTipCan); 4 | nextChart(myjson, idCan, idTipCan, can.width, can.height); 5 | } 6 | 7 | function nextChart(myjson, idCan, idTipCan, canWidth, canHeight) { 8 | var chartType = myjson.type; 9 | if (typeof chartType === "undefined") { 10 | chartType = "line"; 11 | } 12 | 13 | //zoom 14 | if ((canWidth === "100%") && (canHeight === "100%")) { 15 | var can = document.getElementById(idCan); 16 | can.width = $(window).width(); 17 | can.height = $(window).height(); 18 | } 19 | 20 | if (isBar(chartType)) { 21 | barChart(myjson, idCan, idTipCan, canWidth, canHeight); 22 | } else if (isPie(chartType)) { 23 | pieChart(myjson, idCan, idTipCan, canWidth, canHeight); 24 | } else if (isBubble(chartType)) { 25 | bubbleChart(myjson, idCan, idTipCan, canWidth, canHeight); 26 | } else { 27 | lineChart(myjson, idCan, idTipCan, canWidth, canHeight); 28 | } 29 | } 30 | 31 | function isBar(chartType) { 32 | return (chartType == "bar") || (chartType == "hbar") || (chartType == "stackedbar") || (chartType == "hstackedbar") || (chartType == "nbar"); 33 | } 34 | 35 | function isLine(chartType) { 36 | return (chartType == "line") || (chartType == "area"); 37 | } 38 | 39 | function isPie(chartType) { 40 | return (chartType == "pie"); 41 | } 42 | 43 | function isBubble(chartType) { 44 | return (chartType == "bubble"); 45 | } -------------------------------------------------------------------------------- /src/js/nextwidget.js: -------------------------------------------------------------------------------- 1 | function nextWidget(type, myjson, id, zoom, useParentWidth) { 2 | 3 | if (type == "indicator") { 4 | indicator(id, myjson, zoom, useParentWidth); 5 | } else if (type == "display") { 6 | display(id, myjson, zoom, useParentWidth); 7 | } else if (type == "alarm") { 8 | alarm(id, myjson, zoom, useParentWidth); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/js/pdf-capture.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Capture a list fo elements to a pdf document 3 | * 4 | * This needs htlm2canvas and jspdf libraries 5 | * 6 | * pdfSettings object contains following properties: 7 | * - doc is a jspdf document 8 | * - elements is the list of elements needed to be saved 9 | * - position is the position used inside elements list (must be 1 if we have at least 2 elements, otherwise must not be set) 10 | * - title a title shown on first page 11 | * - showFooter if true will show Page x / y at the end of every page 12 | * 13 | */ 14 | 15 | var capturePdf = function(pdfSettings) { 16 | 17 | var elementId; 18 | if (pdfSettings.position === undefined) { 19 | elementId = pdfSettings.elements[0]; 20 | } else { 21 | elementId = pdfSettings.elements[pdfSettings.position-1]; 22 | } 23 | if ((pdfSettings.position === undefined) || (pdfSettings.position == 1)) { 24 | if (pdfSettings.title !== undefined) { 25 | centeredText(pdfSettings.doc, pdfSettings.title, 10); 26 | } 27 | } 28 | html2canvas($(elementId), { 29 | onrendered: function(canvas) { 30 | var imgData = canvas.toDataURL('image/png'); 31 | //console.log("**** elementId="+elementId + " position="+pdfSettings.position); 32 | //console.log(" page width="+pdfSettings.doc.internal.pageSize.width); 33 | //console.log(" image width=" +$(elementId).width()); 34 | var chartWidth = ($(elementId).width()*25.4)/96; 35 | var chartHeight = ($(elementId).height()*25.4)/96; 36 | 37 | var scaledWidth = 0, scaledHeight = 0; 38 | if (chartHeight > pdfSettings.doc.internal.pageSize.height) { 39 | if (chartWidth > pdfSettings.doc.internal.pageSize.width) { 40 | if (chartHeight - pdfSettings.doc.internal.pageSize.height > chartWidth - pdfSettings.doc.internal.pageSize.width) { 41 | scaledWidth = chartWidth*(pdfSettings.doc.internal.pageSize.height-20)/chartHeight; 42 | scaledHeight = pdfSettings.doc.internal.pageSize.height-20; 43 | } else { 44 | scaledWidth = pdfSettings.doc.internal.pageSize.width-20; 45 | scaledHeight = chartHeight*(pdfSettings.doc.internal.pageSize.width-20)/chartWidth; 46 | } 47 | } else { 48 | scaledWidth = chartWidth*(pdfSettings.doc.internal.pageSize.height-20)/chartHeight; 49 | scaledHeight = pdfSettings.doc.internal.pageSize.height-20; 50 | } 51 | } else if (chartWidth > pdfSettings.doc.internal.pageSize.width) { 52 | scaledWidth = pdfSettings.doc.internal.pageSize.width-20; 53 | scaledHeight = chartHeight*(pdfSettings.doc.internal.pageSize.width-20)/chartWidth; 54 | } 55 | 56 | if (scaledWidth > 0) { 57 | var y = (pdfSettings.doc.internal.pageSize.height - scaledHeight) / 2; 58 | if (y < 10) { 59 | y = 10; 60 | } 61 | pdfSettings.doc.addImage(imgData, 'PNG', 10, y, scaledWidth, scaledHeight); 62 | } else { 63 | var x = (pdfSettings.doc.internal.pageSize.width - chartWidth) / 2; 64 | var y = (pdfSettings.doc.internal.pageSize.height - chartHeight) / 2; 65 | pdfSettings.doc.addImage(imgData, 'PNG', x, y); 66 | } 67 | 68 | var page = 1; 69 | var size = pdfSettings.elements.length; 70 | if (pdfSettings.position !== undefined) { 71 | page = pdfSettings.position; 72 | } 73 | 74 | 75 | if (pdfSettings.showFooter) { 76 | var text = "Page " + page + " / " + size; 77 | if (pdfSettings.footerText !== undefined) { 78 | text = pdfSettings.footerText; 79 | } 80 | 81 | centeredText(pdfSettings.doc, text, pdfSettings.doc.internal.pageSize.height-5, 150, 10); 82 | } 83 | 84 | 85 | if ((pdfSettings.position !== undefined) && (pdfSettings.position < size)) { 86 | pdfSettings.doc.addPage(); 87 | pdfSettings.position = pdfSettings.position+1; 88 | capturePdf(pdfSettings); 89 | } else { 90 | 91 | pdfSettings.doc.setProperties({ 92 | title: 'Dashboard', 93 | subject: 'NextReports Dashboard', 94 | author: 'NextCharts', 95 | keywords: 'nextreports, dashboard, pdf', 96 | creator: 'Mihai Dinca-Panaitescu' 97 | }); 98 | 99 | //console.log("**** SAVE"); 100 | pdfSettings.doc.save('dashboard.pdf'); 101 | } 102 | } 103 | }); 104 | 105 | function centeredText(doc, text, y, color, fontSize) { 106 | if (color !== undefined) { 107 | doc.setTextColor(color); 108 | } 109 | if (fontSize !== undefined) { 110 | doc.setFontSize(fontSize); 111 | } 112 | var textWidth = doc.getStringUnitWidth(text) * doc.internal.getFontSize() / doc.internal.scaleFactor; 113 | var textOffset = (doc.internal.pageSize.width - textWidth) / 2; 114 | doc.text(textOffset, y, text); 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /src/js/piechart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Json must contain as mandatory only "data" attribute 3 | * 4 | * type -> piechart 5 | * message -> can have two markups #val for value, #percent for percent, #total for total value, #x for x label 6 | * -> can contain
to split text on more lines 7 | * showLabels -> true means labels are shown on pie with lines; we can use false if we want to show them in message tooltip with #x 8 | * title.alignment -> center, left, right 9 | * onClick -> is a javascript function like 'function doClick(value){ ...}' * 10 | * 11 | * { "type": "pie", 12 | * "background" : "white", 13 | * "data": [[ 16, 66, 24, 30, 80, 52 ]], 14 | * "labels": ["JANUARY","FEBRUARY","MARCH","APRIL","MAY", "JUNE"], 15 | * "color": ["#004CB3","#A04CB3", "#7aa37a", "#f18e9f", "#90e269", "#bc987b"], 16 | * "alpha" : 0.8, 17 | * "showLabels": true, 18 | * "message" : "Value \: #val", 19 | * "title" : { 20 | * "text": "Analiza financiara", 21 | * "font": { 22 | * "weight": "bold", 23 | * "size": "16", 24 | * "family": "sans-serif" 25 | * }, 26 | * "color": "#000000", 27 | * "alignment":"left" 28 | * }, 29 | * "xData" : { 30 | * "font": { 31 | * "weight": "bold", 32 | * "size": "16", 33 | * "family": "sans-serif" 34 | * }, 35 | * "color": "blue" 36 | * }, 37 | * "tooltipPattern" : { 38 | * "decimals": 2, 39 | * "decimalSeparator" : ".", 40 | * "thousandSeparator" : "," 41 | * }, 42 | * "onClick : "function doClick(value){console.log("Call from function: " + value);}" 43 | * } 44 | * 45 | */ 46 | 47 | var pieChart = function(myjson, idCan, idTipCan, canWidth, canHeight) { 48 | 49 | var obj; 50 | var data; 51 | var labels; 52 | var globalAlpha; 53 | var showLabels; 54 | var background; 55 | var message; 56 | var chartType; 57 | var seriesColor; 58 | var titleSpace = 0; 59 | var realWidth; 60 | var realHeight; 61 | var canvas; 62 | var c; 63 | var tipCanvas; 64 | var tempCanvas; 65 | var tempCtx; 66 | var H = 6; 67 | var line = 20; 68 | var hline = 5; 69 | var resizeWidth = false; 70 | var resizeHeight = false; 71 | // position computed if labels are outside canvas; used to shrink the radius 72 | var delta = 0; 73 | //by default chart title and legends strings have a defined font size 74 | //if we want to have font size scaled accordingly with chart width then this property must be true 75 | //(such example may be when we want to show the chart on a big monitor) 76 | var adjustableTextFontSize = false; 77 | var highlighterIndex = -1; 78 | 79 | function drawPie(myjson, idCan, idTipCan, canWidth, canHeight) { 80 | 81 | canvas = document.getElementById(idCan); 82 | if (canvas == null) { 83 | return; 84 | } 85 | tipCanvas = document.getElementById(idTipCan); 86 | c = canvas.getContext('2d'); 87 | 88 | tempCanvas = document.createElement('canvas'); 89 | tempCtx = tempCanvas.getContext('2d'); 90 | 91 | obj = myjson; 92 | chartType = obj.type; 93 | if (typeof chartType === "undefined") { 94 | chartType = "pie"; 95 | } 96 | 97 | background = obj.background; 98 | if (typeof background === "undefined") { 99 | background = "white"; 100 | } 101 | 102 | data = obj.data[0]; 103 | // prevent negative values 104 | for (var i=0; i= pieData[slice]['startAngle'] && angle <= pieData[slice]['endAngle']) { 311 | highlighterIndex = slice; 312 | var tValue = pieData[slice]['value']; 313 | var tTotal = total; 314 | if (obj.tooltipPattern !== undefined) { 315 | tValue = formatNumber(tValue, obj.tooltipPattern.decimals, obj.tooltipPattern.decimalSeparator, obj.tooltipPattern.thousandSeparator); 316 | tTotal = formatNumber(tTotal, obj.tooltipPattern.decimals, obj.tooltipPattern.decimalSeparator, obj.tooltipPattern.thousandSeparator); 317 | } 318 | 319 | var returnValue; 320 | if (labels === undefined) { 321 | returnValue = ""; 322 | } else { 323 | returnValue = labels[slice]; // tValue 324 | } 325 | if (withClick) { 326 | if (found === undefined) { 327 | found = returnValue; 328 | } 329 | } else { 330 | var mes = String(message).replace('#val', tValue); 331 | mes = mes.replace('#x', returnValue); 332 | mes = mes.replace('#total', tTotal); 333 | mes = mes.replace('#percent', pieData[slice]['percent']); 334 | if (obj.onClick !== undefined) { 335 | cursorStyle = 'pointer'; 336 | canvas.style.cursor = cursorStyle; 337 | } 338 | if (found === undefined) { 339 | found = mes; 340 | } 341 | } 342 | } else { 343 | if (cursorStyle != 'pointer') { 344 | canvas.style.cursor = 'default'; 345 | } 346 | } 347 | } 348 | } 349 | } 350 | 351 | } 352 | 353 | if (found !== undefined) { 354 | return found; 355 | } 356 | 357 | if (withFill) { 358 | return stop; 359 | } else { 360 | // empty tooltip message 361 | return ""; 362 | } 363 | } 364 | 365 | function defineSliceSelection(pieData, i, center, H) { 366 | c.beginPath(); 367 | if (pieData.length > 1) { 368 | c.moveTo(center[0],center[1]); 369 | } 370 | c.arc(center[0],center[1],H,pieData[i]['startAngle'],pieData[i]['endAngle'],false); 371 | if (pieData.length > 1) { 372 | c.lineTo(center[0],center[1]); 373 | } 374 | c.closePath(); 375 | } 376 | 377 | function highlight(gradient, luminance) { 378 | c.fillStyle = background; 379 | c.fill(); 380 | 381 | var lColor = highlightColor(gradient,luminance); 382 | c.fillStyle = lColor; 383 | c.globalAlpha = globalAlpha; 384 | c.fill(); 385 | c.globalAlpha = 1; 386 | 387 | highlighterIndex = -1; 388 | } 389 | 390 | function unhighlight(i) { 391 | if (i == 0) { 392 | c.drawImage(tempCanvas, 0, 0); 393 | } 394 | } 395 | 396 | function drawLabels(i, pieData) { 397 | 398 | if (showLabels) { 399 | var x = pieData[i]['middle'][0]; 400 | var y = pieData[i]['middle'][1]; 401 | var x1 = pieData[i]['labelpos'][0]; 402 | var y1 = pieData[i]['labelpos'][1]; 403 | 404 | c.beginPath(); 405 | c.moveTo(x, y); 406 | c.lineTo(x1, y1); 407 | 408 | var writeFrom = true; 409 | if ((pieData[i]['labelAngle'] == Math.PI) || (pieData[i]['labelAngle'] == 2*Math.PI)) { 410 | // no horizontal line to draw 411 | if ((pieData[i]['labelAngle'] == Math.PI) || (pieData.length == 1)) { 412 | writeFrom = false; 413 | } 414 | } else if (pieData[i]['labelAngle'] <= Math.PI/2) { 415 | x1 = x1+hline; 416 | } else if (pieData[i]['labelAngle'] < Math.PI) { 417 | x1 = x1-hline; 418 | writeFrom = false; 419 | } else if (pieData[i]['labelAngle'] <= 3*Math.PI/2) { 420 | x1 = x1-hline; 421 | writeFrom = false; 422 | } else if (pieData[i]['labelAngle'] < 2*Math.PI) { 423 | x1 = x1+hline; 424 | } 425 | c.lineTo(x1, y1); 426 | 427 | c.strokeStyle = seriesColor[i]; 428 | c.stroke(); 429 | 430 | c.fillStyle = seriesColor[i]; 431 | var fontHeight = 12; 432 | if (obj.xData !== undefined) { 433 | //c.fillStyle = obj.xData.color; 434 | var b = " "; 435 | var xfont = obj.xData.font; 436 | fontHeight = xfont.size; 437 | if (adjustableTextFontSize) { 438 | fontHeight=getAdjustableTitleFontSize(); 439 | xfont.size=fontHeight; 440 | } 441 | c.font = xfont.weight + b + xfont.size + "px" + b + xfont.family; 442 | } else { 443 | var fs = 12; 444 | if (adjustableTextFontSize) { 445 | fs = getAdjustableTitleFontSize(); 446 | } 447 | c.font = "bold " + fs + "px sans-serif"; 448 | } 449 | 450 | if (writeFrom) { 451 | c.fillText(labels[i],x1+5, y1 + fontHeight/2); 452 | } else { 453 | var size = c.measureText(labels[i]).width+5; 454 | c.fillText(labels[i],x1-size, y1 + fontHeight/2); 455 | } 456 | } 457 | } 458 | 459 | 460 | function drawInit() { 461 | 462 | var font = c.font; 463 | 464 | //draw background (clear canvas) 465 | c.fillStyle = background; 466 | c.fillRect(0,0,realWidth,realHeight); 467 | 468 | //draw title 469 | if (typeof obj.title !== "undefined") { 470 | var titleColor = obj.title.color; 471 | if (titleColor === undefined) { 472 | titleColor = '#000000'; 473 | } 474 | c.fillStyle = titleColor; 475 | var b = " "; 476 | var f = obj.title.font; 477 | if (f === undefined) { 478 | f.weight = "bold"; 479 | f.size = 12; 480 | f.family = "sans-serif"; 481 | } 482 | if (adjustableTextFontSize) { 483 | f.size=getAdjustableTitleFontSize(); 484 | } 485 | c.font = f.weight + b + f.size + "px" + b + f.family; 486 | var titlePadding = 20; 487 | if (adjustableTextFontSize) { 488 | titlePadding = getAdjustableTitleFontSize()/2; 489 | } 490 | titleSpace = +titlePadding + +f.size; 491 | 492 | var alignment = obj.title.alignment; 493 | if (alignment === undefined) { 494 | alignment = "center"; 495 | } 496 | var xTitle; 497 | if (alignment == "left") { 498 | xTitle = 10; 499 | } else if (alignment == "right") { 500 | xTitle = canvas.width - c.measureText(obj.title.text).width - 10; 501 | } else { 502 | // center 503 | xTitle = canvas.width/2- c.measureText(obj.title.text).width/2; 504 | } 505 | 506 | var titlePadding = 20; 507 | if (adjustableTextFontSize) { 508 | titlePadding = getAdjustableTitleFontSize()/2; 509 | } 510 | c.fillText(obj.title.text, xTitle , titlePadding+titleSpace/2 ); 511 | c.font = font; 512 | } else { 513 | titleSpace = 0; 514 | } 515 | 516 | c.font = font; 517 | } 518 | 519 | function getTooltip(mousePos) { 520 | return drawData(false, false, mousePos); 521 | } 522 | 523 | function onClick(evt) { 524 | var v = drawData(false, true, getMousePos(canvas, evt)); 525 | if ((v !== "") && (obj.onClick !== undefined)) { 526 | var f = obj.onClick; 527 | eval(f); 528 | doClick(v); 529 | } 530 | return v; 531 | } 532 | 533 | function drawChart() { 534 | window.requestAnimFrame = (function(){ 535 | return window.requestAnimationFrame || 536 | window.webkitRequestAnimationFrame || 537 | window.mozRequestAnimationFrame || 538 | window.oRequestAnimationFrame || 539 | window.msRequestAnimationFrame || 540 | function(/* function */ callback, /* DOMElement */ element){ 541 | window.setTimeout(callback, 1000/60); 542 | }; 543 | })(); 544 | 545 | window.requestAnimFrame(animDraw); 546 | } 547 | 548 | function getTitleSpace() { 549 | var space = 0; 550 | if (typeof obj.title !== "undefined") { 551 | var f = obj.title.font; 552 | if (f === undefined) { 553 | f.size = 12; 554 | } 555 | if (adjustableTextFontSize) { 556 | f.size=getAdjustableTitleFontSize(); 557 | } 558 | var titlePadding = 20; 559 | if (adjustableTextFontSize) { 560 | titlePadding = getAdjustableTitleFontSize(); 561 | } 562 | space = +titlePadding + +f.size; 563 | } 564 | return space; 565 | } 566 | 567 | function getLabelHeight() { 568 | var fontHeight = 12; 569 | if (obj.xData !== undefined) { 570 | fontHeight = obj.xData.font.size; 571 | } 572 | if (adjustableTextFontSize) { 573 | fontHeight=getAdjustableTitleFontSize(); 574 | } 575 | return fontHeight; 576 | } 577 | 578 | function getMaxLabelWidth() { 579 | if (typeof labels === "undefined") { 580 | return 0; 581 | } 582 | var font = c.font; 583 | var max = 0; 584 | if (obj.xData !== undefined) { 585 | var b = " "; 586 | var xfont = obj.xData.font; 587 | if (adjustableTextFontSize) { 588 | xfont.size=getAdjustableTitleFontSize(); 589 | } 590 | c.font = xfont.weight + b + xfont.size + "px" + b + xfont.family; 591 | } else { 592 | var fs = 12; 593 | if (adjustableTextFontSize) { 594 | fs=getAdjustableTitleFontSize(); 595 | } 596 | c.font = "bold " + fs + "px sans-serif"; 597 | } 598 | for(var i=0; i max) { 601 | max = size; 602 | } 603 | } 604 | c.font = font; 605 | return max; 606 | } 607 | 608 | // test if label text are overlapping on Y axis 609 | // if yes we modify ylabel position and compute a delta to update pie radius (if position is outside canvas) 610 | function adjustYLabels(pieData, center, R) { 611 | var f = getFontHeight(); 612 | var d = 0; 613 | for(var i=1; i y2 - f/2)) || 618 | (y1 > y2) ) { 619 | pieData[i]['labelpos'][1] = y1 + f/2 + 5; 620 | var m = Math.pow(R,2) - Math.pow(pieData[i]['labelpos'][1]-center[1],2); 621 | if (m > 0) { 622 | pieData[i]['labelpos'][0] = center[0] + Math.sqrt(m); 623 | } else { 624 | pieData[i]['labelpos'][0] = center[0] - Math.sqrt(-m); 625 | } 626 | if (pieData[i]['labelpos'][1] > realHeight-titleSpace) { 627 | d += f; 628 | } 629 | } 630 | } else if (!isRightCadran(i, pieData) && !isRightCadran(i-1, pieData)) { 631 | if ( ((y1 >= y2) && (y1 - f/2 < y2 + f/2)) || 632 | (y1 < y2) ) { 633 | pieData[i]['labelpos'][1] = y1 - f/2 - 5; 634 | var m = Math.pow(R,2) - Math.pow(pieData[i]['labelpos'][1]-center[1],2); 635 | if (m > 0) { 636 | pieData[i]['labelpos'][0] = center[0] - Math.sqrt(m); 637 | } else { 638 | if (pieData[i]['labelAngle'] > 3*Math.PI/2) { 639 | pieData[i]['labelpos'][0] = center[0] + Math.sqrt(-m); 640 | } 641 | } 642 | if (pieData[i]['labelpos'][1] < titleSpace + f) { 643 | d += f; 644 | } 645 | } 646 | } 647 | } 648 | return d; 649 | } 650 | 651 | function resizeCanvas() { 652 | var can = document.getElementById(idCan); 653 | if (can != null) { 654 | var w = canWidth; 655 | if (resizeWidth) { 656 | if (!isPercent(w)) { 657 | w = "100%"; 658 | } 659 | } 660 | var h = canHeight; 661 | if (resizeHeight) { 662 | if (!isPercent(h)) { 663 | h = "100%"; 664 | } 665 | } 666 | updateSize(w, h); 667 | drawChart(); 668 | } 669 | } 670 | 671 | function getFontHeight() { 672 | var fontHeight = 12; 673 | if (obj.xData !== undefined) { 674 | var xfont = obj.xData.font; 675 | fontHeight = xfont.size; 676 | } 677 | if (adjustableTextFontSize) { 678 | fontHeight = getAdjustableTitleFontSize(); 679 | } 680 | return fontHeight; 681 | } 682 | 683 | function isRightCadran(i, pieData) { 684 | var yes = true; 685 | if (pieData[i]['labelAngle'] <= Math.PI/2) { 686 | yes = true; 687 | } else if (pieData[i]['labelAngle'] <= 3*Math.PI/2) { 688 | yes = false; 689 | } else if (pieData[i]['labelAngle'] <= 2*Math.PI) { 690 | yes = true; 691 | } 692 | return yes; 693 | } 694 | 695 | function getAdjustableTitleFontSize() { 696 | return canvas.width/25; 697 | } 698 | 699 | function getAdjustableLabelFontSize() { 700 | return canvas.width/45; 701 | } 702 | 703 | drawPie(myjson, idCan, idTipCan, canWidth, canHeight); 704 | 705 | }; --------------------------------------------------------------------------------