From 02f0c0a4b45539f3ca1cf4a93f45df302c6f9c7b Mon Sep 17 00:00:00 2001 From: pratu16x7 Date: Sat, 21 Oct 2017 21:55:00 +0530 Subject: [PATCH] [add] percentage chart --- docs/assets/js/index.js | 17 +++-- src/charts.css | 137 ++++++++++++++++++++++++++++++++++---- src/charts.js | 142 ++++++++++++++++++++++++---------------- 3 files changed, 222 insertions(+), 74 deletions(-) diff --git a/docs/assets/js/index.js b/docs/assets/js/index.js index c83c391..116e1d8 100755 --- a/docs/assets/js/index.js +++ b/docs/assets/js/index.js @@ -1,16 +1,18 @@ let bar_data = { - "labels": ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"], + // "labels": ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"], + "labels": ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"], "datasets": [{ "color": "orange", - "values": [50804, 10000, 20000, -61500, 82936.88, 24010, 4000, 6000, 25840, 50804.82, 116820, 6000], + // "values": [50804, 10000, 20000, -61500, 82936.88, 24010, 4000, 6000, 25840, 50804.82, 116820, 6000], + "values": [50804, 10000, 20000, 61500, 82936.88, 24010, 40000, 60000, 25840, 50804.82, 116820], // "values": [-108048, 0, 0, -101500, -50000.88, 24010, 0, 0, 25840, 108048.82, 51682, 0], "formatted": ["₹ 0.00", "₹ 0.00", "₹ 0.00", "₹ 61,500.00", "₹ 82,936.88", "₹ 24,010.00", "₹ 0.00", "₹ 0.00", "₹ 25,840.00", "₹ 5,08,048.82", "₹ 1,16,820.00", "₹ 0.00"], } , { "color": "blue", - // "values": [108048, 0, 0, 101500, 50000.88, 24010, 0, 0, 25840, 108048.82, 51682, 0], - "values": [-108048, 0, 0, -101500, -50000.88, 24010, 0, 0, 25840, 108048.82, 51682, 0], + // "values": [-108048, 0, 0, -101500, -50000.88, 24010, 0, 0, 25840, 108048.82, 51682, 0], + "values": [108048, 0, 0, 101500, 50000.88, 24010, 0, 0, 25840, 108048.82, 51682], "formatted": ["₹ 0.00", "₹ 0.00", "₹ 0.00", "₹ 61,500.00", "₹ 82,936.88", "₹ 24,010.00", "₹ 0.00", "₹ 0.00", "₹ 25,840.00", "₹ 5,08,048.82", "₹ 1,16,820.00", "₹ 0.00"], } ] @@ -72,16 +74,17 @@ let line_chart = new frappe.chart.FrappeChart ({ data: line_data, type: 'line', height: 340, - region_fill: 1 + region_fill: 1, + y_axis_mode: 'tick' }) let bar_chart = new frappe.chart.FrappeChart ({ parent: "#charts-1", data: bar_data, - type: 'line', + type: 'percentage', height: 140, is_navigable: 1, - region_fill: 1 + // region_fill: 1 }) bar_chart.parent.addEventListener('data-select', (e) => { diff --git a/src/charts.css b/src/charts.css index e278aa2..76bfab2 100644 --- a/src/charts.css +++ b/src/charts.css @@ -153,7 +153,96 @@ border: 5px solid transparent; border-top-color: rgba(0, 0, 0, 0.8); } + +/*Indicators*/ +.indicator, +.indicator-right { + background: none; + font-size: 12px; + vertical-align: middle; + font-weight: bold; + color: #6c7680; +} +.indicator::before, +.indicator-right::after { + content: ''; + display: inline-block; + height: 8px; + width: 8px; + border-radius: 8px; +} +.indicator::before { + margin: 0 4px 0 0px; +} +.indicator-right::after { + margin: 0 0 0 4px; +} +.indicator.grey::before, +.indicator-right.grey::after { + background: #bdd3e6; +} +.indicator.light-grey::before, +.indicator-right.light-grey::after { + background: #F0F4F7; +} +.indicator.blue::before, +.indicator-right.blue::after { + background: #5e64ff; +} +.indicator.red::before, +.indicator-right.red::after { + background: #ff5858; +} +.indicator.green::before, +.indicator-right.green::after { + background: #28a745; +} +.indicator.light-green::before, +.indicator-right.light-green::after { + background: #98d85b; +} +.indicator.orange::before, +.indicator-right.orange::after { + background: #ffa00a; +} +.indicator.violet::before, +.indicator-right.violet::after { + background: #743ee2; +} +.indicator.darkgrey::before, +.indicator-right.darkgrey::after { + background: #b8c2cc; +} +.indicator.black::before, +.indicator-right.black::after { + background: #36414C; +} +.indicator.yellow::before, +.indicator-right.yellow::after { + background: #FEEF72; +} +.indicator.light-blue::before, +.indicator-right.light-blue::after { + background: #7CD6FD; +} +.indicator.light-blue::before, +.indicator-right.light-blue::after { + background: #7CD6FD; +} +.indicator.purple::before, +.indicator-right.purple::after { + background: #b554ff; +} +.indicator.magenta::before, +.indicator-right.magenta::after { + background: #ffa3ef; +} + +/*Svg properties colors*/ .stroke.grey { + stroke: #bdd3e6; +} +.stroke.light-grey { stroke: #F0F4F7; } .stroke.blue { @@ -171,7 +260,7 @@ .stroke.orange { stroke: #ffa00a; } -.stroke.purple { +.stroke.violet { stroke: #743ee2; } .stroke.darkgrey { @@ -186,10 +275,17 @@ .stroke.light-blue { stroke: #7CD6FD; } -.stroke.lightblue { - stroke: #7CD6FD; +.stroke.purple { + stroke: #b554ff; } +.stroke.magenta { + stroke: #ffa3ef; +} + .fill.grey { + fill: #bdd3e6; +} +.fill.light-grey { fill: #F0F4F7; } .fill.blue { @@ -207,7 +303,7 @@ .fill.orange { fill: #ffa00a; } -.fill.purple { +.fill.violet { fill: #743ee2; } .fill.darkgrey { @@ -222,10 +318,17 @@ .fill.light-blue { fill: #7CD6FD; } -.fill.lightblue { - fill: #7CD6FD; +.fill.purple { + fill: #b554ff; } +.fill.magenta { + fill: #ffa3ef; +} + .background.grey { + background: #bdd3e6; +} +.background.light-grey { background: #F0F4F7; } .background.blue { @@ -243,7 +346,7 @@ .background.orange { background: #ffa00a; } -.background.purple { +.background.violet { background: #743ee2; } .background.darkgrey { @@ -258,10 +361,17 @@ .background.light-blue { background: #7CD6FD; } -.background.lightblue { - background: #7CD6FD; +.background.purple{ + background: #b554ff; } +.background.magenta{ + background: #ffa3ef; +} + .border-top.grey { + border-top: 3px solid #bdd3e6; +} +.border-top.light-grey { border-top: 3px solid #F0F4F7; } .border-top.blue { @@ -279,7 +389,7 @@ .border-top.orange { border-top: 3px solid #ffa00a; } -.border-top.purple { +.border-top.violet { border-top: 3px solid #743ee2; } .border-top.darkgrey { @@ -294,6 +404,9 @@ .border-top.light-blue { border-top: 3px solid #7CD6FD; } -.border-top.lightblue { - border-top: 3px solid #7CD6FD; +.border-top.purple { + border-top: 3px solid #b554ff; +} +.border-top.magenta { + border-top: 3px solid #ffa3ef; } diff --git a/src/charts.js b/src/charts.js index b81aee4..032ff8b 100644 --- a/src/charts.js +++ b/src/charts.js @@ -9,7 +9,7 @@ // { // title: "Total", // color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange', -// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue' +// // 'violet', 'darkgrey', 'black', 'yellow', 'light-blue' // value: 80 // } // ] @@ -262,8 +262,8 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart { this.get_x_tooltip = this.format_lambdas.x_tooltip; this.get_y_tooltip = this.format_lambdas.y_tooltip; - this.colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen', - 'yellow', 'orange', 'red']; + this.colors = ['green', 'blue', 'violet', 'red', 'orange', + 'yellow', 'light-blue', 'light-green', 'purple', 'magenta']; this.zero_line = this.height; } @@ -487,14 +487,7 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart { bind_tooltip() { // TODO: could be in tooltip itself, as it is a given functionality for its parent this.chart_wrapper.addEventListener('mousemove', (e) => { - let rect = this.chart_wrapper.getBoundingClientRect(); - let offset = { - // https://stackoverflow.com/a/7436602/6495043 - // rect.top varies with scroll, so we add whatever has been - // scrolled to it to get absolute distance from actual page top - top: rect.top + (document.documentElement.scrollTop || document.body.scrollTop), - left: rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft) - } + let offset = $$.offset(this.chart_wrapper); let relX = e.pageX - offset.left - this.translate_x; let relY = e.pageY - offset.top - this.translate_y; @@ -672,7 +665,7 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart { } // Make both region parts even - if(pos_no_of_parts % 2 !== 0) pos_no_of_parts++; + if(pos_no_of_parts % 2 !== 0 && neg_no_of_parts > 0) pos_no_of_parts++; if(neg_no_of_parts % 2 !== 0) { // every increase in no_of_parts entails an increase in corresponding bound // except here, it happens implicitly after every calc_no_of_parts() call @@ -820,18 +813,18 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart { } frappe.chart.BarChart = class BarChart extends frappe.chart.AxisChart { - constructor() { - super(arguments[0]); + constructor(args) { + super(args); this.type = 'bar-graph'; + this.x_axis_mode = args.x_axis_mode || 'tick'; + this.y_axis_mode = args.y_axis_mode || 'span'; this.setup(); } setup_values() { 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: { @@ -892,14 +885,14 @@ frappe.chart.LineChart = class LineChart extends frappe.chart.AxisChart { this.type = 'line-graph'; this.region_fill = args.region_fill; + this.x_axis_mode = args.x_axis_mode || 'span'; + this.y_axis_mode = args.y_axis_mode || 'span'; this.setup(); } setup_values() { super.setup_values(); - this.y_axis_mode = 'span'; - this.x_axis_mode = 'span'; this.unit_args = { type: 'dot', args: { radius: 4 } @@ -957,14 +950,21 @@ frappe.chart.PercentageChart = class PercentageChart extends frappe.chart.Frappe constructor(args) { super(args); - this.x = this.data.labels; - this.y = this.data.datasets; - this.get_x_label = this.format_lambdas.x_label; this.get_y_label = this.format_lambdas.y_label; this.get_x_tooltip = this.format_lambdas.x_tooltip; this.get_y_tooltip = this.format_lambdas.y_tooltip; + this.max_slices = 10; + this.max_legend_points = 6; + + this.colors = args.colors; + + if(!this.colors || this.colors.length < this.data.labels.length) { + this.colors = ['light-blue', 'blue', 'violet', 'red', 'orange', + 'yellow', 'green', 'light-green', 'purple', 'magenta']; + } + this.setup(); } @@ -991,22 +991,6 @@ frappe.chart.PercentageChart = class PercentageChart extends frappe.chart.Frappe }); } - setup_values() { - this.x.totals = this.x.map((d, i) => { - let total = 0; - this.y.map(e => { - total += e.values[i]; - }); - return total; - }); - - if(!this.x.colors) { - this.x.colors = ['green', 'blue', 'purple', 'red', 'orange', - 'yellow', 'lightblue', 'lightgreen']; - } - } - - setup_utils() { } setup_components() { this.percentage_bar = $$.create('div', { className: 'progress', @@ -1014,46 +998,83 @@ frappe.chart.PercentageChart = class PercentageChart extends frappe.chart.Frappe }); } + setup_values() { + this.slice_totals = []; + let all_totals = this.data.labels.map((d, i) => { + let total = 0; + this.data.datasets.map(e => { + total += e.values[i]; + }); + return [total, d]; + }).filter(d => { return d[0] > 0; }); // keep only positive results + + let totals = all_totals; + + if(all_totals.length > this.max_slices) { + all_totals.sort((a, b) => { return b[0] - a[0]; }); + + totals = all_totals.slice(0, this.max_slices-1); + let others = all_totals.slice(this.max_slices-1); + + let sum_of_others = 0; + others.map(d => {sum_of_others += d[0]}); + + totals.push([sum_of_others, 'Rest']); + + this.colors[this.max_slices-1] = 'grey'; + } + + this.labels = []; + totals.map(d => { + this.slice_totals.push(d[0]); + this.labels.push(d[1]); + }); + + this.legend_totals = this.slice_totals.slice(0, this.max_legend_points); + } + + setup_utils() { } + make_graph_components() { - this.grand_total = this.x.totals.reduce((a, b) => a + b, 0); - this.x.units = []; - this.x.totals.map((total, i) => { - let part = $$.create('div', { - className: `progress-bar background ${this.x.colors[i]}`, + this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0); + this.slices = []; + this.slice_totals.map((total, i) => { + let slice = $$.create('div', { + className: `progress-bar background ${this.colors[i]}`, style: `width: ${total*100/this.grand_total}%`, inside: this.percentage_bar }); - this.x.units.push(part); + this.slices.push(slice); }); } bind_tooltip() { - this.x.units.map((part, i) => { - part.addEventListener('mouseenter', () => { - let g_off = this.chart_wrapper.offset(), p_off = part.offset(); + this.slices.map((slice, i) => { + slice.addEventListener('mouseenter', () => { + let g_off = $$.offset(this.chart_wrapper), p_off = $$.offset(slice); - let x = p_off.left - g_off.left + part.offsetWidth/2; + let x = p_off.left - g_off.left + slice.offsetWidth/2; let y = p_off.top - g_off.top - 6; - let title = (this.x.formatted && this.x.formatted.length>0 - ? this.x.formatted[i] : this.x[i]) + ': '; - let percent = (this.x.totals[i]*100/this.grand_total).toFixed(1); + let title = (this.formatted_labels && this.formatted_labels.length>0 + ? this.formatted_labels[i] : this.labels[i]) + ': '; + let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1); - this.tip.set_values(x, y, title, percent); + this.tip.set_values(x, y, title, percent + "%"); this.tip.show_tip(); }); }); } show_summary() { - let x_values = this.x.formatted && this.x.formatted.length > 0 - ? this.x.formatted : this.x; - this.x.totals.map((d, i) => { + let x_values = this.formatted_labels && this.formatted_labels.length > 0 + ? this.formatted_labels : this.labels; + this.legend_totals.map((d, i) => { if(d) { let stats = $$.create('div', { className: 'stats', inside: this.stats_wrapper }); - stats.innerHTML = ` + stats.innerHTML = ` ${x_values[i]}: ${d} `; @@ -1614,6 +1635,17 @@ $$.animateSVG = (element, props, dur, easing_type="linear") => { return [anim_element, new_element]; } +$$.offset = function(element) { + let rect = element.getBoundingClientRect(); + return { + // https://stackoverflow.com/a/7436602/6495043 + // rect.top varies with scroll, so we add whatever has been + // scrolled to it to get absolute distance from actual page top + top: rect.top + (document.documentElement.scrollTop || document.body.scrollTop), + left: rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft) + } +}; + $$.bind = function(element, o) { if (element) { for (var event in o) {