├── .travis.yml
├── lib
├── javax.servlet.jar
├── servlet-api-2.5.jar
├── jackson-core-2.2.3.jar
├── yuicompressor-2.4.7.jar
├── jackson-databind-2.2.3.jar
├── jackson-annotations-2.2.3.jar
└── jetty-all-9.0.4.v20130625.jar
├── src
├── js
│ ├── nextwidget.js
│ ├── nextchart.js
│ ├── color-util.js
│ ├── display.js
│ ├── pdf-capture.js
│ ├── alarm.js
│ ├── indicator.js
│ ├── chart-util.js
│ ├── piechart.js
│ ├── bubblechart.js
│ └── linechart.js
├── html
│ ├── main-jetty-test.html
│ ├── main-widget-test.html
│ ├── main-test-dualAxis.html
│ └── main-test.html
└── java
│ └── ro
│ └── nextreports
│ └── charts
│ └── JsonHandler.java
├── README.md
└── LICENSE
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk7
4 | script: ant minify-js
5 |
--------------------------------------------------------------------------------
/lib/javax.servlet.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/javax.servlet.jar
--------------------------------------------------------------------------------
/lib/servlet-api-2.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/servlet-api-2.5.jar
--------------------------------------------------------------------------------
/lib/jackson-core-2.2.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/jackson-core-2.2.3.jar
--------------------------------------------------------------------------------
/lib/yuicompressor-2.4.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/yuicompressor-2.4.7.jar
--------------------------------------------------------------------------------
/lib/jackson-databind-2.2.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/jackson-databind-2.2.3.jar
--------------------------------------------------------------------------------
/lib/jackson-annotations-2.2.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/jackson-annotations-2.2.3.jar
--------------------------------------------------------------------------------
/lib/jetty-all-9.0.4.v20130625.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextreports/nextcharts/HEAD/lib/jetty-all-9.0.4.v20130625.jar
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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/js/color-util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * HSV to RGB color conversion
3 | *
4 | * H runs from 0 to 360 degrees
5 | * S and V run from 0 to 100
6 | *
7 | * http://snipplr.com/view/14590
8 | */
9 | function hsvToRgb(h, s, v) {
10 | var r, g, b;
11 | var i;
12 | var f, p, q, t;
13 |
14 | // Make sure our arguments stay in-range
15 | h = Math.max(0, Math.min(360, h));
16 | s = Math.max(0, Math.min(100, s));
17 | v = Math.max(0, Math.min(100, v));
18 |
19 | // We accept saturation and value arguments from 0 to 100 because that's
20 | // how Photoshop represents those values. Internally, however, the
21 | // saturation and value are calculated from a range of 0 to 1. We make
22 | // That conversion here.
23 | s /= 100;
24 | v /= 100;
25 |
26 | if(s == 0) {
27 | // Achromatic (grey)
28 | r = g = b = v;
29 | return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
30 | }
31 |
32 | h /= 60; // sector 0 to 5
33 | i = Math.floor(h);
34 | f = h - i; // factorial part of h
35 | p = v * (1 - s);
36 | q = v * (1 - s * f);
37 | t = v * (1 - s * (1 - f));
38 |
39 | switch(i) {
40 | case 0:
41 | r = v;
42 | g = t;
43 | b = p;
44 | break;
45 |
46 | case 1:
47 | r = q;
48 | g = v;
49 | b = p;
50 | break;
51 |
52 | case 2:
53 | r = p;
54 | g = v;
55 | b = t;
56 | break;
57 |
58 | case 3:
59 | r = p;
60 | g = q;
61 | b = v;
62 | break;
63 |
64 | case 4:
65 | r = t;
66 | g = p;
67 | b = v;
68 | break;
69 |
70 | default: // case 5:
71 | r = v;
72 | g = p;
73 | b = q;
74 | }
75 |
76 | return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
77 | }
78 |
79 | function hsvToHex(h, s, v) {
80 | var rgb = hsvToRgb(h, s, v);
81 | return rgbToHex(rgb[0], rgb[1], rgb[2]);
82 | }
83 |
84 | function distinctHexColors(count) {
85 | var colors = [];
86 | for(var hue = 0; hue < 360; hue += 360 / count) {
87 | colors.push(hsvToHex(hue, 100, 100));
88 | }
89 | return colors;
90 | }
91 |
92 | function colorLuminance(color, lum) {
93 | var hex = colorToHex(color);
94 | // validate hex string
95 | hex = String(hex).replace(/[^0-9a-f]/gi, '');
96 | if (hex.length < 6) {
97 | hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
98 | }
99 | lum = lum || 0;
100 | // convert to decimal and change luminosity
101 | var rgb = "#", c, i;
102 | for (i = 0; i < 3; i++) {
103 | c = parseInt(hex.substr(i*2,2), 16);
104 | c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
105 | rgb += ("00"+c).substr(c.length);
106 | }
107 | return rgb;
108 | }
109 |
110 | function colorLuminance2(color, lum) {
111 | var hex = colorToHex(color);
112 | // validate hex string
113 | hex = String(hex).replace(/[^0-9a-f]/gi, '');
114 | if (hex.length < 6) {
115 | hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
116 | }
117 | lum = lum || 0;
118 | // convert to decimal and change luminosity
119 | var rgb = "#", c, i;
120 | for (i = 0; i < 3; i++) {
121 | c = parseInt(hex.substr(i*2,2), 16);
122 | c = Math.round(Math.min(Math.max(0, c * lum), 255)).toString(16);
123 | rgb += ("00"+c).substr(c.length);
124 | }
125 | return rgb;
126 | }
127 |
128 |
129 | function componentToHex(c) {
130 | var hex = c.toString(16);
131 | return hex.length == 1 ? "0" + hex : hex;
132 | }
133 |
134 | // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
135 | function rgbToHex(r, g, b) {
136 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
137 | }
138 |
139 | function colorToHex(c) {
140 | var m = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(c);
141 | return m ? '#' + (1 << 24 | m[1] << 16 | m[2] << 8 | m[3]).toString(16).substr(1) : c;
142 | }
143 |
144 | //returns brightness value from 0 to 255
145 | //http://www.webmasterworld.com/forum88/9769.htm
146 | //https://gist.github.com/geekmy/5010419
147 | function get_brightness(hexCode) {
148 | // strip off any leading #
149 | hexCode = hexCode.replace('#', '');
150 |
151 | var c_r = parseInt(hexCode.substr(0, 2),16);
152 | var c_g = parseInt(hexCode.substr(2, 2),16);
153 | var c_b = parseInt(hexCode.substr(4, 2),16);
154 |
155 | return ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
156 | }
157 |
158 | function highlightColor(color, lum) {
159 | var hex = colorToHex(color);
160 | if (get_brightness(hex) > 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/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/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/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/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/chart-util.js:
--------------------------------------------------------------------------------
1 | // This function starts by creating a dummy