[heatmap] subdomains and legend, biased random, blue theme

This commit is contained in:
Prateeksha Singh 2018-04-15 13:12:03 +05:30
parent 3877357bb3
commit eb3041ae4f
22 changed files with 304 additions and 118 deletions

View File

@ -130,6 +130,9 @@ const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
// More colors are difficult to parse visually // More colors are difficult to parse visually
const HEATMAP_DISTRIBUTION_SIZE = 5; const HEATMAP_DISTRIBUTION_SIZE = 5;
const HEATMAP_LEFT_MARGIN = 50;
const HEATMAP_TOP_MARGIN = 25;
const HEATMAP_SQUARE_SIZE = 10; const HEATMAP_SQUARE_SIZE = 10;
const HEATMAP_GUTTER_SIZE = 2; const HEATMAP_GUTTER_SIZE = 2;
@ -137,16 +140,18 @@ const DEFAULT_CHAR_WIDTH = 7;
const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5;
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; 'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey'];
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
const DEFAULT_COLORS = { const DEFAULT_COLORS = {
bar: DEFAULT_CHART_COLORS, bar: DEFAULT_CHART_COLORS,
line: DEFAULT_CHART_COLORS, line: DEFAULT_CHART_COLORS,
pie: DEFAULT_CHART_COLORS, pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS, percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS heatmap: HEATMAP_COLORS_GREEN
}; };
// Universal constants // Universal constants
@ -277,6 +282,10 @@ class SvgTip {
} }
} }
/**
* Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number
*/
function floatTwo(d) { function floatTwo(d) {
return parseFloat(d.toFixed(2)); return parseFloat(d.toFixed(2));
} }
@ -321,6 +330,9 @@ function getStringWidth(string, charWidth) {
// https://stackoverflow.com/a/29325222
function getPositionByAngle(angle, radius) { function getPositionByAngle(angle, radius) {
return { return {
x: Math.sin(angle * ANGLE_RATIO) * radius, x: Math.sin(angle * ANGLE_RATIO) * radius,
@ -620,14 +632,19 @@ function legendDot(x, y, size, fill='none', label) {
return group; return group;
} }
function makeText(className, x, y, content, fontSize = FONT_SIZE) { function makeText(className, x, y, content, options = {}) {
let fontSize = options.fontSize || FONT_SIZE;
let dy = options.dy !== undefined ? options.dy : (fontSize / 2);
let fill = options.fill || FONT_FILL;
let textAnchor = options.textAnchor || 'start';
return createSVG('text', { return createSVG('text', {
className: className, className: className,
x: x, x: x,
y: y, y: y,
dy: (fontSize / 2) + 'px', dy: dy + 'px',
'font-size': fontSize + 'px', 'font-size': fontSize + 'px',
fill: FONT_FILL, fill: fill,
'text-anchor': textAnchor,
innerHTML: content innerHTML: content
}); });
} }
@ -1449,7 +1466,7 @@ class BaseChart {
let titleAreaHeight = 0; let titleAreaHeight = 0;
let legendAreaHeight = 0; let legendAreaHeight = 0;
if(this.title.length) { if(this.title.length) {
titleAreaHeight = 30; titleAreaHeight = 40;
} }
if(this.config.showLegend) { if(this.config.showLegend) {
legendAreaHeight = 30; legendAreaHeight = 30;
@ -1468,10 +1485,13 @@ class BaseChart {
if(this.title.length) { if(this.title.length) {
this.titleEL = makeText( this.titleEL = makeText(
'title', 'title',
this.leftMargin - AXIS_TICK_LENGTH, this.leftMargin - AXIS_TICK_LENGTH * 6,
this.topMargin, this.topMargin,
this.title, this.title,
11 {
fontSize: 12,
fill: '#666666'
}
); );
this.svg.appendChild(this.titleEL); this.svg.appendChild(this.titleEL);
} }
@ -1664,6 +1684,9 @@ const MONTH_NAMES = ["January", "February", "March", "April", "May", "June",
const DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// https://stackoverflow.com/a/11252167/6495043 // https://stackoverflow.com/a/11252167/6495043
function treatAsUtc(date) { function treatAsUtc(date) {
let result = new Date(date); let result = new Date(date);
@ -1689,7 +1712,7 @@ function clone(date) {
// export function getMonthsBetween(startDate, endDate) {}
function getWeeksBetween(startDate, endDate) { function getWeeksBetween(startDate, endDate) {
let weekStartDate = setDayToSunday(startDate); let weekStartDate = setDayToSunday(startDate);
@ -1821,7 +1844,9 @@ let componentConfigs = {
}); });
}, },
animateElements(newData) { } animateElements(newData) {
if(newData) return [];
}
}, },
yAxis: { yAxis: {
layerClass: 'y axis', layerClass: 'y axis',
@ -1957,16 +1982,20 @@ let componentConfigs = {
heatDomain: { heatDomain: {
layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
makeElements(data) { makeElements(data) {
let {index, colWidth, rowHeight, squareSize, xTranslate, discreteDomains} = this.constants; let {index, colWidth, rowHeight, squareSize, xTranslate} = this.constants;
let monthNameHeight = 12; let monthNameHeight = -12;
let x = xTranslate, y = monthNameHeight; let x = xTranslate, y = 0;
this.serializedSubDomains = []; this.serializedSubDomains = [];
data.cols.map((week, weekNo) => { data.cols.map((week, weekNo) => {
if(weekNo === 1) { if(weekNo === 1) {
this.labels.push( this.labels.push(
makeText('domain-name', x, 0, getMonthName(index, true), 11) makeText('domain-name', x, monthNameHeight, getMonthName(index, true),
{
fontSize: 11
}
)
); );
} }
week.map((day, i) => { week.map((day, i) => {
@ -1981,14 +2010,16 @@ let componentConfigs = {
} }
y += rowHeight; y += rowHeight;
}); });
y = monthNameHeight; y = 0;
x += colWidth; x += colWidth;
}); });
return this.serializedSubDomains; return this.serializedSubDomains;
}, },
animateElements(newData) { } animateElements(newData) {
if(newData) return [];
}
}, },
barGraph: { barGraph: {
@ -2192,7 +2223,7 @@ class PercentageChart extends AggregationChart {
s.widths = []; s.widths = [];
let xPos = 0; let xPos = 0;
s.sliceTotals.map((value, i) => { s.sliceTotals.map((value) => {
let width = this.width * value / s.grandTotal; let width = this.width * value / s.grandTotal;
s.widths.push(width); s.widths.push(width);
s.xPositions.push(xPos); s.xPositions.push(xPos);
@ -2612,6 +2643,12 @@ class Heatmap extends BaseChart {
this.setup(); this.setup();
} }
setMargins() {
super.setMargins();
this.leftMargin = HEATMAP_LEFT_MARGIN;
this.topMargin = HEATMAP_TOP_MARGIN;
}
updateWidth() { updateWidth() {
this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH; this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH;
@ -2687,6 +2724,21 @@ class Heatmap extends BaseChart {
let component = getComponent(...args); let component = getComponent(...args);
return [args[0] + '-' + i, component]; return [args[0] + '-' + i, component];
})); }));
let y = 0;
DAY_NAMES_SHORT.forEach((dayName, i) => {
if([1, 3, 5].includes(i)) {
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName,
{
fontSize: HEATMAP_SQUARE_SIZE,
dy: 8,
textAnchor: 'end'
}
);
this.drawArea.appendChild(dayText);
}
y += ROW_HEIGHT;
});
} }
update(data) { update(data) {
@ -2714,8 +2766,8 @@ class Heatmap extends BaseChart {
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect();
let width = parseInt(e.target.getAttribute('width')); let width = parseInt(e.target.getAttribute('width'));
let x = pOff.left - gOff.left + (width+2)/2; let x = pOff.left - gOff.left + width/2;
let y = pOff.top - gOff.top - (width+2)/2; let y = pOff.top - gOff.top;
let value = count + ' ' + this.countLabel; let value = count + ' ' + this.countLabel;
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2];
@ -2726,6 +2778,36 @@ class Heatmap extends BaseChart {
}); });
} }
renderLegend() {
this.legendArea.textContent = '';
let x = 0;
let y = ROW_HEIGHT;
let lessText = makeText('subdomain-name', x, y, 'Less',
{
fontSize: HEATMAP_SQUARE_SIZE + 1,
dy: 9
}
);
x = (COL_WIDTH * 2) + COL_WIDTH/2;
this.legendArea.appendChild(lessText);
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i,
y, HEATMAP_SQUARE_SIZE, color);
this.legendArea.appendChild(square);
});
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4;
let moreText = makeText('subdomain-name', moreTextX, y, 'More',
{
fontSize: HEATMAP_SQUARE_SIZE + 1,
dy: 9
}
);
this.legendArea.appendChild(moreText);
}
getDomains() { getDomains() {
let s = this.state; let s = this.state;
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()]; const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];
@ -3275,7 +3357,7 @@ class AxisChart extends BaseChart {
let s = this.state; let s = this.state;
let formatY = this.config.formatTooltipY; // let formatY = this.config.formatTooltipY;
let formatX = this.config.formatTooltipX; let formatX = this.config.formatTooltipX;
let titles = s.xAxis.labels; let titles = s.xAxis.labels;
@ -3283,7 +3365,7 @@ class AxisChart extends BaseChart {
titles = titles.map(d=>formatX(d)); titles = titles.map(d=>formatX(d));
} }
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; // formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0;
// yVal = formatY ? formatY(set.values[i]) : set.values[i] // yVal = formatY ? formatY(set.values[i]) : set.values[i]
} }
@ -3495,6 +3577,7 @@ class AxisChart extends BaseChart {
// removeDataPoint(index = 0) {} // removeDataPoint(index = 0) {}
} }
// import MultiAxisChart from './charts/MultiAxisChart';
const chartTypes = { const chartTypes = {
// multiaxis: MultiAxisChart, // multiaxis: MultiAxisChart,
percentage: PercentageChart, percentage: PercentageChart,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
import { DAYS_IN_YEAR, SEC_IN_DAY, MONTH_NAMES_SHORT, clone, timestampToMidnight, timestampSec, addDays } from '../../../src/js/utils/date-utils'; import { SEC_IN_DAY, MONTH_NAMES_SHORT, clone, timestampToMidnight, timestampSec, addDays } from '../../../src/js/utils/date-utils';
import { getRandomBias } from '../../../src/js/utils/helpers';
// Composite Chart // Composite Chart
// ================================================================================ // ================================================================================
@ -176,8 +177,8 @@ export const moonData = {
let today = new Date(); let today = new Date();
let start = clone(today); let start = clone(today);
addDays(start, 1); addDays(start, 5);
let end = clone(today); let end = clone(start);
start.setFullYear( start.getFullYear() - 2 ); start.setFullYear( start.getFullYear() - 2 );
end.setFullYear( end.getFullYear() - 1 ); end.setFullYear( end.getFullYear() - 1 );
@ -190,7 +191,7 @@ startTs = timestampToMidnight(startTs);
endTs = timestampToMidnight(endTs, true); endTs = timestampToMidnight(endTs, true);
while (startTs < endTs) { while (startTs < endTs) {
dataPoints[parseInt(startTs)] = Math.floor(Math.random() * 17); dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1));
startTs += SEC_IN_DAY; startTs += SEC_IN_DAY;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
import { shuffle } from '../../../src/js/utils/helpers'; import { shuffle } from '../../../src/js/utils/helpers';
import { HEATMAP_COLORS_YELLOW, HEATMAP_COLORS_BLUE } from '../../../src/js/utils/constants';
import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData, import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData,
barCompositeData, typeData, trendsData, moonData, heatmapData } from './data'; barCompositeData, typeData, trendsData, moonData, heatmapData } from './data';
@ -263,15 +264,16 @@ eventsChart.parent.addEventListener('data-select', (e) => {
// Heatmap // Heatmap
// ================================================================================ // ================================================================================
let heatmap = new Chart("#chart-heatmap", { let heatmapArgs = {
title: "Monthly Distribution",
data: heatmapData, data: heatmapData,
type: 'heatmap', type: 'heatmap',
height: 115, height: 115,
discreteDomains: 1, discreteDomains: 1,
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'] colors: HEATMAP_COLORS_BLUE,
}); legendScale: [0, 1, 2, 4, 5]
};
console.log('heatmapData', Object.assign({}, heatmapData)); new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call( Array.prototype.slice.call(
document.querySelectorAll('.heatmap-mode-buttons button') document.querySelectorAll('.heatmap-mode-buttons button')
@ -290,17 +292,14 @@ Array.prototype.slice.call(
.querySelector('.heatmap-color-buttons .active') .querySelector('.heatmap-color-buttons .active')
.getAttribute('data-color'); .getAttribute('data-color');
if(colors_mode === 'halloween') { if(colors_mode === 'halloween') {
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; colors = HEATMAP_COLORS_YELLOW;
} else if (colors_mode === 'blue') {
colors = HEATMAP_COLORS_BLUE;
} }
new Chart("#chart-heatmap", { heatmapArgs.discreteDomains = discreteDomains;
data: heatmapData, heatmapArgs.colors = colors;
type: 'heatmap', new Chart("#chart-heatmap", heatmapArgs);
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
Array.prototype.slice.call( Array.prototype.slice.call(
btn.parentNode.querySelectorAll('button')).map(el => { btn.parentNode.querySelectorAll('button')).map(el => {
@ -319,7 +318,9 @@ Array.prototype.slice.call(
let colors = []; let colors = [];
if(colors_mode === 'halloween') { if(colors_mode === 'halloween') {
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; colors = HEATMAP_COLORS_YELLOW;
} else if (colors_mode === 'blue') {
colors = HEATMAP_COLORS_BLUE;
} }
let discreteDomains = 1; let discreteDomains = 1;
@ -331,14 +332,9 @@ Array.prototype.slice.call(
discreteDomains = 0; discreteDomains = 0;
} }
new Chart("#chart-heatmap", { heatmapArgs.discreteDomains = discreteDomains;
data: heatmapData, heatmapArgs.colors = colors;
type: 'heatmap', new Chart("#chart-heatmap", heatmapArgs);
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
Array.prototype.slice.call( Array.prototype.slice.call(
btn.parentNode.querySelectorAll('button')).map(el => { btn.parentNode.querySelectorAll('button')).map(el => {

View File

@ -41,6 +41,12 @@ function __$styleInject(css, ref) {
var HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
// Universal constants // Universal constants
/** /**
@ -84,6 +90,19 @@ function shuffle(array) {
* @param {Number} charWidth Width of single char in pixels * @param {Number} charWidth Width of single char in pixels
*/ */
// https://stackoverflow.com/a/29325222
function getRandomBias(min, max, bias, influence) {
var range = max - min;
var biasValue = range * bias + min;
var rnd = Math.random() * range + min,
// random in range
mix = Math.random() * influence; // random mixer
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
}
// Playing around with dates // Playing around with dates
@ -98,6 +117,10 @@ var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
// https://stackoverflow.com/a/11252167/6495043
function clone(date) { function clone(date) {
return new Date(date.getTime()); return new Date(date.getTime());
} }
@ -116,7 +139,7 @@ function timestampToMidnight(timestamp) {
return midnightTs; return midnightTs;
} }
// export function getMonthsBetween(startDate, endDate) {}
@ -136,6 +159,8 @@ function addDays(date, numberOfDays) {
date.setDate(date.getDate() + numberOfDays); date.setDate(date.getDate() + numberOfDays);
} }
// Composite Chart
// ================================================================================
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850]; var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850];
var lineCompositeData = { var lineCompositeData = {
@ -247,8 +272,8 @@ var moonData = {
var today = new Date(); var today = new Date();
var start = clone(today); var start = clone(today);
addDays(start, 1); addDays(start, 5);
var end = clone(today); var end = clone(start);
start.setFullYear(start.getFullYear() - 2); start.setFullYear(start.getFullYear() - 2);
end.setFullYear(end.getFullYear() - 1); end.setFullYear(end.getFullYear() - 1);
@ -261,7 +286,7 @@ startTs = timestampToMidnight(startTs);
endTs = timestampToMidnight(endTs, true); endTs = timestampToMidnight(endTs, true);
while (startTs < endTs) { while (startTs < endTs) {
dataPoints[parseInt(startTs)] = Math.floor(Math.random() * 17); dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1));
startTs += SEC_IN_DAY; startTs += SEC_IN_DAY;
} }
@ -520,15 +545,16 @@ eventsChart.parent.addEventListener('data-select', function (e) {
// Heatmap // Heatmap
// ================================================================================ // ================================================================================
var heatmap = new Chart("#chart-heatmap", { var heatmapArgs = {
title: "Monthly Distribution",
data: heatmapData, data: heatmapData,
type: 'heatmap', type: 'heatmap',
height: 115, height: 115,
discreteDomains: 1, discreteDomains: 1,
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'] colors: HEATMAP_COLORS_BLUE,
}); legendScale: [0, 1, 2, 4, 5]
};
console.log('heatmapData', Object.assign({}, heatmapData)); new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons button')).map(function (el) { Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons button')).map(function (el) {
el.addEventListener('click', function (e) { el.addEventListener('click', function (e) {
@ -543,17 +569,14 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons butt
var colors = []; var colors = [];
var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color'); var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color');
if (colors_mode === 'halloween') { if (colors_mode === 'halloween') {
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; colors = HEATMAP_COLORS_YELLOW;
} else if (colors_mode === 'blue') {
colors = HEATMAP_COLORS_BLUE;
} }
new Chart("#chart-heatmap", { heatmapArgs.discreteDomains = discreteDomains;
data: heatmapData, heatmapArgs.colors = colors;
type: 'heatmap', new Chart("#chart-heatmap", heatmapArgs);
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
el.classList.remove('active'); el.classList.remove('active');
@ -569,7 +592,9 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons but
var colors = []; var colors = [];
if (colors_mode === 'halloween') { if (colors_mode === 'halloween') {
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; colors = HEATMAP_COLORS_YELLOW;
} else if (colors_mode === 'blue') {
colors = HEATMAP_COLORS_BLUE;
} }
var discreteDomains = 1; var discreteDomains = 1;
@ -579,14 +604,9 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons but
discreteDomains = 0; discreteDomains = 0;
} }
new Chart("#chart-heatmap", { heatmapArgs.discreteDomains = discreteDomains;
data: heatmapData, heatmapArgs.colors = colors;
type: 'heatmap', new Chart("#chart-heatmap", heatmapArgs);
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
el.classList.remove('active'); el.classList.remove('active');

File diff suppressed because one or more lines are too long

View File

@ -188,7 +188,8 @@
</div> </div>
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group"> <div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group">
<button type="button" class="btn btn-sm btn-secondary" data-color="default">Default green</button> <button type="button" class="btn btn-sm btn-secondary" data-color="default">Default green</button>
<button type="button" class="btn btn-sm btn-secondary active" data-color="halloween">GitHub's Halloween</button> <button type="button" class="btn btn-sm btn-secondary active" data-color="blue">Blue</button>
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button>
</div> </div>
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", { <pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", {
type: 'heatmap', type: 'heatmap',
@ -202,7 +203,7 @@
// default: today's date in past year // default: today's date in past year
// for an annual heatmap // for an annual heatmap
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'], colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'],
// Set of five incremental colors, // Set of five incremental colors,
// beginning with a low-saturation color for zero data; // beginning with a low-saturation color for zero data;
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] // default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']

View File

@ -348,7 +348,7 @@ export default class AxisChart extends BaseChart {
let s = this.state; let s = this.state;
let formatY = this.config.formatTooltipY; // let formatY = this.config.formatTooltipY;
let formatX = this.config.formatTooltipX; let formatX = this.config.formatTooltipX;
let titles = s.xAxis.labels; let titles = s.xAxis.labels;
@ -356,7 +356,7 @@ export default class AxisChart extends BaseChart {
titles = titles.map(d=>formatX(d)); titles = titles.map(d=>formatX(d));
} }
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; // formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0;
// yVal = formatY ? formatY(set.values[i]) : set.values[i] // yVal = formatY ? formatY(set.values[i]) : set.values[i]
} }
@ -377,7 +377,7 @@ export default class AxisChart extends BaseChart {
} }
mapTooltipXPosition(relX) { mapTooltipXPosition(relX) {
let s = this.state, d = this.data; let s = this.state;
if(!s.yExtremes) return; if(!s.yExtremes) return;
let index = getClosestInArray(relX, s.xAxis.positions, true); let index = getClosestInArray(relX, s.xAxis.positions, true);

View File

@ -194,7 +194,7 @@ export default class BaseChart {
let titleAreaHeight = 0; let titleAreaHeight = 0;
let legendAreaHeight = 0; let legendAreaHeight = 0;
if(this.title.length) { if(this.title.length) {
titleAreaHeight = 30; titleAreaHeight = 40;
} }
if(this.config.showLegend) { if(this.config.showLegend) {
legendAreaHeight = 30; legendAreaHeight = 30;
@ -213,10 +213,13 @@ export default class BaseChart {
if(this.title.length) { if(this.title.length) {
this.titleEL = makeText( this.titleEL = makeText(
'title', 'title',
this.leftMargin - AXIS_TICK_LENGTH, this.leftMargin - AXIS_TICK_LENGTH * 6,
this.topMargin, this.topMargin,
this.title, this.title,
11 {
fontSize: 12,
fill: '#666666'
}
); );
this.svg.appendChild(this.titleEL); this.svg.appendChild(this.titleEL);
} }
@ -242,7 +245,7 @@ export default class BaseChart {
this.tip.offset = { this.tip.offset = {
x: x, x: x,
y: y y: y
} };
} }
renderLegend() {} renderLegend() {}

View File

@ -1,9 +1,10 @@
import BaseChart from './BaseChart'; import BaseChart from './BaseChart';
import { getComponent } from '../objects/ChartComponents'; import { getComponent } from '../objects/ChartComponents';
import { addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone, import { makeText, heatSquare } from '../utils/draw';
import { DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone,
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils'; NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils';
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals'; import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
import { HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, import { HEATMAP_TOP_MARGIN, HEATMAP_LEFT_MARGIN, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE,
HEATMAP_GUTTER_SIZE } from '../utils/constants'; HEATMAP_GUTTER_SIZE } from '../utils/constants';
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE; const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE;
@ -26,6 +27,12 @@ export default class Heatmap extends BaseChart {
this.setup(); this.setup();
} }
setMargins() {
super.setMargins();
this.leftMargin = HEATMAP_LEFT_MARGIN;
this.topMargin = HEATMAP_TOP_MARGIN;
}
updateWidth() { updateWidth() {
this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH; this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH;
@ -101,6 +108,21 @@ export default class Heatmap extends BaseChart {
let component = getComponent(...args); let component = getComponent(...args);
return [args[0] + '-' + i, component]; return [args[0] + '-' + i, component];
})); }));
let y = 0;
DAY_NAMES_SHORT.forEach((dayName, i) => {
if([1, 3, 5].includes(i)) {
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName,
{
fontSize: HEATMAP_SQUARE_SIZE,
dy: 8,
textAnchor: 'end'
}
);
this.drawArea.appendChild(dayText);
}
y += ROW_HEIGHT;
});
} }
update(data) { update(data) {
@ -128,8 +150,8 @@ export default class Heatmap extends BaseChart {
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect();
let width = parseInt(e.target.getAttribute('width')); let width = parseInt(e.target.getAttribute('width'));
let x = pOff.left - gOff.left + (width+2)/2; let x = pOff.left - gOff.left + width/2;
let y = pOff.top - gOff.top - (width+2)/2; let y = pOff.top - gOff.top;
let value = count + ' ' + this.countLabel; let value = count + ' ' + this.countLabel;
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2];
@ -140,6 +162,36 @@ export default class Heatmap extends BaseChart {
}); });
} }
renderLegend() {
this.legendArea.textContent = '';
let x = 0;
let y = ROW_HEIGHT;
let lessText = makeText('subdomain-name', x, y, 'Less',
{
fontSize: HEATMAP_SQUARE_SIZE + 1,
dy: 9
}
);
x = (COL_WIDTH * 2) + COL_WIDTH/2;
this.legendArea.appendChild(lessText);
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i,
y, HEATMAP_SQUARE_SIZE, color);
this.legendArea.appendChild(square);
});
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4;
let moreText = makeText('subdomain-name', moreTextX, y, 'More',
{
fontSize: HEATMAP_SQUARE_SIZE + 1,
dy: 9
}
);
this.legendArea.appendChild(moreText);
}
getDomains() { getDomains() {
let s = this.state; let s = this.state;
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()]; const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];

View File

@ -1,5 +1,5 @@
import AggregationChart from './AggregationChart'; import AggregationChart from './AggregationChart';
import { $, getOffset } from '../utils/dom'; import { getOffset } from '../utils/dom';
import { getComponent } from '../objects/ChartComponents'; import { getComponent } from '../objects/ChartComponents';
import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants'; import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants';
@ -52,7 +52,7 @@ export default class PercentageChart extends AggregationChart {
s.widths = []; s.widths = [];
let xPos = 0; let xPos = 0;
s.sliceTotals.map((value, i) => { s.sliceTotals.map((value) => {
let width = this.width * value / s.grandTotal; let width = this.width * value / s.grandTotal;
s.widths.push(width); s.widths.push(width);
s.xPositions.push(xPos); s.xPositions.push(xPos);

View File

@ -96,7 +96,9 @@ let componentConfigs = {
}); });
}, },
animateElements(newData) { } animateElements(newData) {
if(newData) return [];
}
}, },
yAxis: { yAxis: {
layerClass: 'y axis', layerClass: 'y axis',
@ -232,16 +234,20 @@ let componentConfigs = {
heatDomain: { heatDomain: {
layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
makeElements(data) { makeElements(data) {
let {index, colWidth, rowHeight, squareSize, xTranslate, discreteDomains} = this.constants; let {index, colWidth, rowHeight, squareSize, xTranslate} = this.constants;
let monthNameHeight = 12; let monthNameHeight = -12;
let x = xTranslate, y = monthNameHeight; let x = xTranslate, y = 0;
this.serializedSubDomains = []; this.serializedSubDomains = [];
data.cols.map((week, weekNo) => { data.cols.map((week, weekNo) => {
if(weekNo === 1) { if(weekNo === 1) {
this.labels.push( this.labels.push(
makeText('domain-name', x, 0, getMonthName(index, true), 11) makeText('domain-name', x, monthNameHeight, getMonthName(index, true),
{
fontSize: 11
}
)
); );
} }
week.map((day, i) => { week.map((day, i) => {
@ -255,15 +261,17 @@ let componentConfigs = {
this.serializedSubDomains.push(square); this.serializedSubDomains.push(square);
} }
y += rowHeight; y += rowHeight;
}) });
y = monthNameHeight; y = 0;
x += colWidth; x += colWidth;
}); });
return this.serializedSubDomains; return this.serializedSubDomains;
}, },
animateElements(newData) { } animateElements(newData) {
if(newData) return [];
}
}, },
barGraph: { barGraph: {

View File

@ -44,6 +44,9 @@ export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
// More colors are difficult to parse visually // More colors are difficult to parse visually
export const HEATMAP_DISTRIBUTION_SIZE = 5; export const HEATMAP_DISTRIBUTION_SIZE = 5;
export const HEATMAP_LEFT_MARGIN = 50;
export const HEATMAP_TOP_MARGIN = 25;
export const HEATMAP_SQUARE_SIZE = 10; export const HEATMAP_SQUARE_SIZE = 10;
export const HEATMAP_GUTTER_SIZE = 2; export const HEATMAP_GUTTER_SIZE = 2;
@ -51,16 +54,18 @@ export const DEFAULT_CHAR_WIDTH = 7;
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5;
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; 'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey'];
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
export const HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
export const HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
export const DEFAULT_COLORS = { export const DEFAULT_COLORS = {
bar: DEFAULT_CHART_COLORS, bar: DEFAULT_CHART_COLORS,
line: DEFAULT_CHART_COLORS, line: DEFAULT_CHART_COLORS,
pie: DEFAULT_CHART_COLORS, pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS, percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS heatmap: HEATMAP_COLORS_GREEN
}; };
// Universal constants // Universal constants

View File

@ -11,6 +11,9 @@ export const MONTH_NAMES = ["January", "February", "March", "April", "May", "Jun
export const MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; export const MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
export const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
export const DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// https://stackoverflow.com/a/11252167/6495043 // https://stackoverflow.com/a/11252167/6495043
function treatAsUtc(date) { function treatAsUtc(date) {
let result = new Date(date); let result = new Date(date);
@ -44,7 +47,7 @@ export function timestampToMidnight(timestamp, roundAhead = false) {
return midnightTs; return midnightTs;
} }
export function getMonthsBetween(startDate, endDate) {} // export function getMonthsBetween(startDate, endDate) {}
export function getWeeksBetween(startDate, endDate) { export function getWeeksBetween(startDate, endDate) {
let weekStartDate = setDayToSunday(startDate); let weekStartDate = setDayToSunday(startDate);

View File

@ -124,7 +124,7 @@ export function activate($parent, $child, commonClass, activeClass='active', ind
forEachNode($children, (node, i) => { forEachNode($children, (node, i) => {
if(index >= 0 && i <= index) return; if(index >= 0 && i <= index) return;
node.classList.remove(activeClass); node.classList.remove(activeClass);
}) });
$child.classList.add(activeClass); $child.classList.add(activeClass);
} }

View File

@ -230,14 +230,19 @@ export function legendDot(x, y, size, fill='none', label) {
return group; return group;
} }
export function makeText(className, x, y, content, fontSize = FONT_SIZE) { export function makeText(className, x, y, content, options = {}) {
let fontSize = options.fontSize || FONT_SIZE;
let dy = options.dy !== undefined ? options.dy : (fontSize / 2);
let fill = options.fill || FONT_FILL;
let textAnchor = options.textAnchor || 'start';
return createSVG('text', { return createSVG('text', {
className: className, className: className,
x: x, x: x,
y: y, y: y,
dy: (fontSize / 2) + 'px', dy: dy + 'px',
'font-size': fontSize + 'px', 'font-size': fontSize + 'px',
fill: FONT_FILL, fill: fill,
'text-anchor': textAnchor,
innerHTML: content innerHTML: content
}); });
} }

View File

@ -77,6 +77,15 @@ export function bindChange(obj, getFn, setFn) {
}); });
} }
// https://stackoverflow.com/a/29325222
export function getRandomBias(min, max, bias, influence) {
const range = max - min;
const biasValue = range * bias + min;
var rnd = Math.random() * range + min, // random in range
mix = Math.random() * influence; // random mixer
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
}
export function getPositionByAngle(angle, radius) { export function getPositionByAngle(angle, radius) {
return { return {
x: Math.sin(angle * ANGLE_RATIO) * radius, x: Math.sin(angle * ANGLE_RATIO) * radius,