diff --git a/docs/assets/js/index.js b/docs/assets/js/index.js
index 073b15c..c83c391 100755
--- a/docs/assets/js/index.js
+++ b/docs/assets/js/index.js
@@ -20,8 +20,8 @@ let line_data = {
"labels": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"datasets": [{
"color": "green",
- // "values": [25, 40, 30, 35, 48, 52, 17],
- "values": [25, -90, -30, 35, 48, 52, -17]
+ "values": [25, 40, 30, 35, 48, 52, 17],
+ // "values": [25, -90, -30, 35, 48, 52, -17]
}
]
@@ -70,16 +70,18 @@ let more_line_data = {
let line_chart = new frappe.chart.FrappeChart ({
parent: "#charts-2",
data: line_data,
- type: 'bar',
- height: 140
+ type: 'line',
+ height: 340,
+ region_fill: 1
})
let bar_chart = new frappe.chart.FrappeChart ({
parent: "#charts-1",
data: bar_data,
- type: 'bar',
+ type: 'line',
height: 140,
- is_navigable: 1
+ is_navigable: 1,
+ region_fill: 1
})
bar_chart.parent.addEventListener('data-select', (e) => {
@@ -88,8 +90,9 @@ bar_chart.parent.addEventListener('data-select', (e) => {
// console.log("chart", bar_chart);
+let percentage_data = {};
-let heatmap_data = {}
+let heatmap_data = {};
// update_test() {
// setTimeout(() => {
diff --git a/docs/index.html b/docs/index.html
index dfd845b..02eedc1 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -28,6 +28,7 @@
Frappé Charts
GitHub-inspired simple and modern charts for the web
with zero dependencies.
+ Because dumb charts are hard to come by.
diff --git a/src/charts.css b/src/charts.css
index 73e686f..e278aa2 100644
--- a/src/charts.css
+++ b/src/charts.css
@@ -74,6 +74,30 @@
.chart-container .tick .x-value-text {
text-anchor: middle;
}
+.chart-container .progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1)
+}
+.chart-container .progress-bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ background-color: #36414c;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ -webkit-transition: width .6s ease;
+ -o-transition: width .6s ease;
+ transition: width .6s ease;
+}
.graph-svg-tip {
position: absolute;
z-index: 99999;
diff --git a/src/charts.js b/src/charts.js
index 1b59de5..b81aee4 100644
--- a/src/charts.js
+++ b/src/charts.js
@@ -44,6 +44,8 @@ frappe.chart.FrappeChart = class {
return new frappe.chart.PercentageChart(arguments[0]);
} else if(type === 'heatmap') {
return new frappe.chart.HeatMap(arguments[0]);
+ } else {
+ return new frappe.chart.LineChart(arguments[0]);
}
}
@@ -133,12 +135,10 @@ frappe.chart.FrappeChart = class {
this.parent.appendChild(this.container);
this.chart_wrapper = this.container.querySelector('.frappe-chart');
- // this.chart_wrapper.appendChild();
+ this.stats_wrapper = this.container.querySelector('.graph-stats-container');
this.make_chart_area();
this.make_draw_area();
-
- this.stats_wrapper = this.container.querySelector('.graph-stats-container');
}
make_chart_area() {
@@ -149,6 +149,10 @@ frappe.chart.FrappeChart = class {
height: this.base_height
});
+ this.svg_defs = $$.createSVG('defs', {
+ inside: this.svg,
+ });
+
return this.svg;
}
@@ -417,8 +421,8 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
data.push({values: d.values});
d.svg_units = [];
- this.make_new_units_for_dataset(d.y_tops, d.color || this.colors[i], i);
this.make_path && this.make_path(d, d.color || this.colors[i]);
+ this.make_new_units_for_dataset(d.y_tops, d.color || this.colors[i], i);
});
setTimeout(() => {
@@ -481,13 +485,15 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
}
bind_tooltip() {
- // should be w.r.t. this.parent, but will have to take care of
- // all the elements and padding, margins on top
+ // 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 = {
- top: rect.top + document.body.scrollTop,
- left: rect.left + document.body.scrollLeft
+ // 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 relX = e.pageX - offset.left - this.translate_x;
let relY = e.pageY - offset.top - this.translate_y;
@@ -518,7 +524,7 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
}
});
- // TODO: upside-down tooltips
+ // TODO: upside-down tooltips for negative values?
this.tip.set_values(x, y, title, '', values);
this.tip.show_tip();
break;
@@ -539,14 +545,8 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
let elements_to_animate = [];
elements_to_animate = this.animate_for_equilength_data(elements_to_animate);
- // create new x,y pair string and animate path
if(this.y[0].path) {
- this.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");
- let args = [{unit:this.y[i].path, object: this.y[i], key:'path'}, {d:new_path_str}, 300, "easein"];
- elements_to_animate.push(args);
- });
+ elements_to_animate = this.animate_path(elements_to_animate);
}
// elements_to_animate = elements_to_animate.concat(this.update_y_axis());
@@ -554,14 +554,16 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
if(this.svg.parentNode == this.chart_wrapper) {
this.chart_wrapper.removeChild(this.svg);
+ this.chart_wrapper.appendChild(anim_svg);
}
- this.chart_wrapper.appendChild(anim_svg);
// Replace the new svg (data has long been replaced)
setTimeout(() => {
- this.chart_wrapper.removeChild(anim_svg);
- this.chart_wrapper.appendChild(this.svg);
- }, 250);
+ if(anim_svg.parentNode == this.chart_wrapper) {
+ this.chart_wrapper.removeChild(anim_svg);
+ this.chart_wrapper.appendChild(this.svg);
+ }
+ }, 200);
}
update_y_axis() {
@@ -581,13 +583,38 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
let type = this.unit_args.type;
d.svg_units.map((unit, j) => {
elements_to_animate.push(this.animate[type](
- {unit:unit, array:d.svg_units, index: j}, // unit, with info to replace from data
+ {unit:unit, array:d.svg_units, index: j}, // unit, with info to replace where it came from in the data
d.y_tops[j],
this.zero_line
));
});
});
+
+ // Change in data, so calculate dependencies
this.calc_min_tops();
+
+ return elements_to_animate;
+ }
+
+ animate_path(elements_to_animate) {
+ // create new x,y pair string and animate path
+
+ this.y.map((e, i) => {
+ let new_points_list = e.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y));
+ let new_path_str = new_points_list.join("L");
+ let path_args = [{unit: this.y[i].path, object: this.y[i], key: 'path'}, {d:"M"+new_path_str}, 250, "easein"];
+ elements_to_animate.push(path_args);
+
+ if(this.y[i].region_path) {
+ let region_args = [
+ {unit: this.y[i].region_path, object: this.y[i], key: 'region_path'},
+ {d:"M" + `0,${this.zero_line}L` + new_path_str + `L${this.width},${this.zero_line}`},
+ 250,
+ "easein"
+ ];
+ elements_to_animate.push(region_args);
+ }
+ });
return elements_to_animate;
}
@@ -672,7 +699,6 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
intervals.push(start);
start += interval_size;
}
- console.log("intervals", intervals, count);
return intervals;
}
@@ -782,12 +808,12 @@ frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart {
y = zero_line;
}
- return [bar_obj, {height: height, y: y}, 300, "easein"];
- // bar.animate({height: args.new_height, y: y_top}, 300, mina.easein);
+ return [bar_obj, {height: height, y: y}, 250, "easein"];
+ // bar.animate({height: args.new_height, y: y_top}, 250, mina.easein);
},
'dot': (dot_obj, y_top) => {
- return [dot_obj, {cy: y_top}, 300, "easein"];
- // dot.animate({cy: y_top}, 300, mina.easein);
+ return [dot_obj, {cy: y_top}, 250, "easein"];
+ // dot.animate({cy: y_top}, 250, mina.easein);
}
};
}
@@ -865,6 +891,7 @@ frappe.chart.LineChart = class LineChart extends frappe.chart.AxisChart {
}
this.type = 'line-graph';
+ this.region_fill = args.region_fill;
this.setup();
}
@@ -881,24 +908,48 @@ frappe.chart.LineChart = class LineChart extends frappe.chart.AxisChart {
make_path(d, color) {
let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y));
- let path_str = "M"+points_list.join("L");
+ let points_str = points_list.join("L");
d.path = $$.createSVG('path', {
+ inside: this.svg_units_group,
className: `stroke ${color}`,
- d: path_str
+ d: "M"+points_str
});
- this.svg_units_group.prepend(d.path);
- }
-}
+ if(this.region_fill) {
+ let gradient_id ='path-fill-gradient' + '-' + color;
-frappe.chart.RegionChart = class RegionChart extends frappe.chart.LineChart {
- constructor(args) {
- super(args);
+ this.gradient_def = $$.createSVG('linearGradient', {
+ inside: this.svg_defs,
+ id: gradient_id,
+ x1: 0,
+ x2: 0,
+ y1: 0,
+ y2: 1
+ });
- this.type = 'region-graph';
- this.region_fill = 1;
- this.setup();
+ function set_gradient_stop(grad_elem, offset, color, opacity) {
+ $$.createSVG('stop', {
+ 'inside': grad_elem,
+ 'offset': offset,
+ 'stop-color': color,
+ 'stop-opacity': opacity
+ });
+ }
+
+ set_gradient_stop(this.gradient_def, "0%", color, 0.4);
+ set_gradient_stop(this.gradient_def, "50%", color, 0.2);
+ set_gradient_stop(this.gradient_def, "100%", color, 0);
+
+ d.region_path = $$.createSVG('path', {
+ inside: this.svg_units_group,
+ className: `region-fill`,
+ d: "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`,
+ });
+
+ d.region_path.style.stroke = "none";
+ d.region_path.style.fill = `url(#${gradient_id})`;
+ }
}
}
@@ -924,7 +975,9 @@ frappe.chart.PercentageChart = class PercentageChart extends frappe.chart.Frappe
this.stats_wrapper.className += ' ' + 'graph-focus-margin';
this.stats_wrapper.style.marginBottom = '30px';
this.stats_wrapper.style.paddingTop = '0px';
+ }
+ make_draw_area() {
this.chart_div = $$.create('div', {
className: 'div',
inside: this.chart_wrapper,
@@ -1414,78 +1467,6 @@ frappe.chart.SvgTip = class {
}
}
-frappe.chart.map_c3 = (chart) => {
- if (chart.data) {
- let data = chart.data;
- let type = chart.chart_type || 'line';
- if(type === 'pie') {
- type = 'percentage';
- }
-
- let x = {}, y = [];
-
- if(data.columns) {
- let columns = data.columns;
-
- x = columns.filter(col => {
- return col[0] === data.x;
- })[0];
-
- if(x && x.length) {
- let dataset_length = x.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.splice(0, 1);
-
- return {
- type: type,
- y: y,
- x: x
- }
-
- }
- } else if(data.rows) {
- let rows = data.rows;
- x = rows[0];
-
- rows.map((row, i) => {
- if(i === 0) {
- x = row;
- } else {
- y.push({
- title: 'data' + i,
- values: row,
- });
- }
- });
-
- return {
- type: type,
- y: y,
- x: x
- }
- }
- }
-}
-
// Helpers
frappe.chart.utils = {};
frappe.chart.utils.float_2 = d => parseFloat(d.toFixed(2));