diff --git a/frappe/desk/form/save.py b/frappe/desk/form/save.py
index b2f2ea4482..1e07f10ba7 100644
--- a/frappe/desk/form/save.py
+++ b/frappe/desk/form/save.py
@@ -51,7 +51,7 @@ def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_stat
raise
def send_updated_docs(doc):
- from load import get_docinfo
+ from .load import get_docinfo
get_docinfo(doc)
d = doc.as_dict()
diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html
index 4e2127a090..6912d69ed0 100644
--- a/frappe/public/js/frappe/list/list_sidebar.html
+++ b/frappe/public/js/frappe/list/list_sidebar.html
@@ -52,7 +52,16 @@
{{ __("Help") }}
{% } %}
+
+
\ No newline at end of file
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 3544f5f07c..7c9e738a94 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -65,7 +65,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.get_fields_in_list_view(),
[this.meta.title_field, this.meta.image_field],
(this.settings.add_fields || []),
- this.meta.track_seen ? '_seen' : null
+ this.meta.track_seen ? '_seen' : null,
+ 'enabled',
+ 'disabled'
);
fields.forEach(f => this._add_field(f));
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index 5ae286b838..fa13397614 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -32,6 +32,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
setup_view() {
this.setup_columns();
+ this.bind_charts_button();
}
setup_result_area() {
@@ -223,87 +224,138 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
});
}
+ bind_charts_button() {
+ this.list_sidebar.sidebar.find('.charts-menu').removeClass('hide');
+ this.list_sidebar.sidebar.on('click', '.toggle-charts', (e) => {
+ e.preventDefault();
+ this.toggle_charts();
+ });
+ this.list_sidebar.sidebar.on('click', '.configure-charts', (e) => {
+ e.preventDefault();
+ this.get_chart_data().then(() => this.refresh_charts());
+ });
+ this.refresh_charts_sidebar_button();
+ }
+
+ refresh_charts_sidebar_button() {
+ // show configure charts button if charts is shown
+ const $configure_btn = this.list_sidebar.sidebar.find('.configure-charts');
+ const charts_visible = this.chart && !this.$charts_wrapper.hasClass('hidden');
+
+ if (charts_visible) {
+ $configure_btn.removeClass('hidden');
+ } else {
+ $configure_btn.addClass('hidden');
+ }
+ }
+
toggle_charts() {
if (!this.chart) {
this.setup_charts();
return;
}
this.$charts_wrapper.toggleClass('hidden');
- this.chart.refresh();
+
+ if (!this.$charts_wrapper.hasClass('hidden')) {
+ this.chart.refresh();
+ }
+
+ this.refresh_charts_sidebar_button();
}
setup_charts() {
this.get_chart_data()
.then(args => {
- this.chart_args = args;
let data = {
- labels: this.data.map(d => d.name),
- datasets: [ args.dataset ]
- };
- const df = frappe.meta.get_docfield('Task', args.field);
- const get_doc = (value) => {
- return this.data.find(d => {
- return d[args.field] === value;
- });
+ labels: args.labels,
+ datasets: args.datasets
};
+ this.last_chart_type = args.chart_type;
+
+ const get_df = (field) => frappe.meta.get_docfield(this.doctype, field);
+ const get_doc = (value, field) => this.data.find(d => d[field] === value);
+
this.chart = new Chart({
parent: this.$charts_wrapper[0],
title: __("{0} Chart", [this.doctype]),
data: data,
- type: 'bar', // or 'line', 'scatter', 'pie', 'percentage'
+ type: args.chart_type, // 'bar', 'line', 'scatter', 'pie', 'percentage'
height: 150,
- colors: ['violet', 'blue'],
+ colors: ['violet', 'light-blue', 'orange', 'red'],
- format_tooltip_x: d => (d + '').toUpperCase(),
- format_tooltip_y: value => frappe.format(value, df, { always_show_decimals: true, inline: true }, get_doc(value))
+ format_tooltip_x: value => value.doc.name,
+ format_tooltip_y:
+ value => frappe.format(value, get_df(value.field), { always_show_decimals: true, inline: true }, get_doc(value.doc))
});
+
+ this.refresh_charts_sidebar_button();
});
}
refresh_charts() {
if (!this.chart) return;
- const new_dataset = {
- label: this.chart_args.field,
- values: this.data.map(d => d[this.chart_args.field])
- };
- const labels = this.data.map(d => d.name);
- this.chart.update_values([new_dataset], labels);
+ const { x_field, y_fields, chart_type } = this.chart_args;
+ const args = this.get_chart_args(x_field, y_fields, chart_type);
+ this.chart.update_values(args.datasets, args.labels);
+ this.chart.refresh();
+
+ if (args.chart_type !== this.last_chart_type) {
+ this.chart.get_different_chart(args.chart_type);
+ }
}
get_chart_data() {
return new Promise(resolve => {
- // const x_fields = [];
const cur_list_fields = this.fields.map(f => f[0]);
+ const x_fields = this.meta.fields.filter(df =>
+ !df.hidden && cur_list_fields.includes(df.fieldname)
+ ).map(df => df.fieldname);
const y_fields = this.meta.fields.filter(df =>
!df.hidden && frappe.model.is_numeric_field(df)
&& cur_list_fields.includes(df.fieldname)
).map(df => df.fieldname);
+ const defaults = this.chart_args || {};
+
const dialog = new frappe.ui.Dialog({
title: __('Configure Chart'),
fields: [
{
- label: __('Y Axis Field'),
+ label: __('X Axis Field'),
fieldtype: 'Autocomplete',
- fieldname: 'y_axis',
+ fieldname: 'x_axis',
+ options: x_fields,
+ default: defaults.x_field
+ },
+ {
+ label: __('Y Axis Fields'),
+ fieldtype: 'MultiSelect',
+ fieldname: 'y_axes',
options: y_fields,
- description: __('Showing only Numeric fields from Report')
+ description: __('Showing only Numeric fields from Report'),
+ default: defaults.y_fields
+ },
+ {
+ label: __('Chart Type'),
+ fieldtype: 'Select',
+ options: ['Bar', 'Line', 'Scatter', 'Pie', 'Percentage'],
+ fieldname: 'chart_type',
+ default: toTitle(defaults.chart_type || 'Bar')
}
],
- primary_action: ({ y_axis }) => {
- if (y_fields.includes(y_axis)) {
- const data = this.data.map(d => d[y_axis]);
- const args = {
- field: y_axis,
- dataset: {
- label: y_axis,
- values: data
- }
- }
- resolve(args);
- dialog.hide();
- }
+ primary_action: ({ x_axis, y_axes, chart_type }) => {
+ y_axes = y_axes.split(',').map(d => d.trim()).filter(Boolean);
+
+ if (!(
+ y_axes.every(d => y_fields.includes(d))
+ && x_fields.includes(x_axis)
+ )) return;
+
+ const args = this.get_chart_args(x_axis, y_axes, chart_type);
+ this.chart_args = args;
+ resolve(args);
+ dialog.hide();
}
});
@@ -311,6 +363,41 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
});
}
+ get_chart_args(x_axis, y_axes, chart_type) {
+ const labels = this.data.map(d => {
+ // HACK: labels need strings,
+ // so we return objects that
+ // look like strings and also
+ // monkey patch the doc
+ // javascript is awesome
+ return {
+ doc: d,
+ toString() {
+ return d[x_axis];
+ },
+ slice: String.prototype.slice
+ };
+ });
+
+ return {
+ chart_type: chart_type.toLowerCase(),
+ x_field: x_axis,
+ y_fields: y_axes,
+ labels: labels,
+ datasets: y_axes.map(y_axis => ({
+ title: frappe.meta.get_docfield(this.doctype, y_axis).label,
+ values: this.data.map(d => ({
+ doc: d,
+ field: y_axis,
+ toString() {
+ return d[y_axis];
+ },
+ slice: String.prototype.slice
+ }))
+ }))
+ };
+ }
+
get_editing_object(colIndex, rowIndex, value, parent) {
const control = this.render_editing_input(colIndex, value, parent);
if (!control) return false;
@@ -412,10 +499,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
if (this.report_name && this.report_doc.json.fields) {
this.fields = this.report_doc.json.fields;
return;
- }
-
- // get from user_settings
- else if (this.view_user_settings.fields) {
+ } else if (this.view_user_settings.fields) {
+ // get from user_settings
this.fields = this.view_user_settings.fields;
return;
}