diff --git a/.eslintrc b/.eslintrc
index 44af7b458f..ade1623262 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -118,6 +118,8 @@
"getCookie": true,
"getCookies": true,
"get_url_arg": true,
- "QUnit": true
+ "QUnit": true,
+ "Snap": true,
+ "mina": true
}
}
diff --git a/frappe/docs/assets/img/desk/animated_line_graph.gif b/frappe/docs/assets/img/desk/animated_line_graph.gif
new file mode 100644
index 0000000000..0b0e7b212c
Binary files /dev/null and b/frappe/docs/assets/img/desk/animated_line_graph.gif differ
diff --git a/frappe/docs/assets/img/desk/bar_graph.png b/frappe/docs/assets/img/desk/bar_graph.png
index d25254af6d..b3bd89cc88 100644
Binary files a/frappe/docs/assets/img/desk/bar_graph.png and b/frappe/docs/assets/img/desk/bar_graph.png differ
diff --git a/frappe/docs/assets/img/desk/line_graph.png b/frappe/docs/assets/img/desk/line_graph.png
deleted file mode 100644
index 02c60c7c18..0000000000
Binary files a/frappe/docs/assets/img/desk/line_graph.png and /dev/null differ
diff --git a/frappe/docs/assets/img/desk/line_graph_sales.png b/frappe/docs/assets/img/desk/line_graph_sales.png
new file mode 100644
index 0000000000..0e70ae0031
Binary files /dev/null and b/frappe/docs/assets/img/desk/line_graph_sales.png differ
diff --git a/frappe/docs/assets/img/desk/percentage_graph.png b/frappe/docs/assets/img/desk/percentage_graph.png
new file mode 100644
index 0000000000..3a25d59479
Binary files /dev/null and b/frappe/docs/assets/img/desk/percentage_graph.png differ
diff --git a/frappe/docs/user/en/guides/desk/making_graphs.md b/frappe/docs/user/en/guides/desk/making_graphs.md
index 9234fa58b4..720c9217bf 100644
--- a/frappe/docs/user/en/guides/desk/making_graphs.md
+++ b/frappe/docs/user/en/guides/desk/making_graphs.md
@@ -1,61 +1,100 @@
# Making Graphs
-The Frappe UI **Graph** object enables you to render simple line and bar graphs for a discreet set of data points. You can also set special checkpoint values and summary stats.
+The Frappe UI **Graph** object enables you to render simple line, bar or percentage graphs for single or multiple discreet sets of data points. You can also set special checkpoint values and summary stats.
### Example: Line graph
-Here's is an example of a simple sales graph:
-
- render_graph: function() {
- $('.form-graph').empty();
-
- var months = ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
- var values = [2410, 3100, 1700, 1200, 2700, 1600, 2740, 1000, 850, 1500, 400, 2013];
-
- var goal = 2500;
- var current_val = 2013;
-
- new frappe.ui.Graph({
- parent: $('.form-graph'),
- width: 700,
- height: 140,
- mode: 'line-graph',
-
- title: 'Sales',
- subtitle: 'Monthly',
- y_values: values,
- x_points: months,
-
- specific_values: [
- {
- name: "Goal",
- line_type: "dashed", // "dashed" or "solid"
- value: goal
- },
- ],
- summary_values: [
- {
- name: "This month",
- color: 'green', // Indicator colors: 'grey', 'blue', 'red',
- // 'green', 'orange', 'purple', 'darkgrey',
- // 'black', 'yellow', 'lightblue'
- value: '₹ ' + current_val
- },
- {
- name: "Goal",
- color: 'blue',
- value: '₹ ' + goal
- },
- {
- name: "Completed",
- color: 'green',
- value: (current_val/goal*100).toFixed(1) + "%"
- }
- ]
- });
- },
-
-
-
-Setting the mode to 'bar-graph':
+Here's an example of a simple sales graph:
+
+ // Data
+ let months = ['August, 2016', 'September, 2016', 'October, 2016', 'November, 2016',
+ 'December, 2016', 'January, 2017', 'February, 2017', 'March, 2017', 'April, 2017',
+ 'May, 2017', 'June, 2017', 'July, 2017'];
+
+ let values1 = [24100, 31000, 17000, 12000, 27000, 16000, 27400, 11000, 8500, 15000, 4000, 20130];
+ let values2 = [17890, 10400, 12350, 20400, 17050, 23000, 7100, 13800, 16000, 20400, 11000, 13000];
+ let goal = 25000;
+ let current_val = 20130;
+
+ let g = new frappe.ui.Graph({
+ parent: $('.form-graph').empty(),
+ height: 200, // optional
+ mode: 'line', // 'line', 'bar' or 'percentage'
+
+ title: 'Sales',
+ subtitle: 'Monthly',
+
+ y: [
+ {
+ title: 'Data 1',
+ values: values1,
+ formatted: values1.map(d => '$ ' + d),
+ color: 'green' // Indicator colors: 'grey', 'blue', 'red',
+ // 'green', 'light-green', 'orange', 'purple', 'darkgrey',
+ // 'black', 'yellow', 'lightblue'
+ },
+ {
+ title: 'Data 2',
+ values: values2,
+ formatted: values2.map(d => '$ ' + d),
+ color: 'light-green'
+ }
+ ],
+
+ x: {
+ values: months.map(d => d.substring(0, 3)),
+ formatted: months
+ },
+
+ specific_values: [
+ {
+ name: 'Goal',
+ line_type: 'dashed', // 'dashed' or 'solid'
+ value: goal
+ },
+ ],
+
+ summary: [
+ {
+ name: 'This month',
+ color: 'orange',
+ value: '$ ' + current_val
+ },
+ {
+ name: 'Goal',
+ color: 'blue',
+ value: '$ ' + goal
+ },
+ {
+ name: 'Completed',
+ color: 'green',
+ value: (current_val/goal*100).toFixed(1) + "%"
+ }
+ ]
+ });
+
+
+
+`bar` mode yeilds:
+
+You can set the `colors` property of `x` to an array of color values for `percentage` mode:
+
+
+
+You can also change the values of an existing graph with a new set of `y` values:
+
+ setTimeout(() => {
+ g.change_values([
+ {
+ values: data[2],
+ formatted: data[2].map(d => d + 'L')
+ },
+ {
+ values: data[3],
+ formatted: data[3].map(d => d + 'L')
+ }
+ ]);
+ }, 1000);
+
+
diff --git a/frappe/public/build.json b/frappe/public/build.json
index b350c8151a..054421286e 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -52,7 +52,8 @@
"public/css/desktop.css",
"public/css/form.css",
"public/css/mobile.css",
- "public/css/kanban.css"
+ "public/css/kanban.css",
+ "public/css/graphs.css"
],
"css/frappe-rtl.css": [
"public/css/bootstrap-rtl.css",
@@ -164,7 +165,7 @@
"public/js/frappe/query_string.js",
"public/js/frappe/ui/charts.js",
- "public/js/frappe/ui/graph.js",
+ "public/js/frappe/ui/graphs.js",
"public/js/frappe/ui/comment.js",
"public/js/frappe/misc/rating_icons.html",
diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css
index 0d21271862..c56811e892 100644
--- a/frappe/public/css/form.css
+++ b/frappe/public/css/form.css
@@ -678,80 +678,6 @@ select.form-control {
padding: 10px;
margin: 10px;
}
-.graph-container .graphics {
- margin-top: 10px;
- padding: 10px 0px;
-}
-.graph-container .stats-group {
- display: flex;
- justify-content: space-around;
- flex: 1;
-}
-.graph-container .stats-container {
- display: flex;
- justify-content: space-around;
-}
-.graph-container .stats-container .stats {
- padding-bottom: 15px;
-}
-.graph-container .stats-container .stats-title {
- color: #8D99A6;
-}
-.graph-container .stats-container .stats-value {
- font-size: 20px;
- font-weight: 300;
-}
-.graph-container .stats-container .stats-description {
- font-size: 12px;
- color: #8D99A6;
-}
-.graph-container .stats-container .graph-data .stats-value {
- color: #98d85b;
-}
-.bar-graph .axis,
-.line-graph .axis {
- font-size: 10px;
- fill: #6a737d;
-}
-.bar-graph .axis line,
-.line-graph .axis line {
- stroke: rgba(27, 31, 35, 0.1);
-}
-.data-points circle {
- fill: #28a745;
- stroke: #fff;
- stroke-width: 2;
-}
-.data-points g.mini {
- fill: #98d85b;
-}
-.data-points path {
- fill: none;
- stroke: #28a745;
- stroke-opacity: 1;
- stroke-width: 2px;
-}
-.line-graph .path {
- fill: none;
- stroke: #28a745;
- stroke-opacity: 1;
- stroke-width: 2px;
-}
-line.dashed {
- stroke-dasharray: 5,3;
-}
-.tick.x-axis-label {
- display: block;
-}
-.tick .specific-value {
- text-anchor: start;
-}
-.tick .y-value-text {
- text-anchor: end;
-}
-.tick .x-value-text {
- text-anchor: middle;
-}
body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
height: 80px !important;
}
diff --git a/frappe/public/css/graphs.css b/frappe/public/css/graphs.css
new file mode 100644
index 0000000000..a9fdf62dc9
--- /dev/null
+++ b/frappe/public/css/graphs.css
@@ -0,0 +1,274 @@
+/* graphs */
+.graph-container .graph-focus-margin {
+ margin: 0px 5%;
+}
+.graph-container .graph-graphics {
+ margin-top: 10px;
+ padding: 10px 0px;
+ position: relative;
+}
+.graph-container .graph-stats-group {
+ display: flex;
+ justify-content: space-around;
+ flex: 1;
+}
+.graph-container .graph-stats-container {
+ display: flex;
+ justify-content: space-around;
+ padding-top: 10px;
+}
+.graph-container .graph-stats-container .stats {
+ padding-bottom: 15px;
+}
+.graph-container .graph-stats-container .stats-title {
+ color: #8D99A6;
+}
+.graph-container .graph-stats-container .stats-value {
+ font-size: 20px;
+ font-weight: 300;
+}
+.graph-container .graph-stats-container .stats-description {
+ font-size: 12px;
+ color: #8D99A6;
+}
+.graph-container .graph-stats-container .graph-data .stats-value {
+ color: #98d85b;
+}
+.graph-container .bar-graph .axis,
+.graph-container .line-graph .axis {
+ font-size: 10px;
+ fill: #6a737d;
+}
+.graph-container .bar-graph .axis line,
+.graph-container .line-graph .axis line {
+ stroke: rgba(27, 31, 35, 0.1);
+}
+.graph-container .percentage-graph {
+ margin-top: 35px;
+}
+.graph-container .percentage-graph .progress {
+ margin-bottom: 0px;
+}
+.graph-container .graph-data-points circle {
+ stroke: #fff;
+ stroke-width: 2;
+}
+.graph-container .graph-data-points path {
+ fill: none;
+ stroke-opacity: 1;
+ stroke-width: 2px;
+}
+.graph-container line.graph-dashed {
+ stroke-dasharray: 5,3;
+}
+.graph-container .tick.x-axis-label {
+ display: block;
+}
+.graph-container .tick .specific-value {
+ text-anchor: start;
+}
+.graph-container .tick .y-value-text {
+ text-anchor: end;
+}
+.graph-container .tick .x-value-text {
+ text-anchor: middle;
+}
+.graph-container .graph-svg-tip {
+ position: absolute;
+ z-index: 99999;
+ padding: 10px;
+ font-size: 12px;
+ color: #959da5;
+ text-align: center;
+ background: rgba(0, 0, 0, 0.8);
+ border-radius: 3px;
+}
+.graph-container .graph-svg-tip.comparison {
+ padding: 0;
+ text-align: left;
+ pointer-events: none;
+}
+.graph-container .graph-svg-tip.comparison .title {
+ display: block;
+ padding: 10px;
+ margin: 0;
+ font-weight: 600;
+ line-height: 1;
+ pointer-events: none;
+}
+.graph-container .graph-svg-tip.comparison ul {
+ margin: 0;
+ white-space: nowrap;
+ list-style: none;
+}
+.graph-container .graph-svg-tip.comparison li {
+ display: inline-block;
+ padding: 5px 10px;
+}
+.graph-container .graph-svg-tip ul,
+.graph-container .graph-svg-tip ol {
+ padding-left: 0;
+ display: flex;
+}
+.graph-container .graph-svg-tip ul.data-point-list li {
+ min-width: 90px;
+ flex: 1;
+}
+.graph-container .graph-svg-tip strong {
+ color: #dfe2e5;
+}
+.graph-container .graph-svg-tip::after {
+ position: absolute;
+ bottom: -10px;
+ left: 50%;
+ width: 5px;
+ height: 5px;
+ margin: 0 0 0 -5px;
+ content: " ";
+ border: 5px solid transparent;
+ border-top-color: rgba(0, 0, 0, 0.8);
+}
+.graph-container .stroke.grey {
+ stroke: #F0F4F7;
+}
+.graph-container .stroke.blue {
+ stroke: #5e64ff;
+}
+.graph-container .stroke.red {
+ stroke: #ff5858;
+}
+.graph-container .stroke.light-green {
+ stroke: #98d85b;
+}
+.graph-container .stroke.green {
+ stroke: #28a745;
+}
+.graph-container .stroke.orange {
+ stroke: #ffa00a;
+}
+.graph-container .stroke.purple {
+ stroke: #743ee2;
+}
+.graph-container .stroke.darkgrey {
+ stroke: #b8c2cc;
+}
+.graph-container .stroke.black {
+ stroke: #36414C;
+}
+.graph-container .stroke.yellow {
+ stroke: #FEEF72;
+}
+.graph-container .stroke.light-blue {
+ stroke: #7CD6FD;
+}
+.graph-container .stroke.lightblue {
+ stroke: #7CD6FD;
+}
+.graph-container .fill.grey {
+ fill: #F0F4F7;
+}
+.graph-container .fill.blue {
+ fill: #5e64ff;
+}
+.graph-container .fill.red {
+ fill: #ff5858;
+}
+.graph-container .fill.light-green {
+ fill: #98d85b;
+}
+.graph-container .fill.green {
+ fill: #28a745;
+}
+.graph-container .fill.orange {
+ fill: #ffa00a;
+}
+.graph-container .fill.purple {
+ fill: #743ee2;
+}
+.graph-container .fill.darkgrey {
+ fill: #b8c2cc;
+}
+.graph-container .fill.black {
+ fill: #36414C;
+}
+.graph-container .fill.yellow {
+ fill: #FEEF72;
+}
+.graph-container .fill.light-blue {
+ fill: #7CD6FD;
+}
+.graph-container .fill.lightblue {
+ fill: #7CD6FD;
+}
+.graph-container .background.grey {
+ background: #F0F4F7;
+}
+.graph-container .background.blue {
+ background: #5e64ff;
+}
+.graph-container .background.red {
+ background: #ff5858;
+}
+.graph-container .background.light-green {
+ background: #98d85b;
+}
+.graph-container .background.green {
+ background: #28a745;
+}
+.graph-container .background.orange {
+ background: #ffa00a;
+}
+.graph-container .background.purple {
+ background: #743ee2;
+}
+.graph-container .background.darkgrey {
+ background: #b8c2cc;
+}
+.graph-container .background.black {
+ background: #36414C;
+}
+.graph-container .background.yellow {
+ background: #FEEF72;
+}
+.graph-container .background.light-blue {
+ background: #7CD6FD;
+}
+.graph-container .background.lightblue {
+ background: #7CD6FD;
+}
+.graph-container .border-top.grey {
+ border-top: 3px solid #F0F4F7;
+}
+.graph-container .border-top.blue {
+ border-top: 3px solid #5e64ff;
+}
+.graph-container .border-top.red {
+ border-top: 3px solid #ff5858;
+}
+.graph-container .border-top.light-green {
+ border-top: 3px solid #98d85b;
+}
+.graph-container .border-top.green {
+ border-top: 3px solid #28a745;
+}
+.graph-container .border-top.orange {
+ border-top: 3px solid #ffa00a;
+}
+.graph-container .border-top.purple {
+ border-top: 3px solid #743ee2;
+}
+.graph-container .border-top.darkgrey {
+ border-top: 3px solid #b8c2cc;
+}
+.graph-container .border-top.black {
+ border-top: 3px solid #36414C;
+}
+.graph-container .border-top.yellow {
+ border-top: 3px solid #FEEF72;
+}
+.graph-container .border-top.light-blue {
+ border-top: 3px solid #7CD6FD;
+}
+.graph-container .border-top.lightblue {
+ border-top: 3px solid #7CD6FD;
+}
diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js
index d27aa619e0..ca2b4ab9bd 100644
--- a/frappe/public/js/frappe/form/dashboard.js
+++ b/frappe/public/js/frappe/form/dashboard.js
@@ -418,9 +418,8 @@ frappe.ui.form.Dashboard = Class.extend({
this.graph_area.empty().removeClass('hidden');
$.extend(args, {
parent: me.graph_area,
- width: 710,
- height: 140,
- mode: 'line-graph'
+ mode: 'line',
+ height: 140
});
new frappe.ui.Graph(args);
diff --git a/frappe/public/js/frappe/ui/graph.js b/frappe/public/js/frappe/ui/graph.js
deleted file mode 100644
index 2347f13b0d..0000000000
--- a/frappe/public/js/frappe/ui/graph.js
+++ /dev/null
@@ -1,308 +0,0 @@
-// specific_values = [
-// {
-// name: "Average",
-// line_type: "dashed", // "dashed" or "solid"
-// value: 10
-// },
-
-// summary_values = [
-// {
-// name: "Total",
-// color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange',
-// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue'
-// value: 80
-// }
-// ]
-
-frappe.ui.Graph = class Graph {
- constructor({
- parent = null,
-
- width = 0, height = 0,
- title = '', subtitle = '',
-
- y_values = [],
- x_points = [],
-
- specific_values = [],
- summary_values = [],
-
- color = '',
- mode = '',
- } = {}) {
-
- if(Object.getPrototypeOf(this) === frappe.ui.Graph.prototype) {
- if(mode === 'line-graph') {
- return new frappe.ui.LineGraph(arguments[0]);
- } else if(mode === 'bar-graph') {
- return new frappe.ui.BarGraph(arguments[0]);
- }
- }
-
- this.parent = parent;
-
- this.width = width;
- this.height = height;
-
- this.title = title;
- this.subtitle = subtitle;
-
- this.y_values = y_values;
- this.x_points = x_points;
-
- this.specific_values = specific_values;
- this.summary_values = summary_values;
-
- this.color = color;
- this.mode = mode;
-
- this.$graph = null;
-
- frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this));
- }
-
- setup() {
- this.setup_container();
- this.refresh();
- }
-
- refresh() {
- this.setup_values();
- this.setup_components();
- this.make_y_axis();
- this.make_x_axis();
- this.make_units();
- if(this.specific_values.length > 0) {
- this.show_specific_values();
- }
- this.setup_group();
-
- if(this.summary_values.length > 0) {
- this.show_summary();
- }
- }
-
- setup_container() {
- this.container = $('
')
- .addClass('graph-container')
- .append($(`
${this.title}
`))
- .append($(`
${this.subtitle}
`))
- .append($(`
`))
- .append($(`
`))
- .appendTo(this.parent);
-
- let $graphics = this.container.find('.graphics');
- this.$stats_container = this.container.find('.stats-container');
-
- this.$graph = $('
')
- .addClass(this.mode)
- .appendTo($graphics);
-
- this.$svg = $(`
`);
- this.$graph.append(this.$svg);
-
- this.snap = new Snap(this.$svg[0]);
- }
-
- setup_values() {
- this.upper_graph_bound = this.get_upper_limit_and_parts(this.y_values)[0];
- this.y_axis = this.get_y_axis(this.y_values);
- this.avg_unit_width = (this.width-100)/(this.x_points.length - 1);
- }
-
- setup_components() {
- this.y_axis_group = this.snap.g().attr({
- class: "y axis"
- });
-
- this.x_axis_group = this.snap.g().attr({
- class: "x axis"
- });
-
- this.graph_list = this.snap.g().attr({
- class: "data-points",
- });
-
- this.specific_y_lines = this.snap.g().attr({
- class: "specific axis",
- });
- }
-
- setup_group() {
- this.snap.g(
- this.y_axis_group,
- this.x_axis_group,
- this.graph_list,
- this.specific_y_lines
- ).attr({
- transform: "translate(60, 10)" // default
- });
- }
-
- show_specific_values() {
- this.specific_values.map(d => {
- this.specific_y_lines.add(this.snap.g(
- this.snap.line(0, 0, this.width - 70, 0).attr({
- class: d.line_type === "dashed" ? "dashed": ""
- }),
- this.snap.text(this.width - 95, 0, d.name.toUpperCase()).attr({
- dy: ".32em",
- class: "specific-value",
- })
- ).attr({
- class: "tick",
- transform: `translate(0, ${100 - 100/(this.upper_graph_bound/d.value) })`
- }));
- });
- }
-
- show_summary() {
- this.summary_values.map(d => {
- this.$stats_container.append($(`
- ${d.name}: ${d.value}
-
`));
- });
- }
-
- // Helpers
- get_upper_limit_and_parts(array) {
- let specific_values = this.specific_values.map(d => d.value);
- let max_val = parseInt(Math.max(...array, ...specific_values));
- if((max_val+"").length <= 1) {
- return [10, 5];
- } else {
- let multiplier = Math.pow(10, ((max_val+"").length - 1));
- let significant = Math.ceil(max_val/multiplier);
- if(significant % 2 !== 0) significant++;
- let parts = (significant < 5) ? significant : significant/2;
- return [significant * multiplier, parts];
- }
- }
-
- get_y_axis(array) {
- let upper_limit, parts;
- [upper_limit, parts] = this.get_upper_limit_and_parts(array);
- let y_axis = [];
- for(var i = 0; i <= parts; i++){
- y_axis.push(upper_limit / parts * i);
- }
- return y_axis;
- }
-};
-
-frappe.ui.BarGraph = class BarGraph extends frappe.ui.Graph {
- constructor(args = {}) {
- super(args);
- }
-
- setup_values() {
- super.setup_values();
- this.avg_unit_width = (this.width-50)/(this.x_points.length + 2);
- }
-
- make_y_axis() {
- this.y_axis.map((point) => {
- this.y_axis_group.add(this.snap.g(
- this.snap.line(0, 0, this.width, 0),
- this.snap.text(-3, 0, point+"").attr({
- dy: ".32em",
- class: "y-value-text"
- })
- ).attr({
- class: "tick",
- transform: `translate(0, ${100 - (100/(this.y_axis.length-1) * this.y_axis.indexOf(point)) })`
- }));
- });
- }
-
- make_x_axis() {
- this.x_axis_group.attr({
- transform: "translate(0,100)"
- });
- this.x_points.map((point, i) => {
- this.x_axis_group.add(this.snap.g(
- this.snap.line(0, 0, 0, 6),
- this.snap.text(0, 9, point).attr({
- dy: ".71em",
- class: "x-value-text"
- })
- ).attr({
- class: "tick x-axis-label",
- transform: `translate(${ ((this.avg_unit_width - 5)*3/2) + i * (this.avg_unit_width + 5) }, 0)`
- }));
- });
- }
-
- make_units() {
- this.y_values.map((value, i) => {
- this.graph_list.add(this.snap.g(
- this.snap.rect(
- 0,
- (100 - 100/(this.upper_graph_bound/value)),
- this.avg_unit_width - 5,
- 100/(this.upper_graph_bound/value)
- )
- ).attr({
- class: "bar mini",
- transform: `translate(${ (this.avg_unit_width - 5) + i * (this.avg_unit_width + 5) }, 0)`,
- }));
- });
- }
-};
-
-frappe.ui.LineGraph = class LineGraph extends frappe.ui.Graph {
- constructor(args = {}) {
- super(args);
- }
-
- make_y_axis() {
- this.y_axis.map((point) => {
- this.y_axis_group.add(this.snap.g(
- this.snap.line(0, 0, -6, 0),
- this.snap.text(-9, 0, point+"").attr({
- dy: ".32em",
- class: "y-value-text"
- })
- ).attr({
- class: "tick",
- transform: `translate(0, ${100 - (100/(this.y_axis.length-1)
- * this.y_axis.indexOf(point)) })`
- }));
- });
- }
-
- make_x_axis() {
- this.x_axis_group.attr({
- transform: "translate(0,-7)"
- });
- this.x_points.map((point, i) => {
- this.x_axis_group.add(this.snap.g(
- this.snap.line(0, 0, 0, this.height - 25),
- this.snap.text(0, this.height - 15, point).attr({
- dy: ".71em",
- class: "x-value-text"
- })
- ).attr({
- class: "tick",
- transform: `translate(${ i * this.avg_unit_width }, 0)`
- }));
- });
- }
-
- make_units() {
- let points_list = [];
- this.y_values.map((value, i) => {
- let x = i * this.avg_unit_width;
- let y = (100 - 100/(this.upper_graph_bound/value));
- this.graph_list.add(this.snap.circle( x, y, 4));
- points_list.push(x+","+y);
- });
-
- this.make_path("M"+points_list.join("L"));
- }
-
- make_path(path_str) {
- this.graph_list.prepend(this.snap.path(path_str));
- }
-
-};
diff --git a/frappe/public/js/frappe/ui/graphs.js b/frappe/public/js/frappe/ui/graphs.js
new file mode 100644
index 0000000000..f45ced98ba
--- /dev/null
+++ b/frappe/public/js/frappe/ui/graphs.js
@@ -0,0 +1,569 @@
+// specific_values = [
+// {
+// name: "Average",
+// line_type: "dashed", // "dashed" or "solid"
+// value: 10
+// },
+
+// summary = [
+// {
+// name: "Total",
+// color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange',
+// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue'
+// value: 80
+// }
+// ]
+
+// Graph: Abstract object
+frappe.ui.Graph = class Graph {
+ constructor({
+ parent = null,
+ height = 240,
+
+ title = '', subtitle = '',
+
+ y = [],
+ x = [],
+
+ specific_values = [],
+ summary = [],
+
+ color = 'blue',
+ mode = '',
+ }) {
+
+ if(Object.getPrototypeOf(this) === frappe.ui.Graph.prototype) {
+ if(mode === 'line') {
+ return new frappe.ui.LineGraph(arguments[0]);
+ } else if(mode === 'bar') {
+ return new frappe.ui.BarGraph(arguments[0]);
+ } else if(mode === 'percentage') {
+ return new frappe.ui.PercentageGraph(arguments[0]);
+ }
+ }
+
+ this.parent = parent;
+ this.base_height = height;
+ this.height = height - 40;
+
+ this.translate_x = 60;
+ this.translate_y = 10;
+
+ this.title = title;
+ this.subtitle = subtitle;
+
+ this.y = y;
+ this.x = x;
+
+ this.specific_values = specific_values;
+ this.summary = summary;
+
+ this.color = color;
+ this.mode = mode;
+
+ this.$graph = null;
+
+ // Validate all arguments
+
+ frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this));
+ }
+
+ setup() {
+ this.bind_window_event();
+ this.refresh();
+ }
+
+ bind_window_event() {
+ $(window).on('resize orientationChange', () => {
+ this.refresh();
+ });
+ }
+
+ refresh() {
+
+ this.base_width = this.parent.width() - 20;
+ this.width = this.base_width - 100;
+
+ this.setup_container();
+
+ this.setup_values();
+
+ this.setup_utils();
+
+ this.setup_components();
+ this.make_graph_components();
+
+ this.make_tooltip();
+
+ if(this.summary.length > 0) {
+ this.show_custom_summary();
+ } else {
+ this.show_summary();
+ }
+ }
+
+ setup_container() {
+ // Graph needs a dedicated parent element
+ this.parent.empty();
+
+ this.container = $('
')
+ .addClass('graph-container')
+ .append($(`
${this.title}
`))
+ .append($(`
${this.subtitle}
`))
+ .append($(`
`))
+ .append($(`
`))
+ .appendTo(this.parent);
+
+ this.$graphics = this.container.find('.graph-graphics');
+ this.$stats_container = this.container.find('.graph-stats-container');
+
+ this.$graph = $('
')
+ .addClass(this.mode + '-graph')
+ .appendTo(this.$graphics);
+
+ this.$graph.append(this.make_graph_area());
+ }
+
+ make_graph_area() {
+ this.$svg = $(`
`);
+ this.snap = new Snap(this.$svg[0]);
+ return this.$svg;
+ }
+
+ setup_values() {
+ // Multiplier
+ let all_values = this.specific_values.map(d => d.value);
+ this.y.map(d => {
+ all_values = all_values.concat(d.values);
+ });
+ [this.upper_limit, this.parts] = this.get_upper_limit_and_parts(all_values);
+ this.multiplier = this.height / this.upper_limit;
+
+ // Baselines
+ this.set_avg_unit_width_and_x_offset();
+
+ this.x_axis_values = this.x.values.map((d, i) => this.x_offset + i * this.avg_unit_width);
+ this.y_axis_values = this.get_y_axis_values(this.upper_limit, this.parts);
+
+ // Data points
+ this.y.map(d => {
+ d.y_tops = d.values.map( val => this.height - val * this.multiplier );
+ d.data_units = [];
+ });
+
+ this.calc_min_tops();
+ }
+
+ set_avg_unit_width_and_x_offset() {
+ this.avg_unit_width = this.width/(this.x.values.length - 1);
+ this.x_offset = 0;
+ }
+
+ calc_min_tops() {
+ this.y_min_tops = new Array(this.x_axis_values.length).fill(9999);
+ this.y.map(d => {
+ d.y_tops.map( (y_top, i) => {
+ if(y_top < this.y_min_tops[i]) {
+ this.y_min_tops[i] = y_top;
+ }
+ });
+ });
+ }
+
+ setup_components() {
+ this.y_axis_group = this.snap.g().attr({ class: "y axis" });
+ this.x_axis_group = this.snap.g().attr({ class: "x axis" });
+ this.data_units = this.snap.g().attr({ class: "graph-data-points" });
+ this.specific_y_lines = this.snap.g().attr({ class: "specific axis" });
+ }
+
+ make_graph_components() {
+ this.make_y_axis();
+ this.make_x_axis();
+
+ this.y.map((d, i) => {
+ this.make_units(d.y_tops, d.color, i);
+ this.make_path(d);
+ });
+
+ if(this.specific_values.length > 0) {
+ this.show_specific_values();
+ }
+ this.setup_group();
+ }
+
+ setup_group() {
+ this.snap.g(
+ this.y_axis_group,
+ this.x_axis_group,
+ this.data_units,
+ this.specific_y_lines
+ ).attr({
+ transform: `translate(${this.translate_x}, ${this.translate_y})`
+ });
+ }
+
+ // make HORIZONTAL lines for y values
+ make_y_axis() {
+ let width, text_end_at = -9, label_class = '', start_at = 0;
+ if(this.y_axis_mode === 'span') { // long spanning lines
+ width = this.width + 6;
+ start_at = -6;
+ } else if(this.y_axis_mode === 'tick'){ // short label lines
+ width = -6;
+ label_class = 'y-axis-label';
+ }
+
+ this.y_axis_values.map((point) => {
+ this.y_axis_group.add(this.snap.g(
+ this.snap.line(start_at, 0, width, 0),
+ this.snap.text(text_end_at, 0, point+"").attr({
+ dy: ".32em",
+ class: "y-value-text"
+ })
+ ).attr({
+ class: `tick ${label_class}`,
+ transform: `translate(0, ${this.height - point * this.multiplier })`
+ }));
+ });
+ }
+
+ // make VERTICAL lines for x values
+ make_x_axis() {
+ let start_at, height, text_start_at, label_class = '';
+ if(this.x_axis_mode === 'span') { // long spanning lines
+ start_at = -7;
+ height = this.height + 15;
+ text_start_at = this.height + 25;
+ } else if(this.x_axis_mode === 'tick'){ // short label lines
+ start_at = this.height;
+ height = 6;
+ text_start_at = 9;
+ label_class = 'x-axis-label';
+ }
+
+ this.x_axis_group.attr({
+ transform: `translate(0,${start_at})`
+ });
+ this.x.values.map((point, i) => {
+ this.x_axis_group.add(this.snap.g(
+ this.snap.line(0, 0, 0, height),
+ this.snap.text(0, text_start_at, point).attr({
+ dy: ".71em",
+ class: "x-value-text"
+ })
+ ).attr({
+ class: `tick ${label_class}`,
+ transform: `translate(${ this.x_axis_values[i] }, 0)`
+ }));
+ });
+ }
+
+ make_units(y_values, color, dataset_index) {
+ let d = this.unit_args;
+ y_values.map((y, i) => {
+ let data_unit = this.draw[d.type](this.x_axis_values[i],
+ y, d.args, color, dataset_index);
+ this.data_units.add(data_unit);
+ this.y[dataset_index].data_units.push(data_unit);
+ });
+ }
+
+ make_path() { }
+
+ make_tooltip() {
+ this.tip = $(`
`).attr({
+ style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;`
+ }).appendTo(this.$graphics);
+
+ this.tip_title = this.tip.find('.title');
+ this.tip_data_point_list = this.tip.find('.data-point-list');
+
+ this.bind_tooltip();
+ }
+
+ bind_tooltip() {
+ this.$graphics.on('mousemove', (e) => {
+ let offset = $(this.$graphics).offset();
+ var relX = e.pageX - offset.left - this.translate_x;
+ var relY = e.pageY - offset.top - this.translate_y;
+
+ if(relY < this.height) {
+ for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) {
+ let x_val = this.x_axis_values[i];
+ if(relX > x_val - this.avg_unit_width/2) {
+ let x = x_val - this.tip.width()/2 + this.translate_x;
+ let y = this.y_min_tops[i] - this.tip.height() + this.translate_y;
+
+ this.fill_tooltip(i);
+
+ this.tip.attr({
+ style: `top: ${y}px; left: ${x-0.5}px; opacity: 1; pointer-events: none;`
+ });
+ break;
+ }
+ }
+ } else {
+ this.tip.attr({
+ style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;`
+ });
+ }
+ });
+
+ this.$graphics.on('mouseleave', () => {
+ this.tip.attr({
+ style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;`
+ });
+ });
+ }
+
+ fill_tooltip(i) {
+ this.tip_title.html(this.x.formatted && this.x.formatted.length>0
+ ? this.x.formatted[i] : this.x.values[i]);
+ this.tip_data_point_list.empty();
+ this.y.map(y_set => {
+ let $li = $(`
+
+ ${y_set.formatted ? y_set.formatted[i] : y_set.values[i]}
+
+ ${y_set.title ? y_set.title : '' }
+ `).addClass(`border-top ${y_set.color}`);
+ this.tip_data_point_list.append($li);
+ });
+ }
+
+ show_specific_values() {
+ this.specific_values.map(d => {
+ this.specific_y_lines.add(this.snap.g(
+ this.snap.line(0, 0, this.width, 0).attr({
+ class: d.line_type === "dashed" ? "graph-dashed": ""
+ }),
+ this.snap.text(this.width + 5, 0, d.name.toUpperCase()).attr({
+ dy: ".32em",
+ class: "specific-value",
+ })
+ ).attr({
+ class: "tick",
+ transform: `translate(0, ${this.height - d.value * this.multiplier })`
+ }));
+ });
+ }
+
+ show_summary() { }
+
+ show_custom_summary() {
+ this.summary.map(d => {
+ this.$stats_container.append($(`
+ ${d.name}: ${d.value}
+
`));
+ });
+ }
+
+ change_values(new_y) {
+ let u = this.unit_args;
+ this.y.map((d, i) => {
+ let new_d = new_y[i];
+ new_d.y_tops = new_d.values.map(val => this.height - val * this.multiplier);
+
+ // below is equal to this.y[i].data_units..
+ d.data_units.map((unit, j) => {
+ let current_y_top = d.y_tops[j];
+ let current_height = this.height - current_y_top;
+
+ let new_y_top = new_d.y_tops[j];
+ let new_height = current_height - (new_y_top - current_y_top);
+
+ this.animate[u.type](unit, new_y_top, {new_height: new_height});
+ });
+ });
+
+ // Replace values and formatted and tops
+ this.y.map((d, i) => {
+ let new_d = new_y[i];
+ [d.values, d.formatted, d.y_tops] = [new_d.values, new_d.formatted, new_d.y_tops];
+ });
+
+ this.calc_min_tops();
+
+ // create new x,y pair string and animate path
+ if(this.y[0].path) {
+ new_y.map((e, i) => {
+ let new_points_list = e.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y));
+ let new_path_str = "M"+new_points_list.join("L");
+ this.y[i].path.animate({d:new_path_str}, 300, mina.easein);
+ });
+ }
+ }
+
+ // Helpers
+ get_strwidth(string) {
+ return string.length * 8;
+ }
+
+ get_upper_limit_and_parts(array) {
+ let max_val = parseInt(Math.max(...array));
+ if((max_val+"").length <= 1) {
+ return [10, 5];
+ } else {
+ let multiplier = Math.pow(10, ((max_val+"").length - 1));
+ let significant = Math.ceil(max_val/multiplier);
+ if(significant % 2 !== 0) significant++;
+ let parts = (significant < 5) ? significant : significant/2;
+ return [significant * multiplier, parts];
+ }
+ }
+
+ get_y_axis_values(upper_limit, parts) {
+ let y_axis = [];
+ for(var i = 0; i <= parts; i++){
+ y_axis.push(upper_limit / parts * i);
+ }
+ return y_axis;
+ }
+
+ // Objects
+ setup_utils() {
+ this.draw = {
+ 'bar': (x, y, args, color, index) => {
+ let total_width = this.avg_unit_width - args.space_width;
+ let start_x = x - total_width/2;
+
+ let width = total_width / args.no_of_datasets;
+ let current_x = start_x + width * index;
+ return this.snap.rect(current_x, y, width, this.height - y).attr({
+ class: `bar mini fill ${color}`
+ });
+ },
+ 'dot': (x, y, args, color) => {
+ return this.snap.circle(x, y, args.radius).attr({
+ class: `fill ${color}`
+ });
+ }
+ };
+
+ this.animate = {
+ 'bar': (bar, new_y, args) => {
+ bar.animate({height: args.new_height, y: new_y}, 300, mina.easein);
+ },
+ 'dot': (dot, new_y) => {
+ dot.animate({cy: new_y}, 300, mina.easein);
+ }
+ };
+ }
+};
+
+frappe.ui.BarGraph = class BarGraph extends frappe.ui.Graph {
+ constructor(args = {}) {
+ super(args);
+ }
+
+ setup_values() {
+ var me = this;
+ super.setup_values();
+ this.x_offset = this.avg_unit_width;
+ this.y_axis_mode = 'span';
+ this.x_axis_mode = 'tick';
+ this.unit_args = {
+ type: 'bar',
+ args: {
+ space_width: this.y.length > 1 ?
+ me.avg_unit_width/2 : me.avg_unit_width/8,
+ no_of_datasets: this.y.length
+ }
+ };
+ }
+
+ set_avg_unit_width_and_x_offset() {
+ this.avg_unit_width = this.width/(this.x.values.length + 1);
+ this.x_offset = this.avg_unit_width;
+ }
+};
+
+frappe.ui.LineGraph = class LineGraph extends frappe.ui.Graph {
+ constructor(args = {}) {
+ super(args);
+ }
+
+ setup_values() {
+ super.setup_values();
+ this.y_axis_mode = 'tick';
+ this.x_axis_mode = 'span';
+ this.unit_args = {
+ type: 'dot',
+ args: { radius: 4 }
+ };
+ }
+
+ make_path(d) {
+ let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y));
+ let path_str = "M"+points_list.join("L");
+ d.path = this.snap.path(path_str).attr({class: `stroke ${d.color}`});
+ this.data_units.prepend(d.path);
+ }
+};
+
+frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph {
+ constructor(args = {}) {
+ super(args);
+ }
+
+ make_graph_area() {
+ this.$graphics.addClass('graph-focus-margin');
+ this.$stats_container.addClass('graph-focus-margin').attr({
+ style: `padding-top: 0px; margin-bottom: 30px;`
+ });
+ this.$div = $(`
`);
+ this.$chart = this.$div.find('.progress-chart');
+ return this.$div;
+ }
+
+ setup_values() {
+ this.x.totals = this.x.values.map((d, i) => {
+ let total = 0;
+ this.y.map(e => {
+ total += e.values[i];
+ });
+ return total;
+ });
+
+ // Calculate x unit distances for tooltips
+ }
+
+ setup_utils() { }
+ setup_components() {
+ this.$percentage_bar = $(`
+
`).appendTo(this.$chart);
+ }
+
+ make_graph_components() {
+ let grand_total = this.x.totals.reduce((a, b) => a + b, 0);
+ this.x.units = [];
+ this.x.totals.map((total, i) => {
+ let $part = $(`
`);
+ this.x.units.push($part);
+ this.$percentage_bar.append($part);
+ });
+ }
+
+ make_tooltip() { }
+
+ show_summary() {
+ let values = this.x.formatted.length > 0 ? this.x.formatted : this.x.values;
+ this.x.totals.map((d, i) => {
+ this.$stats_container.append($(`
+
+ ${values[i]}:
+ ${d}
+
+
`));
+ });
+ }
+};
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index 3162cd5db1..b3762fea6f 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -43,7 +43,7 @@ frappe.views.QueryReport = Class.extend({
this.wrapper = $("
").appendTo(this.page.main);
$('
\
\
-
\
+
\
\
\
\
diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less
index 69767fddf4..12860bc07d 100644
--- a/frappe/public/less/form.less
+++ b/frappe/public/less/form.less
@@ -708,10 +708,10 @@ select.form-control {
}
.password-strength-indicator {
- float: right;
- padding: 15px;
- margin-top: -41px;
- margin-right: -7px;
+ float: right;
+ padding: 15px;
+ margin-top: -41px;
+ margin-right: -7px;
}
.password-strength-message {
@@ -856,7 +856,6 @@ select.form-control {
}
/* goals */
-
.goals-page-container {
background-color: #fafbfc;
padding-top: 1px;
@@ -870,106 +869,6 @@ select.form-control {
}
}
-.graph-container {
- .graphics {
- margin-top: 10px;
- padding: 10px 0px;
- }
-
- .stats-group {
- display: flex;
- justify-content: space-around;
- flex: 1;
- }
-
- .stats-container {
- display: flex;
- justify-content: space-around;
-
- .stats {
- padding-bottom: 15px;
- }
-
- .stats-title {
- color: #8D99A6;
- }
- .stats-value {
- font-size: 20px;
- font-weight: 300;
- }
- .stats-description {
- font-size: 12px;
- color: #8D99A6;
- }
- .graph-data .stats-value {
- color: #98d85b;
- }
- }
-}
-
-.bar-graph, .line-graph {
-
- .axis {
- font-size: 10px;
- fill: #6a737d;
-
- line {
- stroke: rgba(27,31,35,0.1);
- }
- }
-}
-
-.data-points {
- circle {
- fill: #28a745;
- stroke: #fff;
- stroke-width: 2;
- }
-
- g.mini {
- fill: #98d85b;
- }
-
- path {
- fill: none;
- stroke: #28a745;
- stroke-opacity: 1;
- stroke-width: 2px;
- }
-}
-
-.line-graph {
- .path {
- fill: none;
- stroke: #28a745;
- stroke-opacity: 1;
- stroke-width: 2px;
- }
-}
-
-line.dashed {
- stroke-dasharray: 5,3;
-}
-
-.tick {
- &.x-axis-label {
- display: block;
- }
-
- .specific-value {
- text-anchor: start;
- }
-
- .y-value-text {
- text-anchor: end;
- }
-
- .x-value-text {
- text-anchor: middle;
- }
-}
-
-
body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
height: 80px !important;
}
diff --git a/frappe/public/less/graphs.less b/frappe/public/less/graphs.less
new file mode 100644
index 0000000000..92f8c2b1e1
--- /dev/null
+++ b/frappe/public/less/graphs.less
@@ -0,0 +1,319 @@
+
+/* graphs */
+.graph-container {
+ .graph-focus-margin {
+ margin: 0px 5%;
+ }
+
+ .graph-graphics {
+ margin-top: 10px;
+ padding: 10px 0px;
+ position: relative;
+ }
+
+ .graph-stats-group {
+ display: flex;
+ justify-content: space-around;
+ flex: 1;
+ }
+
+ .graph-stats-container {
+ display: flex;
+ justify-content: space-around;
+ padding-top: 10px;
+
+ .stats {
+ padding-bottom: 15px;
+ }
+
+ // Custom (impactified) stats style
+ .stats-title {
+ color: #8D99A6;
+ }
+ .stats-value {
+ font-size: 20px;
+ font-weight: 300;
+ }
+ .stats-description {
+ font-size: 12px;
+ color: #8D99A6;
+ }
+ .graph-data .stats-value {
+ color: #98d85b;
+ }
+ }
+
+ .bar-graph, .line-graph {
+
+ // baselines
+ .axis {
+ font-size: 10px;
+ fill: #6a737d;
+
+ line {
+ stroke: rgba(27,31,35,0.1);
+ }
+ }
+ }
+
+ .percentage-graph {
+ margin-top: 35px;
+
+ .progress {
+ margin-bottom: 0px;
+ }
+ }
+
+ .graph-data-points {
+ circle {
+ // fill: #28a745;
+ stroke: #fff;
+ stroke-width: 2;
+ }
+
+ g.mini {
+ // fill: #98d85b;
+ }
+
+ path {
+ fill: none;
+ // stroke: #28a745;
+ stroke-opacity: 1;
+ stroke-width: 2px;
+ }
+ }
+
+ line.graph-dashed {
+ stroke-dasharray: 5,3;
+ }
+
+ .tick {
+ &.x-axis-label {
+ display: block;
+ }
+
+ .specific-value {
+ text-anchor: start;
+ }
+
+ .y-value-text {
+ text-anchor: end;
+ }
+
+ .x-value-text {
+ text-anchor: middle;
+ }
+ }
+
+ .graph-svg-tip {
+ position: absolute;
+ z-index: 99999;
+ padding: 10px;
+ font-size: 12px;
+ color: #959da5;
+ text-align: center;
+ background: rgba(0,0,0,0.8);
+ border-radius: 3px;
+
+ &.comparison {
+ padding: 0;
+ text-align: left;
+ pointer-events: none;
+
+ .title {
+ display: block;
+ padding: 10px;
+ margin: 0;
+ font-weight: 600;
+ line-height: 1;
+ pointer-events: none;
+ }
+
+ ul {
+ margin: 0;
+ white-space: nowrap;
+ list-style: none;
+ }
+
+ li {
+ display: inline-block;
+ padding: 5px 10px;
+ }
+ }
+
+ ul, ol {
+ padding-left: 0;
+ display: flex;
+ }
+
+ ul.data-point-list li {
+ min-width: 90px;
+ flex: 1;
+ }
+
+ strong {
+ color: #dfe2e5;
+ }
+
+ &::after {
+ position: absolute;
+ bottom: -10px;
+ left: 50%;
+ width: 5px;
+ height: 5px;
+ margin: 0 0 0 -5px;
+ content: " ";
+ border: 5px solid transparent;
+ border-top-color: rgba(0,0,0,0.8);
+ }
+ }
+
+ .stroke.grey {
+ stroke: #F0F4F7;
+ }
+ .stroke.blue {
+ stroke: #5e64ff;
+ }
+ .stroke.red {
+ stroke: #ff5858;
+ }
+ .stroke.light-green {
+ stroke: #98d85b;
+ }
+ .stroke.green {
+ stroke: #28a745;
+ }
+ .stroke.orange {
+ stroke: #ffa00a;
+ }
+ .stroke.purple {
+ stroke: #743ee2;
+ }
+ .stroke.darkgrey {
+ stroke: #b8c2cc;
+ }
+ .stroke.black {
+ stroke: #36414C;
+ }
+ .stroke.yellow {
+ stroke: #FEEF72;
+ }
+ .stroke.light-blue {
+ stroke: #7CD6FD;
+ }
+ .stroke.lightblue {
+ stroke: #7CD6FD;
+ }
+
+ .fill.grey {
+ fill: #F0F4F7;
+ }
+ .fill.blue {
+ fill: #5e64ff;
+ }
+ .fill.red {
+ fill: #ff5858;
+ }
+ .fill.light-green {
+ fill: #98d85b;
+ }
+ .fill.green {
+ fill: #28a745;
+ }
+ .fill.orange {
+ fill: #ffa00a;
+ }
+ .fill.purple {
+ fill: #743ee2;
+ }
+ .fill.darkgrey {
+ fill: #b8c2cc;
+ }
+ .fill.black {
+ fill: #36414C;
+ }
+ .fill.yellow {
+ fill: #FEEF72;
+ }
+ .fill.light-blue {
+ fill: #7CD6FD;
+ }
+ .fill.lightblue {
+ fill: #7CD6FD;
+ }
+
+ .background.grey {
+ background: #F0F4F7;
+ }
+ .background.blue {
+ background: #5e64ff;
+ }
+ .background.red {
+ background: #ff5858;
+ }
+ .background.light-green {
+ background: #98d85b;
+ }
+ .background.green {
+ background: #28a745;
+ }
+ .background.orange {
+ background: #ffa00a;
+ }
+ .background.purple {
+ background: #743ee2;
+ }
+ .background.darkgrey {
+ background: #b8c2cc;
+ }
+ .background.black {
+ background: #36414C;
+ }
+ .background.yellow {
+ background: #FEEF72;
+ }
+ .background.light-blue {
+ background: #7CD6FD;
+ }
+ .background.lightblue {
+ background: #7CD6FD;
+ }
+
+ .border-top.grey {
+ border-top: 3px solid #F0F4F7;
+ }
+ .border-top.blue {
+ border-top: 3px solid #5e64ff;
+ }
+ .border-top.red {
+ border-top: 3px solid #ff5858;
+ }
+ .border-top.light-green {
+ border-top: 3px solid #98d85b;
+ }
+ .border-top.green {
+ border-top: 3px solid #28a745;
+ }
+ .border-top.orange {
+ border-top: 3px solid #ffa00a;
+ }
+ .border-top.purple {
+ border-top: 3px solid #743ee2;
+ }
+ .border-top.darkgrey {
+ border-top: 3px solid #b8c2cc;
+ }
+ .border-top.black {
+ border-top: 3px solid #36414C;
+ }
+ .border-top.yellow {
+ border-top: 3px solid #FEEF72;
+ }
+ .border-top.light-blue {
+ border-top: 3px solid #7CD6FD;
+ }
+ .border-top.lightblue {
+ border-top: 3px solid #7CD6FD;
+ }
+
+}
diff --git a/frappe/tests/test_goal.py b/frappe/tests/test_goal.py
index 5fe490ab56..6e94858785 100644
--- a/frappe/tests/test_goal.py
+++ b/frappe/tests/test_goal.py
@@ -31,4 +31,4 @@ class TestGoal(unittest.TestCase):
frappe.db.set_value('Event', docname, 'description', 1)
data = get_monthly_goal_graph_data('Test', 'Event', docname, 'description', 'description', 'description',
'Event', '', 'description', 'creation', 'starts_on = "2014-01-01"', 'count')
- self.assertEquals(float(data['y_values'][-1]), 1)
+ self.assertEquals(float(data['y'][0]['values'][-1]), 1)
diff --git a/frappe/utils/goal.py b/frappe/utils/goal.py
index bf1b9c345e..015c225ae4 100644
--- a/frappe/utils/goal.py
+++ b/frappe/utils/goal.py
@@ -76,15 +76,21 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_
month_to_value_dict[current_month_year] = current_month_value
months = []
+ months_formatted = []
values = []
+ values_formatted = []
for i in xrange(0, 12):
month_value = formatdate(add_months(today(), -i), "MM-yyyy")
month_word = getdate(month_value).strftime('%b')
+ month_year = getdate(month_value).strftime('%B') + ', ' + getdate(month_value).strftime('%Y')
months.insert(0, month_word)
+ months_formatted.insert(0, month_year)
if month_value in month_to_value_dict:
- values.insert(0, month_to_value_dict[month_value])
+ val = month_to_value_dict[month_value]
else:
- values.insert(0, 0)
+ val = 0
+ values.insert(0, val)
+ values_formatted.insert(0, format_value(val, meta.get_field(goal_total_field), doc))
specific_values = []
summary_values = [
@@ -119,10 +125,20 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_
data = {
'title': title,
# 'subtitle':
- 'y_values': values,
- 'x_points': months,
+ 'y': [
+ {
+ 'color': 'green',
+ 'values': values,
+ 'formatted': values_formatted
+ }
+ ],
+ 'x': {
+ 'values': months,
+ 'formatted': months_formatted
+ },
+
'specific_values': specific_values,
- 'summary_values': summary_values
+ 'summary': summary_values
}
return data