Merge pull request #53 from IDDT/master
Add custom legend for Heatmap chart.
This commit is contained in:
commit
ebfd6bd8dc
119
dist/frappe-charts.min.cjs.js
vendored
119
dist/frappe-charts.min.cjs.js
vendored
@ -675,6 +675,29 @@ function runSVGAnimation(svg_container, elements) {
|
||||
// //
|
||||
// }
|
||||
|
||||
function calc_distribution(values, distribution_size) {
|
||||
// Assume non-negative values,
|
||||
// implying distribution minimum at zero
|
||||
|
||||
var data_max_value = Math.max.apply(Math, toConsumableArray(values));
|
||||
|
||||
var distribution_step = 1 / (distribution_size - 1);
|
||||
var distribution = [];
|
||||
|
||||
for (var i = 0; i < distribution_size; i++) {
|
||||
var checkpoint = data_max_value * (distribution_step * i);
|
||||
distribution.push(checkpoint);
|
||||
}
|
||||
|
||||
return distribution;
|
||||
}
|
||||
|
||||
function get_max_checkpoint(value, distribution) {
|
||||
return distribution.filter(function (d) {
|
||||
return d < value;
|
||||
}).length;
|
||||
}
|
||||
|
||||
function calc_y_intervals(array) {
|
||||
//*** Where the magic happens ***
|
||||
|
||||
@ -2641,6 +2664,12 @@ function lighten_darken_color(col, amt) {
|
||||
return (usePound ? "#" : "") + (g | b << 8 | r << 16).toString(16);
|
||||
}
|
||||
|
||||
function is_valid_color(string) {
|
||||
// https://stackoverflow.com/a/8027444/6495043
|
||||
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string)
|
||||
);
|
||||
}
|
||||
|
||||
var ANGLE_RATIO = Math.PI / 180;
|
||||
var FULL_ANGLE = 360;
|
||||
|
||||
@ -2945,7 +2974,9 @@ var Heatmap = function (_BaseChart) {
|
||||
_ref$discrete_domains = _ref.discrete_domains,
|
||||
discrete_domains = _ref$discrete_domains === undefined ? 0 : _ref$discrete_domains,
|
||||
_ref$count_label = _ref.count_label,
|
||||
count_label = _ref$count_label === undefined ? '' : _ref$count_label;
|
||||
count_label = _ref$count_label === undefined ? '' : _ref$count_label,
|
||||
_ref$legend_colors = _ref.legend_colors,
|
||||
legend_colors = _ref$legend_colors === undefined ? [] : _ref$legend_colors;
|
||||
classCallCheck(this, Heatmap);
|
||||
|
||||
var _this = possibleConstructorReturn(this, (Heatmap.__proto__ || Object.getPrototypeOf(Heatmap)).call(this, arguments[0]));
|
||||
@ -2961,7 +2992,12 @@ var Heatmap = function (_BaseChart) {
|
||||
var today = new Date();
|
||||
_this.start = start || add_days(today, 365);
|
||||
|
||||
_this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
legend_colors = legend_colors.slice(0, 5);
|
||||
_this.legend_colors = _this.validate_colors(legend_colors) ? legend_colors : ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
|
||||
// Hardcoded for a fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
_this.distribution_size = 5;
|
||||
|
||||
_this.translate_x = 0;
|
||||
_this.setup();
|
||||
@ -2969,6 +3005,21 @@ var Heatmap = function (_BaseChart) {
|
||||
}
|
||||
|
||||
createClass(Heatmap, [{
|
||||
key: 'validate_colors',
|
||||
value: function validate_colors(colors) {
|
||||
if (colors.length < 5) return 0;
|
||||
|
||||
var valid = 1;
|
||||
colors.forEach(function (string) {
|
||||
if (!is_valid_color(string)) {
|
||||
valid = 0;
|
||||
console.warn('"' + string + '" is not a valid color.');
|
||||
}
|
||||
}, this);
|
||||
|
||||
return valid;
|
||||
}
|
||||
}, {
|
||||
key: 'setup_base_values',
|
||||
value: function setup_base_values() {
|
||||
this.today = new Date();
|
||||
@ -3012,9 +3063,16 @@ var Heatmap = function (_BaseChart) {
|
||||
}, {
|
||||
key: 'setup_values',
|
||||
value: function setup_values() {
|
||||
var _this2 = this;
|
||||
|
||||
this.domain_label_group.textContent = '';
|
||||
this.data_groups.textContent = '';
|
||||
this.distribution = this.get_distribution(this.data, this.legend_colors);
|
||||
|
||||
var data_values = Object.keys(this.data).map(function (key) {
|
||||
return _this2.data[key];
|
||||
});
|
||||
this.distribution = calc_distribution(data_values, this.distribution_size);
|
||||
|
||||
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);
|
||||
@ -3081,12 +3139,14 @@ var Heatmap = function (_BaseChart) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (data_value) {
|
||||
color_index = get_max_checkpoint(data_value, this.distribution);
|
||||
}
|
||||
|
||||
var x = 13 + (index + week_col_change) * 12;
|
||||
@ -3124,7 +3184,7 @@ var Heatmap = function (_BaseChart) {
|
||||
}, {
|
||||
key: 'render_month_labels',
|
||||
value: function render_month_labels() {
|
||||
var _this2 = this;
|
||||
var _this3 = this;
|
||||
|
||||
// this.first_month_label = 1;
|
||||
// if (this.first_week_start.getDate() > 8) {
|
||||
@ -3146,11 +3206,11 @@ var Heatmap = function (_BaseChart) {
|
||||
this.month_start_points.pop();
|
||||
|
||||
this.month_start_points.map(function (start, i) {
|
||||
var month_name = _this2.month_names[_this2.months[i]].substring(0, 3);
|
||||
var month_name = _this3.month_names[_this3.months[i]].substring(0, 3);
|
||||
|
||||
$.createSVG('text', {
|
||||
className: 'y-value-text',
|
||||
inside: _this2.domain_label_group,
|
||||
inside: _this3.domain_label_group,
|
||||
x: start + 12,
|
||||
y: 10,
|
||||
dy: '.32em',
|
||||
@ -3170,26 +3230,26 @@ var Heatmap = function (_BaseChart) {
|
||||
}, {
|
||||
key: 'bind_tooltip',
|
||||
value: function bind_tooltip() {
|
||||
var _this3 = this;
|
||||
var _this4 = this;
|
||||
|
||||
Array.prototype.slice.call(document.querySelectorAll(".data-group .day")).map(function (el) {
|
||||
el.addEventListener('mouseenter', function (e) {
|
||||
var count = e.target.getAttribute('data-value');
|
||||
var date_parts = e.target.getAttribute('data-date').split('-');
|
||||
|
||||
var month = _this3.month_names[parseInt(date_parts[1]) - 1].substring(0, 3);
|
||||
var month = _this4.month_names[parseInt(date_parts[1]) - 1].substring(0, 3);
|
||||
|
||||
var g_off = _this3.chart_wrapper.getBoundingClientRect(),
|
||||
var g_off = _this4.chart_wrapper.getBoundingClientRect(),
|
||||
p_off = e.target.getBoundingClientRect();
|
||||
|
||||
var width = parseInt(e.target.getAttribute('width'));
|
||||
var x = p_off.left - g_off.left + (width + 2) / 2;
|
||||
var y = p_off.top - g_off.top - (width + 2) / 2;
|
||||
var value = count + ' ' + _this3.count_label;
|
||||
var value = count + ' ' + _this4.count_label;
|
||||
var name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2];
|
||||
|
||||
_this3.tip.set_values(x, y, name, value, [], 1);
|
||||
_this3.tip.show_tip();
|
||||
_this4.tip.set_values(x, y, name, value, [], 1);
|
||||
_this4.tip.show_tip();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -3200,37 +3260,6 @@ var Heatmap = function (_BaseChart) {
|
||||
this.setup_values();
|
||||
this.bind_tooltip();
|
||||
}
|
||||
}, {
|
||||
key: 'get_distribution',
|
||||
value: function get_distribution() {
|
||||
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
var mapper_array = arguments[1];
|
||||
|
||||
var data_values = Object.keys(data).map(function (key) {
|
||||
return data[key];
|
||||
});
|
||||
var data_max_value = Math.max.apply(Math, toConsumableArray(data_values));
|
||||
|
||||
var distribution_step = 1 / (mapper_array.length - 1);
|
||||
var distribution = [];
|
||||
|
||||
mapper_array.map(function (color, i) {
|
||||
var checkpoint = data_max_value * (distribution_step * i);
|
||||
distribution.push(checkpoint);
|
||||
});
|
||||
|
||||
return distribution;
|
||||
}
|
||||
}, {
|
||||
key: 'get_max_checkpoint',
|
||||
value: function get_max_checkpoint(value, distribution) {
|
||||
return distribution.filter(function (d, i) {
|
||||
if (i === 1) {
|
||||
return distribution[0] < value;
|
||||
}
|
||||
return d <= value;
|
||||
}).length - 1;
|
||||
}
|
||||
}]);
|
||||
return Heatmap;
|
||||
}(BaseChart);
|
||||
|
||||
119
dist/frappe-charts.min.esm.js
vendored
119
dist/frappe-charts.min.esm.js
vendored
@ -673,6 +673,29 @@ function runSVGAnimation(svg_container, elements) {
|
||||
// //
|
||||
// }
|
||||
|
||||
function calc_distribution(values, distribution_size) {
|
||||
// Assume non-negative values,
|
||||
// implying distribution minimum at zero
|
||||
|
||||
var data_max_value = Math.max.apply(Math, toConsumableArray(values));
|
||||
|
||||
var distribution_step = 1 / (distribution_size - 1);
|
||||
var distribution = [];
|
||||
|
||||
for (var i = 0; i < distribution_size; i++) {
|
||||
var checkpoint = data_max_value * (distribution_step * i);
|
||||
distribution.push(checkpoint);
|
||||
}
|
||||
|
||||
return distribution;
|
||||
}
|
||||
|
||||
function get_max_checkpoint(value, distribution) {
|
||||
return distribution.filter(function (d) {
|
||||
return d < value;
|
||||
}).length;
|
||||
}
|
||||
|
||||
function calc_y_intervals(array) {
|
||||
//*** Where the magic happens ***
|
||||
|
||||
@ -2639,6 +2662,12 @@ function lighten_darken_color(col, amt) {
|
||||
return (usePound ? "#" : "") + (g | b << 8 | r << 16).toString(16);
|
||||
}
|
||||
|
||||
function is_valid_color(string) {
|
||||
// https://stackoverflow.com/a/8027444/6495043
|
||||
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string)
|
||||
);
|
||||
}
|
||||
|
||||
var ANGLE_RATIO = Math.PI / 180;
|
||||
var FULL_ANGLE = 360;
|
||||
|
||||
@ -2943,7 +2972,9 @@ var Heatmap = function (_BaseChart) {
|
||||
_ref$discrete_domains = _ref.discrete_domains,
|
||||
discrete_domains = _ref$discrete_domains === undefined ? 0 : _ref$discrete_domains,
|
||||
_ref$count_label = _ref.count_label,
|
||||
count_label = _ref$count_label === undefined ? '' : _ref$count_label;
|
||||
count_label = _ref$count_label === undefined ? '' : _ref$count_label,
|
||||
_ref$legend_colors = _ref.legend_colors,
|
||||
legend_colors = _ref$legend_colors === undefined ? [] : _ref$legend_colors;
|
||||
classCallCheck(this, Heatmap);
|
||||
|
||||
var _this = possibleConstructorReturn(this, (Heatmap.__proto__ || Object.getPrototypeOf(Heatmap)).call(this, arguments[0]));
|
||||
@ -2959,7 +2990,12 @@ var Heatmap = function (_BaseChart) {
|
||||
var today = new Date();
|
||||
_this.start = start || add_days(today, 365);
|
||||
|
||||
_this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
legend_colors = legend_colors.slice(0, 5);
|
||||
_this.legend_colors = _this.validate_colors(legend_colors) ? legend_colors : ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
|
||||
// Hardcoded for a fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
_this.distribution_size = 5;
|
||||
|
||||
_this.translate_x = 0;
|
||||
_this.setup();
|
||||
@ -2967,6 +3003,21 @@ var Heatmap = function (_BaseChart) {
|
||||
}
|
||||
|
||||
createClass(Heatmap, [{
|
||||
key: 'validate_colors',
|
||||
value: function validate_colors(colors) {
|
||||
if (colors.length < 5) return 0;
|
||||
|
||||
var valid = 1;
|
||||
colors.forEach(function (string) {
|
||||
if (!is_valid_color(string)) {
|
||||
valid = 0;
|
||||
console.warn('"' + string + '" is not a valid color.');
|
||||
}
|
||||
}, this);
|
||||
|
||||
return valid;
|
||||
}
|
||||
}, {
|
||||
key: 'setup_base_values',
|
||||
value: function setup_base_values() {
|
||||
this.today = new Date();
|
||||
@ -3010,9 +3061,16 @@ var Heatmap = function (_BaseChart) {
|
||||
}, {
|
||||
key: 'setup_values',
|
||||
value: function setup_values() {
|
||||
var _this2 = this;
|
||||
|
||||
this.domain_label_group.textContent = '';
|
||||
this.data_groups.textContent = '';
|
||||
this.distribution = this.get_distribution(this.data, this.legend_colors);
|
||||
|
||||
var data_values = Object.keys(this.data).map(function (key) {
|
||||
return _this2.data[key];
|
||||
});
|
||||
this.distribution = calc_distribution(data_values, this.distribution_size);
|
||||
|
||||
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);
|
||||
@ -3079,12 +3137,14 @@ var Heatmap = function (_BaseChart) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (data_value) {
|
||||
color_index = get_max_checkpoint(data_value, this.distribution);
|
||||
}
|
||||
|
||||
var x = 13 + (index + week_col_change) * 12;
|
||||
@ -3122,7 +3182,7 @@ var Heatmap = function (_BaseChart) {
|
||||
}, {
|
||||
key: 'render_month_labels',
|
||||
value: function render_month_labels() {
|
||||
var _this2 = this;
|
||||
var _this3 = this;
|
||||
|
||||
// this.first_month_label = 1;
|
||||
// if (this.first_week_start.getDate() > 8) {
|
||||
@ -3144,11 +3204,11 @@ var Heatmap = function (_BaseChart) {
|
||||
this.month_start_points.pop();
|
||||
|
||||
this.month_start_points.map(function (start, i) {
|
||||
var month_name = _this2.month_names[_this2.months[i]].substring(0, 3);
|
||||
var month_name = _this3.month_names[_this3.months[i]].substring(0, 3);
|
||||
|
||||
$.createSVG('text', {
|
||||
className: 'y-value-text',
|
||||
inside: _this2.domain_label_group,
|
||||
inside: _this3.domain_label_group,
|
||||
x: start + 12,
|
||||
y: 10,
|
||||
dy: '.32em',
|
||||
@ -3168,26 +3228,26 @@ var Heatmap = function (_BaseChart) {
|
||||
}, {
|
||||
key: 'bind_tooltip',
|
||||
value: function bind_tooltip() {
|
||||
var _this3 = this;
|
||||
var _this4 = this;
|
||||
|
||||
Array.prototype.slice.call(document.querySelectorAll(".data-group .day")).map(function (el) {
|
||||
el.addEventListener('mouseenter', function (e) {
|
||||
var count = e.target.getAttribute('data-value');
|
||||
var date_parts = e.target.getAttribute('data-date').split('-');
|
||||
|
||||
var month = _this3.month_names[parseInt(date_parts[1]) - 1].substring(0, 3);
|
||||
var month = _this4.month_names[parseInt(date_parts[1]) - 1].substring(0, 3);
|
||||
|
||||
var g_off = _this3.chart_wrapper.getBoundingClientRect(),
|
||||
var g_off = _this4.chart_wrapper.getBoundingClientRect(),
|
||||
p_off = e.target.getBoundingClientRect();
|
||||
|
||||
var width = parseInt(e.target.getAttribute('width'));
|
||||
var x = p_off.left - g_off.left + (width + 2) / 2;
|
||||
var y = p_off.top - g_off.top - (width + 2) / 2;
|
||||
var value = count + ' ' + _this3.count_label;
|
||||
var value = count + ' ' + _this4.count_label;
|
||||
var name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2];
|
||||
|
||||
_this3.tip.set_values(x, y, name, value, [], 1);
|
||||
_this3.tip.show_tip();
|
||||
_this4.tip.set_values(x, y, name, value, [], 1);
|
||||
_this4.tip.show_tip();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -3198,37 +3258,6 @@ var Heatmap = function (_BaseChart) {
|
||||
this.setup_values();
|
||||
this.bind_tooltip();
|
||||
}
|
||||
}, {
|
||||
key: 'get_distribution',
|
||||
value: function get_distribution() {
|
||||
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
var mapper_array = arguments[1];
|
||||
|
||||
var data_values = Object.keys(data).map(function (key) {
|
||||
return data[key];
|
||||
});
|
||||
var data_max_value = Math.max.apply(Math, toConsumableArray(data_values));
|
||||
|
||||
var distribution_step = 1 / (mapper_array.length - 1);
|
||||
var distribution = [];
|
||||
|
||||
mapper_array.map(function (color, i) {
|
||||
var checkpoint = data_max_value * (distribution_step * i);
|
||||
distribution.push(checkpoint);
|
||||
});
|
||||
|
||||
return distribution;
|
||||
}
|
||||
}, {
|
||||
key: 'get_max_checkpoint',
|
||||
value: function get_max_checkpoint(value, distribution) {
|
||||
return distribution.filter(function (d, i) {
|
||||
if (i === 1) {
|
||||
return distribution[0] < value;
|
||||
}
|
||||
return d <= value;
|
||||
}).length - 1;
|
||||
}
|
||||
}]);
|
||||
return Heatmap;
|
||||
}(BaseChart);
|
||||
|
||||
2
dist/frappe-charts.min.iife.js
vendored
2
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
2
docs/assets/js/frappe-charts.min.js
vendored
2
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
@ -360,7 +360,7 @@ let current_date = new Date();
|
||||
let timestamp = current_date.getTime()/1000;
|
||||
timestamp = Math.floor(timestamp - (timestamp % 86400)).toFixed(1); // convert to midnight
|
||||
for (var i = 0; i< 375; i++) {
|
||||
heatmap_data[parseInt(timestamp)] = Math.floor(Math.random() * 6);
|
||||
heatmap_data[parseInt(timestamp)] = Math.floor(Math.random() * 5);
|
||||
timestamp = Math.floor(timestamp - 86400).toFixed(1);
|
||||
}
|
||||
|
||||
@ -368,6 +368,7 @@ new Chart({
|
||||
parent: "#chart-heatmap",
|
||||
data: heatmap_data,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: 1 // default 0
|
||||
});
|
||||
@ -384,12 +385,61 @@ Array.prototype.slice.call(
|
||||
discrete_domains = 1;
|
||||
}
|
||||
|
||||
let colors = [];
|
||||
let colors_mode = document
|
||||
.querySelector('.heatmap-color-buttons .active')
|
||||
.getAttribute('data-color');
|
||||
if(colors_mode === 'halloween') {
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
}
|
||||
|
||||
new Chart({
|
||||
parent: "#chart-heatmap",
|
||||
data: heatmap_data,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: discrete_domains
|
||||
discrete_domains: discrete_domains,
|
||||
legend_colors: colors
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.heatmap-color-buttons button')
|
||||
).map(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let colors_mode = btn.getAttribute('data-color');
|
||||
let colors = [];
|
||||
|
||||
if(colors_mode === 'halloween') {
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
}
|
||||
|
||||
let discrete_domains = 1;
|
||||
|
||||
let view_mode = document
|
||||
.querySelector('.heatmap-mode-buttons .active')
|
||||
.getAttribute('data-mode');
|
||||
if(view_mode === 'continuous') {
|
||||
discrete_domains = 0;
|
||||
}
|
||||
|
||||
new Chart({
|
||||
parent: "#chart-heatmap",
|
||||
data: heatmap_data,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: discrete_domains,
|
||||
legend_colors: colors
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
|
||||
@ -227,7 +227,7 @@
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
And an Annual Heatmap
|
||||
And a Month-wise Heatmap
|
||||
</h6>
|
||||
<div id="chart-heatmap" class="border"
|
||||
style="overflow: scroll; text-align: center; padding: 20px;"></div>
|
||||
@ -235,12 +235,28 @@
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-mode="discrete">Discrete</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-mode="continuous">Continuous</button>
|
||||
</div>
|
||||
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="default">Default green</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart({
|
||||
parent: "#heatmap",
|
||||
data: heatmap_data, // object with date/timestamp-value pairs
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
discrete_domains: 1 // default 0
|
||||
data: heatmap_data, // object with date/timestamp-value pairs
|
||||
|
||||
discrete_domains: 1 // default: 0
|
||||
|
||||
start: start_date,
|
||||
// A Date object;
|
||||
// default: today's date in past year
|
||||
// for an annual heatmap
|
||||
|
||||
legend_colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'],
|
||||
// Set of five incremental colors,
|
||||
// beginning with a low-saturation color for zero data;
|
||||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import $ from '../utils/dom';
|
||||
import { add_days, get_dd_mm_yyyy, get_weeks_between } from '../utils/date-utils';
|
||||
import { calc_distribution, get_max_checkpoint } from '../utils/intervals';
|
||||
import { is_valid_color } from '../utils/colors';
|
||||
|
||||
export default class Heatmap extends BaseChart {
|
||||
constructor({
|
||||
@ -9,7 +11,8 @@ export default class Heatmap extends BaseChart {
|
||||
subdomain = '',
|
||||
data = {},
|
||||
discrete_domains = 0,
|
||||
count_label = ''
|
||||
count_label = '',
|
||||
legend_colors = []
|
||||
}) {
|
||||
super(arguments[0]);
|
||||
|
||||
@ -24,12 +27,33 @@ export default class Heatmap extends BaseChart {
|
||||
let today = new Date();
|
||||
this.start = start || add_days(today, 365);
|
||||
|
||||
this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
legend_colors = legend_colors.slice(0, 5);
|
||||
this.legend_colors = this.validate_colors(legend_colors)
|
||||
? legend_colors
|
||||
: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
|
||||
// Hardcoded for a fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
this.distribution_size = 5;
|
||||
|
||||
this.translate_x = 0;
|
||||
this.setup();
|
||||
}
|
||||
|
||||
validate_colors(colors) {
|
||||
if(colors.length < 5) return 0;
|
||||
|
||||
let valid = 1;
|
||||
colors.forEach(function(string) {
|
||||
if(!is_valid_color(string)) {
|
||||
valid = 0;
|
||||
console.warn('"' + string + '" is not a valid color.');
|
||||
}
|
||||
}, this);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
setup_base_values() {
|
||||
this.today = new Date();
|
||||
|
||||
@ -71,7 +95,10 @@ export default class Heatmap extends BaseChart {
|
||||
setup_values() {
|
||||
this.domain_label_group.textContent = '';
|
||||
this.data_groups.textContent = '';
|
||||
this.distribution = this.get_distribution(this.data, this.legend_colors);
|
||||
|
||||
let data_values = Object.keys(this.data).map(key => this.data[key]);
|
||||
this.distribution = calc_distribution(data_values, this.distribution_size);
|
||||
|
||||
this.month_names = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
@ -131,12 +158,14 @@ export default class Heatmap extends BaseChart {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if(data_value) {
|
||||
color_index = get_max_checkpoint(data_value, this.distribution);
|
||||
}
|
||||
|
||||
let x = 13 + (index + week_col_change) * 12;
|
||||
@ -204,7 +233,6 @@ export default class Heatmap extends BaseChart {
|
||||
dy: '.32em',
|
||||
innerHTML: month_name
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -247,28 +275,4 @@ export default class Heatmap extends BaseChart {
|
||||
this.setup_values();
|
||||
this.bind_tooltip();
|
||||
}
|
||||
|
||||
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, i) => {
|
||||
if(i === 1) {
|
||||
return distribution[0] < value;
|
||||
}
|
||||
return d <= value;
|
||||
}).length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,4 +15,9 @@ export function lighten_darken_color(col, amt) {
|
||||
let b = limit_color(((num >> 8) & 0x00FF) + amt);
|
||||
let g = limit_color((num & 0x0000FF) + amt);
|
||||
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||
}
|
||||
|
||||
export function is_valid_color(string) {
|
||||
// https://stackoverflow.com/a/8027444/6495043
|
||||
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string);
|
||||
}
|
||||
@ -32,6 +32,27 @@ export function clump_intervals(start, interval_size, count) {
|
||||
// //
|
||||
// }
|
||||
|
||||
export function calc_distribution(values, distribution_size) {
|
||||
// Assume non-negative values,
|
||||
// implying distribution minimum at zero
|
||||
|
||||
let data_max_value = Math.max(...values);
|
||||
|
||||
let distribution_step = 1 / (distribution_size - 1);
|
||||
let distribution = [];
|
||||
|
||||
for(var i = 0; i < distribution_size; i++) {
|
||||
let checkpoint = data_max_value * (distribution_step * i);
|
||||
distribution.push(checkpoint);
|
||||
}
|
||||
|
||||
return distribution;
|
||||
}
|
||||
|
||||
export function get_max_checkpoint(value, distribution) {
|
||||
return distribution.filter(d => d < value).length;
|
||||
}
|
||||
|
||||
export function calc_y_intervals(array) {
|
||||
//*** Where the magic happens ***
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user