Просмотр исходного кода

Merge pull request #4027 from pratu16x7/c3-to-graph

[charts] Heatmap
version-14
Rushabh Mehta 7 лет назад
committed by GitHub
Родитель
Сommit
c5837f28fe
13 измененных файлов: 892 добавлений и 4050 удалений
  1. +0
    -1
      .eslintrc
  2. +6
    -24
      frappe/desk/page/activity/activity.js
  3. +0
    -2
      frappe/public/build.json
  4. +0
    -140
      frappe/public/css/cal-heatmap.css
  5. +4
    -0
      frappe/public/css/form.css
  6. +69
    -71
      frappe/public/css/graphs.css
  7. +7
    -39
      frappe/public/js/frappe/form/dashboard.js
  8. +1
    -1
      frappe/public/js/frappe/form/templates/form_dashboard.html
  9. +595
    -91
      frappe/public/js/frappe/ui/graphs.js
  10. +1
    -0
      frappe/public/js/frappe/views/reports/query_report.js
  11. +0
    -3471
      frappe/public/js/lib/cal-heatmap.js
  12. +5
    -0
      frappe/public/less/form.less
  13. +204
    -210
      frappe/public/less/graphs.less

+ 0
- 1
.eslintrc Просмотреть файл

@@ -49,7 +49,6 @@
"moment": true, "moment": true,
"hljs": true, "hljs": true,
"Awesomplete": true, "Awesomplete": true,
"CalHeatMap": true,
"Sortable": true, "Sortable": true,
"Showdown": true, "Showdown": true,
"Taggle": true, "Taggle": true,


+ 6
- 24
frappe/desk/page/activity/activity.js Просмотреть файл

@@ -180,30 +180,12 @@ frappe.activity.render_heatmap = function(page) {
method: "frappe.desk.page.activity.activity.get_heatmap_data", method: "frappe.desk.page.activity.activity.get_heatmap_data",
callback: function(r) { callback: function(r) {
if(r.message) { if(r.message) {
var legend = [];
var max = Math.max.apply(this, $.map(r.message, function(v) { return v }));
var legend = [cint(max/5), cint(max*2/5), cint(max*3/5), cint(max*4/5)];
var heatmap = new CalHeatMap();
heatmap.init({
itemSelector: ".heatmap",
domain: "month",
subDomain: "day",
start: moment().subtract(1, 'year').add(1, 'month').toDate(),
cellSize: 9,
cellPadding: 2,
domainGutter: 2,
range: 12,
domainLabelFormat: function(date) {
return moment(date).format("MMM").toUpperCase();
},
displayLegend: false,
legend: legend,
tooltip: true,
subDomainTitleFormat: {
empty: "{date}",
filled: "{count} actions on {date}"
},
subDomainDateFormat: "%d-%b"
var heatmap = new frappe.ui.HeatMap({
parent: $(".heatmap"),
height: 100,
start: new Date(moment().subtract(1, 'year').toDate()),
count_label: "actions",
discrete_domains: 0
}); });


heatmap.update(r.message); heatmap.update(r.message);


+ 0
- 2
frappe/public/build.json Просмотреть файл

@@ -97,7 +97,6 @@
"public/css/bootstrap.css", "public/css/bootstrap.css",
"public/css/font-awesome.css", "public/css/font-awesome.css",
"public/css/octicons/octicons.css", "public/css/octicons/octicons.css",
"public/css/cal-heatmap.css",
"public/css/c3.min.css", "public/css/c3.min.css",
"public/css/desk.css", "public/css/desk.css",
"public/css/indicator.css", "public/css/indicator.css",
@@ -231,7 +230,6 @@
], ],
"js/d3.min.js": [ "js/d3.min.js": [
"public/js/lib/d3.min.js", "public/js/lib/d3.min.js",
"public/js/lib/cal-heatmap.js",
"public/js/lib/c3.min.js" "public/js/lib/c3.min.js"
], ],
"css/module.min.css": [ "css/module.min.css": [


+ 0
- 140
frappe/public/css/cal-heatmap.css Просмотреть файл

@@ -1,140 +0,0 @@
/* Cal-HeatMap CSS */

.cal-heatmap-container {
display: block;
}

.cal-heatmap-container .graph-label
{
fill: #999;
font-size: 10px
}

.cal-heatmap-container .graph, .cal-heatmap-container .graph-legend rect {
shape-rendering: crispedges
}

.cal-heatmap-container .graph-rect
{
fill: #ededed
}

.cal-heatmap-container .graph-subdomain-group rect:hover
{
stroke: #000;
stroke-width: 1px
}

.cal-heatmap-container .subdomain-text {
font-size: 8px;
fill: #999;
pointer-events: none
}

.cal-heatmap-container .hover_cursor:hover {
cursor: pointer
}

.cal-heatmap-container .qi {
background-color: #999;
fill: #999
}

/*
Remove comment to apply this style to date with value equal to 0
.q0
{
background-color: #fff;
fill: #fff;
stroke: #ededed
}
*/

.cal-heatmap-container .q1
{
background-color: #dae289;
fill: #dae289
}

.cal-heatmap-container .q2
{
background-color: #cedb9c;
fill: #9cc069
}

.cal-heatmap-container .q3
{
background-color: #b5cf6b;
fill: #669d45
}

.cal-heatmap-container .q4
{
background-color: #637939;
fill: #637939
}

.cal-heatmap-container .q5
{
background-color: #3b6427;
fill: #3b6427
}

.cal-heatmap-container rect.highlight
{
stroke:#444;
stroke-width:1
}

.cal-heatmap-container text.highlight
{
fill: #444
}

.cal-heatmap-container rect.now
{
stroke: red
}

.cal-heatmap-container text.now
{
fill: red;
font-weight: 800
}

.cal-heatmap-container .domain-background {
fill: none;
shape-rendering: crispedges
}

.ch-tooltip {
padding: 10px;
background: #222;
color: #bbb;
font-size: 12px;
line-height: 1.4;
width: 140px;
position: absolute;
z-index: 99999;
text-align: center;
border-radius: 2px;
box-shadow: 2px 2px 2px rgba(0,0,0,0.2);
display: none;
box-sizing: border-box;
}

.ch-tooltip::after{
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
content: "";
padding: 0;
display: block;
bottom: -6px;
left: 50%;
margin-left: -6px;
border-width: 6px 6px 0;
border-top-color: #222;
}

+ 4
- 0
frappe/public/css/form.css Просмотреть файл

@@ -98,6 +98,10 @@
.form-dashboard-section:last-child { .form-dashboard-section:last-child {
border-bottom: none; border-bottom: none;
} }
.form-heatmap .heatmap {
display: flex;
justify-content: center;
}
.form-heatmap .heatmap-message { .form-heatmap .heatmap-message {
margin-top: 10px; margin-top: 10px;
} }


+ 69
- 71
frappe/public/css/graphs.css Просмотреть файл

@@ -2,9 +2,10 @@
.graph-container .graph-focus-margin { .graph-container .graph-focus-margin {
margin: 0px 5%; margin: 0px 5%;
} }
.graph-container .graph-graphics {
.graph-container .graphics {
margin-top: 10px; margin-top: 10px;
padding: 10px 0px;
padding-top: 10px;
padding-bottom: 10px;
position: relative; position: relative;
} }
.graph-container .graph-stats-group { .graph-container .graph-stats-group {
@@ -34,31 +35,28 @@
.graph-container .graph-stats-container .graph-data .stats-value { .graph-container .graph-stats-container .graph-data .stats-value {
color: #98d85b; color: #98d85b;
} }
.graph-container .bar-graph .axis,
.graph-container .line-graph .axis {
.graph-container .axis,
.graph-container .chart-label {
font-size: 10px; font-size: 10px;
fill: #6a737d;
fill: #959ba1;
} }
.graph-container .bar-graph .axis line,
.graph-container .line-graph .axis line {
.graph-container .axis line,
.graph-container .chart-label line {
stroke: rgba(27, 31, 35, 0.1); stroke: rgba(27, 31, 35, 0.1);
} }
.graph-container .percentage-graph {
margin-top: 35px;
}
.graph-container .percentage-graph .progress { .graph-container .percentage-graph .progress {
margin-bottom: 0px; margin-bottom: 0px;
} }
.graph-container .graph-data-points circle {
.graph-container .data-points circle {
stroke: #fff; stroke: #fff;
stroke-width: 2; stroke-width: 2;
} }
.graph-container .graph-data-points path {
.graph-container .data-points path {
fill: none; fill: none;
stroke-opacity: 1; stroke-opacity: 1;
stroke-width: 2px; stroke-width: 2px;
} }
.graph-container line.graph-dashed {
.graph-container line.dashed {
stroke-dasharray: 5,3; stroke-dasharray: 5,3;
} }
.graph-container .tick.x-axis-label { .graph-container .tick.x-axis-label {
@@ -73,7 +71,7 @@
.graph-container .tick .x-value-text { .graph-container .tick .x-value-text {
text-anchor: middle; text-anchor: middle;
} }
.graph-container .graph-svg-tip {
.graph-svg-tip {
position: absolute; position: absolute;
z-index: 99999; z-index: 99999;
padding: 10px; padding: 10px;
@@ -83,12 +81,12 @@
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
border-radius: 3px; border-radius: 3px;
} }
.graph-container .graph-svg-tip.comparison {
.graph-svg-tip.comparison {
padding: 0; padding: 0;
text-align: left; text-align: left;
pointer-events: none; pointer-events: none;
} }
.graph-container .graph-svg-tip.comparison .title {
.graph-svg-tip.comparison .title {
display: block; display: block;
padding: 10px; padding: 10px;
margin: 0; margin: 0;
@@ -96,28 +94,28 @@
line-height: 1; line-height: 1;
pointer-events: none; pointer-events: none;
} }
.graph-container .graph-svg-tip.comparison ul {
.graph-svg-tip.comparison ul {
margin: 0; margin: 0;
white-space: nowrap; white-space: nowrap;
list-style: none; list-style: none;
} }
.graph-container .graph-svg-tip.comparison li {
.graph-svg-tip.comparison li {
display: inline-block; display: inline-block;
padding: 5px 10px; padding: 5px 10px;
} }
.graph-container .graph-svg-tip ul,
.graph-container .graph-svg-tip ol {
.graph-svg-tip ul,
.graph-svg-tip ol {
padding-left: 0; padding-left: 0;
display: flex; display: flex;
} }
.graph-container .graph-svg-tip ul.data-point-list li {
.graph-svg-tip ul.data-point-list li {
min-width: 90px; min-width: 90px;
flex: 1; flex: 1;
} }
.graph-container .graph-svg-tip strong {
.graph-svg-tip strong {
color: #dfe2e5; color: #dfe2e5;
} }
.graph-container .graph-svg-tip::after {
.graph-svg-tip .svg-pointer {
position: absolute; position: absolute;
bottom: -10px; bottom: -10px;
left: 50%; left: 50%;
@@ -128,147 +126,147 @@
border: 5px solid transparent; border: 5px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8); border-top-color: rgba(0, 0, 0, 0.8);
} }
.graph-container .stroke.grey {
.stroke.grey {
stroke: #F0F4F7; stroke: #F0F4F7;
} }
.graph-container .stroke.blue {
.stroke.blue {
stroke: #5e64ff; stroke: #5e64ff;
} }
.graph-container .stroke.red {
.stroke.red {
stroke: #ff5858; stroke: #ff5858;
} }
.graph-container .stroke.light-green {
.stroke.light-green {
stroke: #98d85b; stroke: #98d85b;
} }
.graph-container .stroke.green {
.stroke.green {
stroke: #28a745; stroke: #28a745;
} }
.graph-container .stroke.orange {
.stroke.orange {
stroke: #ffa00a; stroke: #ffa00a;
} }
.graph-container .stroke.purple {
.stroke.purple {
stroke: #743ee2; stroke: #743ee2;
} }
.graph-container .stroke.darkgrey {
.stroke.darkgrey {
stroke: #b8c2cc; stroke: #b8c2cc;
} }
.graph-container .stroke.black {
.stroke.black {
stroke: #36414C; stroke: #36414C;
} }
.graph-container .stroke.yellow {
.stroke.yellow {
stroke: #FEEF72; stroke: #FEEF72;
} }
.graph-container .stroke.light-blue {
.stroke.light-blue {
stroke: #7CD6FD; stroke: #7CD6FD;
} }
.graph-container .stroke.lightblue {
.stroke.lightblue {
stroke: #7CD6FD; stroke: #7CD6FD;
} }
.graph-container .fill.grey {
.fill.grey {
fill: #F0F4F7; fill: #F0F4F7;
} }
.graph-container .fill.blue {
.fill.blue {
fill: #5e64ff; fill: #5e64ff;
} }
.graph-container .fill.red {
.fill.red {
fill: #ff5858; fill: #ff5858;
} }
.graph-container .fill.light-green {
.fill.light-green {
fill: #98d85b; fill: #98d85b;
} }
.graph-container .fill.green {
.fill.green {
fill: #28a745; fill: #28a745;
} }
.graph-container .fill.orange {
.fill.orange {
fill: #ffa00a; fill: #ffa00a;
} }
.graph-container .fill.purple {
.fill.purple {
fill: #743ee2; fill: #743ee2;
} }
.graph-container .fill.darkgrey {
.fill.darkgrey {
fill: #b8c2cc; fill: #b8c2cc;
} }
.graph-container .fill.black {
.fill.black {
fill: #36414C; fill: #36414C;
} }
.graph-container .fill.yellow {
.fill.yellow {
fill: #FEEF72; fill: #FEEF72;
} }
.graph-container .fill.light-blue {
.fill.light-blue {
fill: #7CD6FD; fill: #7CD6FD;
} }
.graph-container .fill.lightblue {
.fill.lightblue {
fill: #7CD6FD; fill: #7CD6FD;
} }
.graph-container .background.grey {
.background.grey {
background: #F0F4F7; background: #F0F4F7;
} }
.graph-container .background.blue {
.background.blue {
background: #5e64ff; background: #5e64ff;
} }
.graph-container .background.red {
.background.red {
background: #ff5858; background: #ff5858;
} }
.graph-container .background.light-green {
.background.light-green {
background: #98d85b; background: #98d85b;
} }
.graph-container .background.green {
.background.green {
background: #28a745; background: #28a745;
} }
.graph-container .background.orange {
.background.orange {
background: #ffa00a; background: #ffa00a;
} }
.graph-container .background.purple {
.background.purple {
background: #743ee2; background: #743ee2;
} }
.graph-container .background.darkgrey {
.background.darkgrey {
background: #b8c2cc; background: #b8c2cc;
} }
.graph-container .background.black {
.background.black {
background: #36414C; background: #36414C;
} }
.graph-container .background.yellow {
.background.yellow {
background: #FEEF72; background: #FEEF72;
} }
.graph-container .background.light-blue {
.background.light-blue {
background: #7CD6FD; background: #7CD6FD;
} }
.graph-container .background.lightblue {
.background.lightblue {
background: #7CD6FD; background: #7CD6FD;
} }
.graph-container .border-top.grey {
.border-top.grey {
border-top: 3px solid #F0F4F7; border-top: 3px solid #F0F4F7;
} }
.graph-container .border-top.blue {
.border-top.blue {
border-top: 3px solid #5e64ff; border-top: 3px solid #5e64ff;
} }
.graph-container .border-top.red {
.border-top.red {
border-top: 3px solid #ff5858; border-top: 3px solid #ff5858;
} }
.graph-container .border-top.light-green {
.border-top.light-green {
border-top: 3px solid #98d85b; border-top: 3px solid #98d85b;
} }
.graph-container .border-top.green {
.border-top.green {
border-top: 3px solid #28a745; border-top: 3px solid #28a745;
} }
.graph-container .border-top.orange {
.border-top.orange {
border-top: 3px solid #ffa00a; border-top: 3px solid #ffa00a;
} }
.graph-container .border-top.purple {
.border-top.purple {
border-top: 3px solid #743ee2; border-top: 3px solid #743ee2;
} }
.graph-container .border-top.darkgrey {
.border-top.darkgrey {
border-top: 3px solid #b8c2cc; border-top: 3px solid #b8c2cc;
} }
.graph-container .border-top.black {
.border-top.black {
border-top: 3px solid #36414C; border-top: 3px solid #36414C;
} }
.graph-container .border-top.yellow {
.border-top.yellow {
border-top: 3px solid #FEEF72; border-top: 3px solid #FEEF72;
} }
.graph-container .border-top.light-blue {
.border-top.light-blue {
border-top: 3px solid #7CD6FD; border-top: 3px solid #7CD6FD;
} }
.graph-container .border-top.lightblue {
.border-top.lightblue {
border-top: 3px solid #7CD6FD; border-top: 3px solid #7CD6FD;
} }

+ 7
- 39
frappe/public/js/frappe/form/dashboard.js Просмотреть файл

@@ -334,22 +334,12 @@ frappe.ui.form.Dashboard = Class.extend({
// heatmap // heatmap
render_heatmap: function() { render_heatmap: function() {
if(!this.heatmap) { if(!this.heatmap) {
this.heatmap = new CalHeatMap();
this.heatmap.init({
itemSelector: "#heatmap-" + frappe.model.scrub(this.frm.doctype),
domain: "month",
subDomain: "day",
start: moment().subtract(1, 'year').add(1, 'month').toDate(),
cellSize: 9,
cellPadding: 2,
domainGutter: 2,
range: 12,
domainLabelFormat: function(date) {
return moment(date).format("MMM").toUpperCase();
},
displayLegend: false,
legend: [5, 10, 15, 20]
// subDomainTextFormat: "%d",
this.heatmap = new frappe.ui.HeatMap({
parent: this.heatmap_area.find("#heatmap-" + frappe.model.scrub(this.frm.doctype)),
height: 100,
start: new Date(moment().subtract(1, 'year').toDate()),
count_label: "items",
discrete_domains: 0
}); });


// center the heatmap // center the heatmap
@@ -388,16 +378,14 @@ frappe.ui.form.Dashboard = Class.extend({
return indicator; return indicator;
}, },


//graphs
// graphs
setup_graph: function() { setup_graph: function() {
var me = this; var me = this;

var method = this.data.graph_method; var method = this.data.graph_method;
var args = { var args = {
doctype: this.frm.doctype, doctype: this.frm.doctype,
docname: this.frm.doc.name, docname: this.frm.doc.name,
}; };

$.extend(args, this.data.graph_method_args); $.extend(args, this.data.graph_method_args);


frappe.call({ frappe.call({
@@ -421,29 +409,9 @@ frappe.ui.form.Dashboard = Class.extend({
mode: 'line', mode: 'line',
height: 140 height: 140
}); });

new frappe.ui.Graph(args); new frappe.ui.Graph(args);
}, },


setup_chart: function(opts) {
var me = this;

this.graph_area.removeClass('hidden');

$.extend(opts, {
wrapper: me.graph_area,
padding: {
right: 30,
bottom: 30
}
});

this.chart = new frappe.ui.Chart(opts);
if(this.chart) {
this.show();
this.chart.set_chart_size(me.wrapper.width() - 60);
}
},
show: function() { show: function() {
this.section.removeClass('hidden'); this.section.removeClass('hidden');
} }


+ 1
- 1
frappe/public/js/frappe/form/templates/form_dashboard.html Просмотреть файл

@@ -2,7 +2,7 @@
<div class="progress-area hidden form-dashboard-section"> <div class="progress-area hidden form-dashboard-section">
</div> </div>
<div class="form-heatmap hidden form-dashboard-section"> <div class="form-heatmap hidden form-dashboard-section">
<div id="heatmap-{{ frappe.model.scrub(frm.doctype) }}"></div>
<div id="heatmap-{{ frappe.model.scrub(frm.doctype) }}" class="heatmap"></div>
<div class="text-muted small heatmap-message hidden"></div> <div class="text-muted small heatmap-message hidden"></div>
</div> </div>
<div class="form-graph form-dashboard-section hidden"></div> <div class="form-graph form-dashboard-section hidden"></div>


+ 595
- 91
frappe/public/js/frappe/ui/graphs.js Просмотреть файл

@@ -28,7 +28,6 @@ frappe.ui.Graph = class Graph {
specific_values = [], specific_values = [],
summary = [], summary = [],


color = 'blue',
mode = '', mode = '',
}) { }) {


@@ -43,27 +42,28 @@ frappe.ui.Graph = class Graph {
} }


this.parent = parent; this.parent = parent;
this.base_height = height;
this.height = height - 40;


this.translate_x = 60;
this.translate_y = 10;
this.set_margins(height);


this.title = title; this.title = title;
this.subtitle = subtitle; this.subtitle = subtitle;


// Begin axis graph-related args

this.y = y; this.y = y;
this.x = x; this.x = x;


this.specific_values = specific_values; this.specific_values = specific_values;
this.summary = summary; this.summary = summary;


this.color = color;
this.mode = mode; this.mode = mode;


// this.current_hover_index = 0;
// this.current_selected_index = 0;

this.$graph = null; this.$graph = null;


// Validate all arguments
// Validate all arguments, check passed data format, set defaults


frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this)); frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this));
} }
@@ -81,18 +81,16 @@ frappe.ui.Graph = class Graph {


refresh() { refresh() {


this.base_width = this.parent.width() - 20;
this.width = this.base_width - 100;
this.setup_base_values();
this.set_width();
this.width = this.base_width - this.translate_x * 2;


this.setup_container(); this.setup_container();
this.setup_components();
this.setup_values(); this.setup_values();

this.setup_utils(); this.setup_utils();


this.setup_components();
this.make_graph_components(); this.make_graph_components();

this.make_tooltip(); this.make_tooltip();


if(this.summary.length > 0) { if(this.summary.length > 0) {
@@ -102,6 +100,20 @@ frappe.ui.Graph = class Graph {
} }
} }


set_margins(height) {
this.base_height = height;
this.height = height - 40;

this.translate_x = 60;
this.translate_y = 10;
}

set_width() {
this.base_width = this.parent.width();
}

setup_base_values() {}

setup_container() { setup_container() {
// Graph needs a dedicated parent element // Graph needs a dedicated parent element
this.parent.empty(); this.parent.empty();
@@ -110,11 +122,11 @@ frappe.ui.Graph = class Graph {
.addClass('graph-container') .addClass('graph-container')
.append($(`<h6 class="title" style="margin-top: 15px;">${this.title}</h6>`)) .append($(`<h6 class="title" style="margin-top: 15px;">${this.title}</h6>`))
.append($(`<h6 class="sub-title uppercase">${this.subtitle}</h6>`)) .append($(`<h6 class="sub-title uppercase">${this.subtitle}</h6>`))
.append($(`<div class="graph-graphics"></div>`))
.append($(`<div class="graphics"></div>`))
.append($(`<div class="graph-stats-container"></div>`)) .append($(`<div class="graph-stats-container"></div>`))
.appendTo(this.parent); .appendTo(this.parent);


this.$graphics = this.container.find('.graph-graphics');
this.$graphics = this.container.find('.graphics');
this.$stats_container = this.container.find('.graph-stats-container'); this.$stats_container = this.container.find('.graph-stats-container');


this.$graph = $('<div>') this.$graph = $('<div>')
@@ -130,6 +142,13 @@ frappe.ui.Graph = class Graph {
return this.$svg; return this.$svg;
} }


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: "data-points" });
this.specific_y_lines = this.snap.g().attr({ class: "specific axis" });
}

setup_values() { setup_values() {
// Multiplier // Multiplier
let all_values = this.specific_values.map(d => d.value); let all_values = this.specific_values.map(d => d.value);
@@ -170,20 +189,15 @@ frappe.ui.Graph = class Graph {
}); });
} }


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() { make_graph_components() {
this.make_y_axis(); this.make_y_axis();
this.make_x_axis(); this.make_x_axis();
this.y_colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen',
'yellow', 'orange', 'red']


this.y.map((d, i) => { this.y.map((d, i) => {
this.make_units(d.y_tops, d.color, i);
this.make_path(d);
this.make_units(d.y_tops, d.color || this.y_colors[i], i);
this.make_path(d, d.color || this.y_colors[i]);
}); });


if(this.specific_values.length > 0) { if(this.specific_values.length > 0) {
@@ -246,6 +260,11 @@ frappe.ui.Graph = class Graph {
transform: `translate(0,${start_at})` transform: `translate(0,${start_at})`
}); });
this.x.values.map((point, i) => { this.x.values.map((point, i) => {
let allowed_space = this.avg_unit_width * 1.5;
if(this.get_strwidth(point) > allowed_space) {
let allowed_letters = allowed_space / 8;
point = point.slice(0, allowed_letters-3) + " ...";
}
this.x_axis_group.add(this.snap.g( this.x_axis_group.add(this.snap.g(
this.snap.line(0, 0, 0, height), this.snap.line(0, 0, 0, height),
this.snap.text(0, text_start_at, point).attr({ this.snap.text(0, text_start_at, point).attr({
@@ -262,8 +281,13 @@ frappe.ui.Graph = class Graph {
make_units(y_values, color, dataset_index) { make_units(y_values, color, dataset_index) {
let d = this.unit_args; let d = this.unit_args;
y_values.map((y, i) => { y_values.map((y, i) => {
let data_unit = this.draw[d.type](this.x_axis_values[i],
y, d.args, color, dataset_index);
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.data_units.add(data_unit);
this.y[dataset_index].data_units.push(data_unit); this.y[dataset_index].data_units.push(data_unit);
}); });
@@ -272,75 +296,58 @@ frappe.ui.Graph = class Graph {
make_path() { } make_path() { }


make_tooltip() { make_tooltip() {
this.tip = $(`<div class="graph-svg-tip comparison">
<span class="title"></span>
<ul class="data-point-list">
</ul>
</div>`).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');

// should be w.r.t. this.parent
this.tip = new frappe.ui.SvgTip({
parent: this.$graphics,
});
this.bind_tooltip(); this.bind_tooltip();
} }


bind_tooltip() { bind_tooltip() {
// should be w.r.t. this.parent, but will have to take care of
// all the elements and padding, margins on top
this.$graphics.on('mousemove', (e) => { this.$graphics.on('mousemove', (e) => {
let offset = $(this.$graphics).offset();
let offset = this.$graphics.offset();
var relX = e.pageX - offset.left - this.translate_x; var relX = e.pageX - offset.left - this.translate_x;
var relY = e.pageY - offset.top - this.translate_y; 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;
}
}
if(relY < this.height + this.translate_y * 2) {
this.map_tooltip_x_position_and_show(relX);
} else { } else {
this.tip.attr({
style: `top: 0px; left: 0px; opacity: 0; pointer-events: none;`
});
this.tip.hide_tip()
} }
}); });

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 = $(`<li>
<strong style="display: block;">
${y_set.formatted ? y_set.formatted[i] : y_set.values[i]}
</strong>
${y_set.title ? y_set.title : '' }
</li>`).addClass(`border-top ${y_set.color}`);
this.tip_data_point_list.append($li);
});
map_tooltip_x_position_and_show(relX) {
for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) {
let x_val = this.x_axis_values[i];
// let delta = i === 0 ? this.avg_unit_width : x_val - this.x_axis_values[i-1];
if(relX > x_val - this.avg_unit_width/2) {
let x = x_val + this.translate_x - 0.5;
let y = this.y_min_tops[i] + this.translate_y;
let title = this.x.formatted && this.x.formatted.length>0
? this.x.formatted[i] : this.x.values[i];
let values = this.y.map((set, j) => {
return {
title: set.title,
value: set.formatted ? set.formatted[i] : set.values[i],
color: set.color || this.y_colors[j],
}
});

this.tip.set_values(x, y, title, '', values);
this.tip.show_tip();
break;
}
}
} }


show_specific_values() { show_specific_values() {
this.specific_values.map(d => { this.specific_values.map(d => {
this.specific_y_lines.add(this.snap.g( this.specific_y_lines.add(this.snap.g(
this.snap.line(0, 0, this.width, 0).attr({ this.snap.line(0, 0, this.width, 0).attr({
class: d.line_type === "dashed" ? "graph-dashed": ""
class: d.line_type === "dashed" ? "dashed": ""
}), }),
this.snap.text(this.width + 5, 0, d.name.toUpperCase()).attr({ this.snap.text(this.width + 5, 0, d.name.toUpperCase()).attr({
dy: ".32em", dy: ".32em",
@@ -434,6 +441,9 @@ frappe.ui.Graph = class Graph {


let width = total_width / args.no_of_datasets; let width = total_width / args.no_of_datasets;
let current_x = start_x + width * index; let current_x = start_x + width * index;
if(y == this.height) {
y = this.height * 0.98;
}
return this.snap.rect(current_x, y, width, this.height - y).attr({ return this.snap.rect(current_x, y, width, this.height - y).attr({
class: `bar mini fill ${color}` class: `bar mini fill ${color}`
}); });
@@ -442,6 +452,11 @@ frappe.ui.Graph = class Graph {
return this.snap.circle(x, y, args.radius).attr({ return this.snap.circle(x, y, args.radius).attr({
class: `fill ${color}` class: `fill ${color}`
}); });
},
'rect': (x, y, args, color) => {
return this.snap.rect(x, y, args.width, args.height).attr({
class: `fill ${color}`
});
} }
}; };


@@ -470,6 +485,7 @@ frappe.ui.BarGraph = class BarGraph extends frappe.ui.Graph {
this.unit_args = { this.unit_args = {
type: 'bar', type: 'bar',
args: { args: {
// More intelligent width setting
space_width: this.y.length > 1 ? space_width: this.y.length > 1 ?
me.avg_unit_width/2 : me.avg_unit_width/8, me.avg_unit_width/2 : me.avg_unit_width/8,
no_of_datasets: this.y.length no_of_datasets: this.y.length
@@ -498,10 +514,10 @@ frappe.ui.LineGraph = class LineGraph extends frappe.ui.Graph {
}; };
} }


make_path(d) {
make_path(d, color) {
let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y));
let path_str = "M"+points_list.join("L"); let path_str = "M"+points_list.join("L");
d.path = this.snap.path(path_str).attr({class: `stroke ${d.color}`});
d.path = this.snap.path(path_str).attr({class: `stroke ${color}`});
this.data_units.prepend(d.path); this.data_units.prepend(d.path);
} }
}; };
@@ -512,7 +528,9 @@ frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph {
} }


make_graph_area() { make_graph_area() {
this.$graphics.addClass('graph-focus-margin');
this.$graphics.addClass('graph-focus-margin').attr({
style: `margin-top: 45px;`
});
this.$stats_container.addClass('graph-focus-margin').attr({ this.$stats_container.addClass('graph-focus-margin').attr({
style: `padding-top: 0px; margin-bottom: 30px;` style: `padding-top: 0px; margin-bottom: 30px;`
}); });
@@ -533,37 +551,523 @@ frappe.ui.PercentageGraph = class PercentageGraph extends frappe.ui.Graph {
return total; return total;
}); });


// Calculate x unit distances for tooltips
if(!this.x.colors) {
this.x.colors = ['green', 'blue', 'purple', 'red', 'orange',
'yellow', 'lightblue', 'lightgreen'];
}
} }


setup_utils() { } setup_utils() { }
setup_components() { setup_components() {
this.$percentage_bar = $(`<div class="progress"> this.$percentage_bar = $(`<div class="progress">
</div>`).appendTo(this.$chart);
</div>`).appendTo(this.$chart); // get this.height, width and avg from this if needed
} }


make_graph_components() { make_graph_components() {
let grand_total = this.x.totals.reduce((a, b) => a + b, 0);
this.grand_total = this.x.totals.reduce((a, b) => a + b, 0);
this.x.units = []; this.x.units = [];
this.x.totals.map((total, i) => { this.x.totals.map((total, i) => {
let $part = $(`<div class="progress-bar background ${this.x.colors[i]}" let $part = $(`<div class="progress-bar background ${this.x.colors[i]}"
style="width: ${total*100/grand_total}%"></div>`);
style="width: ${total*100/this.grand_total}%"></div>`);
this.x.units.push($part); this.x.units.push($part);
this.$percentage_bar.append($part); this.$percentage_bar.append($part);
}); });
} }


make_tooltip() { }
bind_tooltip() {
this.x.units.map(($part, i) => {
$part.on('mouseenter', () => {
let g_off = this.$graphics.offset(), p_off = $part.offset();

let x = p_off.left - g_off.left + $part.width()/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.values[i]) + ': ';
let percent = (this.x.totals[i]*100/this.grand_total).toFixed(1);

this.tip.set_values(x, y, title, percent);
this.tip.show_tip();
});
});
}


show_summary() { show_summary() {
let values = this.x.formatted.length > 0 ? this.x.formatted : this.x.values;
let x_values = this.x.formatted && this.x.formatted.length > 0
? this.x.formatted : this.x.values;
this.x.totals.map((d, i) => { this.x.totals.map((d, i) => {
this.$stats_container.append($(`<div class="stats">
<span class="indicator ${this.x.colors[i]}">
<span class="text-muted">${values[i]}:</span>
${d}
</span>
</div>`));
if(d) {
this.$stats_container.append($(`<div class="stats">
<span class="indicator ${this.x.colors[i]}">
<span class="text-muted">${x_values[i]}:</span>
${d}
</span>
</div>`));
}
});
}
};

frappe.ui.HeatMap = class HeatMap extends frappe.ui.Graph {
constructor({
parent = null,
height = 240,
title = '', subtitle = '',

start = '',
domain = '',
subdomain = '',
data = {},
discrete_domains = 0,
count_label = '',

// remove these graph related args
y = [],
x = [],
specific_values = [],
summary = [],
mode = 'heatmap',
} = {}) {
super(arguments[0]);
this.start = start;
this.data = data;
this.discrete_domains = discrete_domains;

this.count_label = count_label;

this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
}

setup_base_values() {
this.today = new Date();

this.first_week_start = new Date(this.start.toDateString());
this.last_week_start = new Date(this.today.toDateString());
if(this.first_week_start.getDay() !== 7) {
this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay());
}
if(this.last_week_start.getDay() !== 7) {
this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay());
}
this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1;
}

set_width() {
this.base_width = (this.no_of_cols) * 12;
}

setup_components() {
this.domain_label_group = this.snap.g().attr({ class: "domain-label-group chart-label" });
this.data_groups = this.snap.g().attr({ class: "data-groups", transform: `translate(0, 20)` });
}

setup_values() {
this.distribution = this.get_distribution(this.data, this.legend_colors);
this.month_names = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];

this.render_all_weeks_and_store_x_values(this.no_of_cols);
}

render_all_weeks_and_store_x_values(no_of_weeks) {
let current_week_sunday = new Date(this.first_week_start);
this.week_col = 0;
this.current_month = current_week_sunday.getMonth();

this.months = [this.current_month + ''];
this.month_weeks = {}, this.month_start_points = [];
this.month_weeks[this.current_month] = 0;
this.month_start_points.push(13);

for(var i = 0; i < no_of_weeks; i++) {
let data_group, month_change = 0;
let day = new Date(current_week_sunday);

[data_group, month_change] = this.get_week_squares_group(day, this.week_col);
this.data_groups.add(data_group);
this.week_col += 1 + parseInt(this.discrete_domains && month_change);
this.month_weeks[this.current_month]++;
if(month_change) {
this.current_month = (this.current_month + 1) % 12;
this.months.push(this.current_month + '');
this.month_weeks[this.current_month] = 1;
}
this.add_days(current_week_sunday, 7);
}
this.render_month_labels();
}

get_week_squares_group(current_date, index) {
const no_of_weekdays = 7;
const square_side = 10;
const cell_padding = 2;
const step = 1;

let month_change = 0;
let week_col_change = 0;

let data_group = this.snap.g().attr({ class: "data-group" });

for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) {
let data_value = 0;
let color_index = 0;

// TODO: More foolproof for any data
let timestamp = Math.floor(current_date.getTime()/1000).toFixed(1);

if(this.data[timestamp]) {
data_value = this.data[timestamp];
color_index = this.get_max_checkpoint(data_value, this.distribution);
}

if(this.data[Math.round(timestamp)]) {
data_value = this.data[Math.round(timestamp)];
color_index = this.get_max_checkpoint(data_value, this.distribution);
}

let x = 13 + (index + week_col_change) * 12;

data_group.add(this.snap.rect(x, y, square_side, square_side).attr({
'class': `day`,
'fill': this.legend_colors[color_index],
'data-date': this.get_dd_mm_yyyy(current_date),
'data-value': data_value,
'data-day': current_date.getDay()
}));

let next_date = new Date(current_date);
this.add_days(next_date, 1);
if(next_date.getMonth() - current_date.getMonth()) {
month_change = 1;
if(this.discrete_domains) {
week_col_change = 1;
}

this.month_start_points.push(13 + (index + week_col_change) * 12);
}
current_date = next_date;
}

return [data_group, month_change];
}

render_month_labels() {
this.first_month_label = 1;
// if (this.first_week_start.getDate() > 8) {
// this.first_month_label = 0;
// }
this.last_month_label = 1;

let first_month = this.months.shift();
let first_month_start = this.month_start_points.shift();
// render first month if

let last_month = this.months.pop();
let last_month_start = this.month_start_points.pop();
// render last month if

this.month_start_points.map((start, i) => {
let month_name = this.month_names[this.months[i]].substring(0, 3);
this.domain_label_group.add(this.snap.text(start + 12, 10, month_name).attr({
dy: ".32em",
class: "y-value-text"
}));
});
}

make_graph_components() {
this.container.find('.graph-stats-container, .sub-title, .title').hide();
this.container.find('.graphics').css({'margin-top': '0px', 'padding-top': '0px'});
}

bind_tooltip() {
this.container.on('mouseenter', '.day', (e) => {
let subdomain = $(e.target);
let count = subdomain.attr('data-value');
let date_parts = subdomain.attr('data-date').split('-');

let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3);

let g_off = this.$graphics.offset(), p_off = subdomain.offset();

let width = parseInt(subdomain.attr('width'));
let x = p_off.left - g_off.left + (width+2)/2;
let y = p_off.top - g_off.top - (width+2)/2;
let value = count + ' ' + this.count_label;
let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2];

this.tip.set_values(x, y, name, value, [], 1);
this.tip.show_tip();
});
}

update(data) {
this.data = data;
this.setup_values();
}

get_distribution(data, mapper_array) {
let data_values = Object.keys(data).map(key => data[key]);
let data_max_value = Math.max(...data_values);

let distribution_step = 1 / (mapper_array.length - 1);
let distribution = [];

mapper_array.map((color, i) => {
let checkpoint = data_max_value * (distribution_step * i);
distribution.push(checkpoint);
});

return distribution;
}

get_max_checkpoint(value, distribution) {
return distribution.filter(d => {
return d <= value;
}).length - 1;
}

// TODO: date utils, move these out

// https://stackoverflow.com/a/11252167/6495043
treat_as_utc(date_str) {
let result = new Date(date_str);
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
return result;
}

get_dd_mm_yyyy(date) {
let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based
return [
(dd>9 ? '' : '0') + dd,
(mm>9 ? '' : '0') + mm,
date.getFullYear()
].join('-');
}

get_weeks_between(start_date_str, end_date_str) {
return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7);
}

get_days_between(start_date_str, end_date_str) {
let milliseconds_per_day = 24 * 60 * 60 * 1000;
return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day;
}

// mutates
add_days(date, number_of_days) {
date.setDate(date.getDate() + number_of_days);
}

get_month_name() {}
}

frappe.ui.SvgTip = class {
constructor({
parent = null
}) {
this.parent = parent;
this.title_name = '';
this.title_value = '';
this.list_values = [];
this.title_value_first = 0;

this.x = 0;
this.y = 0;

this.top = 0;
this.left = 0;

this.setup();
}

setup() {
this.make_tooltip();
}

refresh() {
this.fill();
this.calc_position();
// this.show_tip();
}

make_tooltip() {
this.container = $(`<div class="graph-svg-tip comparison">
<span class="title"></span>
<ul class="data-point-list"></ul>
<div class="svg-pointer"></div>
</div>`).appendTo(this.parent);
this.hide_tip();

this.title = this.container.find('.title');
this.data_point_list = this.container.find('.data-point-list');

this.parent.on('mouseleave', () => {
this.hide_tip();
});
}

fill() {
let title;
if(this.title_value_first) {
title = `<strong>${this.title_value}</strong>${this.title_name}`;
} else {
title = `${this.title_name}<strong>${this.title_value}</strong>`;
}
this.title.html(title);
this.data_point_list.empty();
this.list_values.map((set, i) => {
let $li = $(`<li>
<strong style="display: block;">${set.value ? set.value : '' }</strong>
${set.title ? set.title : '' }
</li>`).addClass(`border-top ${set.color || 'black'}`);

this.data_point_list.append($li);
});
}

calc_position() {
this.top = this.y - this.container.height();
this.left = this.x - this.container.width()/2;
let max_left = this.parent.width() - this.container.width();

let $pointer = this.container.find('.svg-pointer');

if(this.left < 0) {
$pointer.css({ 'left': `calc(50% - ${-1 * this.left}px)` });
this.left = 0;
} else if(this.left > max_left) {
let delta = this.left - max_left;
$pointer.css({ 'left': `calc(50% + ${delta}px)` });
this.left = max_left;
} else {
$pointer.css({ 'left': `50%` });
}
}

set_values(x, y, title_name = '', title_value = '', list_values = [], title_value_first = 0) {
this.title_name = title_name;
this.title_value = title_value;
this.list_values = list_values;
this.x = x;
this.y = y;
this.title_value_first = title_value_first;
this.refresh();
}

hide_tip() {
this.container.css({
'top': '0px',
'left': '0px',
'opacity': '0'
});
}

show_tip() {
this.container.css({
'top': this.top + 'px',
'left': this.left + 'px',
'opacity': '1'
}); });
} }
}; };


frappe.provide("frappe.ui.graphs");

frappe.ui.graphs.get_timeseries = function(start, frequency, length) {

}

frappe.ui.graphs.map_c3 = function(chart) {
if (chart.data) {
let data = chart.data;
let mode = chart.chart_type || 'line';
if(mode === 'pie') {
mode = 'percentage';
}

let x = {}, y = [];

if(data.columns) {
let columns = data.columns;

x.values = columns.filter(col => {
return col[0] === data.x;
})[0];

if(x.values && x.values.length) {
let dataset_length = x.values.length;
let dirty = false;
columns.map(col => {
if(col[0] !== data.x) {
if(col.length === dataset_length) {
let title = col[0];
col.splice(0, 1);
y.push({
title: title,
values: col,
});
} else {
dirty = true;
}
}
})

if(dirty) {
return;
}

x.values.splice(0, 1);

return {
mode: mode,
y: y,
x: x
}

}
} else if(data.rows) {
let rows = data.rows;
x.values = rows[0];

rows.map((row, i) => {
if(i === 0) {
x.values = row;
} else {
y.push({
title: 'data' + i,
values: row,
})
}
});

return {
mode: mode,
y: y,
x: x
}
}
}
}


// frappe.ui.CompositeGraph = class {
// constructor({
// parent = null
// }) {
// this.parent = parent;
// this.title_name = '';
// this.title_value = '';
// this.list_values = [];

// this.x = 0;
// this.y = 0;

// this.top = 0;
// this.left = 0;

// this.setup();
// }
// }

+ 1
- 0
frappe/public/js/frappe/views/reports/query_report.js Просмотреть файл

@@ -3,6 +3,7 @@


frappe.provide("frappe.views"); frappe.provide("frappe.views");
frappe.provide("frappe.query_reports"); frappe.provide("frappe.query_reports");
frappe.provide("frappe.ui.graphs");


frappe.standard_pages["query-report"] = function() { frappe.standard_pages["query-report"] = function() {
var wrapper = frappe.container.add_page('query-report'); var wrapper = frappe.container.add_page('query-report');


+ 0
- 3471
frappe/public/js/lib/cal-heatmap.js
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 5
- 0
frappe/public/less/form.less Просмотреть файл

@@ -136,6 +136,11 @@


.form-heatmap { .form-heatmap {


.heatmap {
display: flex;
justify-content: center;
}

.heatmap-message { .heatmap-message {
margin-top: 10px; margin-top: 10px;
} }


+ 204
- 210
frappe/public/less/graphs.less Просмотреть файл

@@ -5,9 +5,10 @@
margin: 0px 5%; margin: 0px 5%;
} }


.graph-graphics {
.graphics {
margin-top: 10px; margin-top: 10px;
padding: 10px 0px;
padding-top: 10px;
padding-bottom: 10px;
position: relative; position: relative;
} }


@@ -43,28 +44,22 @@
} }
} }


.bar-graph, .line-graph {

// baselines
.axis {
font-size: 10px;
fill: #6a737d;
.axis, .chart-label {
font-size: 10px;
fill: #959ba1;


line {
stroke: rgba(27,31,35,0.1);
}
line {
stroke: rgba(27,31,35,0.1);
} }
} }


.percentage-graph { .percentage-graph {
margin-top: 35px;

.progress { .progress {
margin-bottom: 0px; margin-bottom: 0px;
} }
} }


.graph-data-points {
.data-points {
circle { circle {
// fill: #28a745; // fill: #28a745;
stroke: #fff; stroke: #fff;
@@ -83,7 +78,7 @@
} }
} }


line.graph-dashed {
line.dashed {
stroke-dasharray: 5,3; stroke-dasharray: 5,3;
} }


@@ -104,216 +99,215 @@
text-anchor: middle; 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;
.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; 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;
ul {
margin: 0;
white-space: nowrap;
list-style: none;
} }


&::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);
li {
display: inline-block;
padding: 5px 10px;
} }
} }


.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;
ul, ol {
padding-left: 0;
display: flex;
} }


.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;
ul.data-point-list li {
min-width: 90px;
flex: 1;
} }


.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;
strong {
color: #dfe2e5;
} }


.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;
.svg-pointer {
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;
} }

Загрузка…
Отмена
Сохранить