[refactor] heatmap

This commit is contained in:
Prateeksha Singh 2018-04-01 22:23:52 +05:30
parent fee00250ef
commit 8acc304fad
15 changed files with 435 additions and 263 deletions

View File

@ -82,6 +82,8 @@ function fire(target, type, properties) {
return target.dispatchEvent(evt);
}
// https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/
class SvgTip {
constructor({
parent = null,
@ -467,7 +469,7 @@ function makeGradient(svgDefElem, color, lighter = false) {
return gradientId;
}
function makeHeatSquare(className, x, y, size, fill='none', data={}) {
function heatSquare(className, x, y, size, fill='none', data={}) {
let args = {
className: className,
x: x,
@ -1695,6 +1697,22 @@ let componentConfigs = {
);
}
},
percentageBars: {
layerClass: 'percentage-bars',
makeElements(data) {
// return data.sliceStrings.map((s, i) =>{
// let slice = makePath(s, 'pie-path', 'none', data.colors[i]);
// slice.style.transition = 'transform .3s;';
// return slice;
// });
},
animateElements(newData) {
// return this.store.map((slice, i) =>
// animatePathStr(slice, newData.sliceStrings[i])
// );
}
},
yAxis: {
layerClass: 'y axis',
makeElements(data) {
@ -1826,6 +1844,39 @@ let componentConfigs = {
}
},
heatDomain: {
layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
makeElements(data) {
let {colWidth, rowHeight, squareSize, xTranslate} = this.constants;
let x = xTranslate, y = 0;
this.serializedSubDomains = [];
data.cols.map(week => {
week.map((day, i) => {
let data = {
'data-date': day.YyyyMmDd,
'data-value': day.dataValue,
'data-day': i
};
let square = heatSquare('day', x, y, squareSize, day.fill, data);
this.serializedSubDomains.push(square);
y += rowHeight;
});
y = 0;
x += colWidth;
});
return this.serializedSubDomains;
},
animateElements(newData) {
// return this.store.map((slice, i) =>
// animatePathStr(slice, newData.sliceStrings[i])
// );
}
},
barGraph: {
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; },
makeElements(data) {
@ -2158,13 +2209,13 @@ function treatAsUtc(date) {
return result;
}
function getDdMmYyyy(date) {
function getYyyyMmDd(date) {
let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based
return [
(dd>9 ? '' : '0') + dd,
date.getFullYear(),
(mm>9 ? '' : '0') + mm,
date.getFullYear()
(dd>9 ? '' : '0') + dd
].join('-');
}
@ -2176,8 +2227,11 @@ function clone(date) {
function getWeeksBetween(startDate, endDate) {
return Math.ceil(getDaysBetween(startDate, endDate) / NO_OF_DAYS_IN_WEEK);
let weekStartDate = setDayToSunday(startDate);
return Math.ceil(getDaysBetween(weekStartDate, endDate) / NO_OF_DAYS_IN_WEEK);
}
function getDaysBetween(startDate, endDate) {
@ -2185,18 +2239,28 @@ function getDaysBetween(startDate, endDate) {
return (treatAsUtc(endDate) - treatAsUtc(startDate)) / millisecondsPerDay;
}
function areInSameMonth(startDate, endDate) {
return startDate.getMonth() === endDate.getMonth()
&& startDate.getFullYear() === endDate.getFullYear();
}
function getMonthName(i, short=false) {
let monthName = MONTH_NAMES[i];
return short ? monthName.slice(0, 3) : monthName;
}
function getLastDateInMonth (month, year) {
return new Date(year, month + 1, 0); // 0: last day in previous month
}
// mutates
function setDayToSunday(date) {
const day = date.getDay();
if(day !== NO_OF_DAYS_IN_WEEK) {
addDays(date, (-1) * day);
let newDate = clone(date);
const day = newDate.getDay();
if(day !== 0) {
addDays(newDate, (-1) * day);
}
return date;
return newDate;
}
// mutates
@ -2417,8 +2481,6 @@ function getMaxCheckpoint(value, distribution) {
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE;
const ROW_HEIGHT = COL_WIDTH;
const DAY_INCR = 1;
class Heatmap extends BaseChart {
constructor(parent, options) {
super(parent, options);
@ -2427,6 +2489,11 @@ class Heatmap extends BaseChart {
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
this.countLabel = options.countLabel || '';
let validStarts = ['Sunday', 'Monday'];
let startSubDomain = validStarts.includes(options.startSubDomain)
? options.startSubDomain : 'Sunday';
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
this.setup();
}
@ -2438,17 +2505,6 @@ class Heatmap extends BaseChart {
}
}
makeChartArea() {
super.makeChartArea();
this.domainLabelGroup = makeSVGGroup(this.drawArea,
'domain-label-group chart-label');
this.colGroups = makeSVGGroup(this.drawArea,
'data-groups',
`translate(0, 20)`
);
}
prepareData(data=this.data) {
if(data.start && data.end && data.start > data.end) {
throw new Error('Start date cannot be greater than end date.');
@ -2458,6 +2514,7 @@ class Heatmap extends BaseChart {
data.start = new Date();
data.start.setFullYear( data.start.getFullYear() - 1 );
}
console.log(data.start);
if(!data.end) { data.end = new Date(); }
data.dataPoints = data.dataPoints || {};
@ -2465,7 +2522,7 @@ class Heatmap extends BaseChart {
let points = {};
Object.keys(data.dataPoints).forEach(timestampSec$$1 => {
let date = new Date(timestampSec$$1 * NO_OF_MILLIS);
points[getDdMmYyyy(date)] = data.dataPoints[timestampSec$$1];
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec$$1];
});
data.dataPoints = points;
}
@ -2479,10 +2536,42 @@ class Heatmap extends BaseChart {
s.start = this.data.start;
s.end = this.data.end;
s.firstWeekStart = setDayToSunday(clone(s.start));
s.noOfWeeks = getWeeksBetween(s.firstWeekStart, s.end);
s.firstWeekStart = setDayToSunday(s.start);
s.noOfWeeks = getWeeksBetween(s.start, s.end);
s.distribution = calcDistribution(
Object.values(this.data.dataPoints), HEATMAP_DISTRIBUTION_SIZE);
s.domainConfigs = this.getDomains();
}
setupComponents() {
let s = this.state;
console.log(s.domainConfigs);
let componentConfigs = s.domainConfigs.map((config, i) => [
'heatDomain',
{
index: i,
colWidth: COL_WIDTH,
rowHeight: ROW_HEIGHT,
squareSize: HEATMAP_SQUARE_SIZE,
xTranslate: s.domainConfigs
.filter((config, j) => j < i)
.map(config => config.cols.length)
.reduce((a, b) => a + b, 0)
* COL_WIDTH
},
function() {
return s.domainConfigs[i];
}.bind(this)
]);
this.components = new Map(componentConfigs
.map(args => {
let component = getComponent(...args);
return [args[0], component];
}));
}
update(data) {
@ -2494,105 +2583,6 @@ class Heatmap extends BaseChart {
this.bindTooltip();
}
render() {
this.domainLabelGroup.textContent = '';
this.colGroups.textContent = '';
let currentWeekSunday = new Date(this.state.firstWeekStart);
this.currentWeekCol = 0;
this.currentMonth = currentWeekSunday.getMonth();
this.months = [this.currentMonth + ''];
this.monthWeeks = {}, this.monthStartPoints = [];
this.monthWeeks[this.currentMonth] = 0;
for(var i = 0; i < this.state.noOfWeeks; i++) {
let colGroup, monthChange = 0;
let day = new Date(currentWeekSunday);
[colGroup, monthChange] = this.getWeekSquaresGroup(day, this.currentWeekCol);
this.colGroups.appendChild(colGroup);
this.currentWeekCol += 1 + parseInt(this.discreteDomains && monthChange);
this.monthWeeks[this.currentMonth]++;
if(monthChange) {
this.currentMonth = (this.currentMonth + 1) % NO_OF_YEAR_MONTHS;
this.months.push(this.currentMonth + '');
this.monthWeeks[this.currentMonth] = 1;
}
addDays(currentWeekSunday, NO_OF_DAYS_IN_WEEK);
}
this.renderMonthLabels();
}
getWeekSquaresGroup(currentDate, currentWeekCol) {
let monthChange = 0;
let weekColChange = 0;
let colGroup = makeSVGGroup(this.colGroups, 'data-group');
for(var y = 0, i = 0; i < NO_OF_DAYS_IN_WEEK; i += DAY_INCR, y += ROW_HEIGHT) {
let ddmmyyyy = getDdMmYyyy(currentDate);
let dataValue = this.data.dataPoints[ddmmyyyy] || 0;
let colorIndex = getMaxCheckpoint(dataValue, this.state.distribution);
let x = (currentWeekCol + weekColChange) * COL_WIDTH;
let dataAttr = {
'data-date': ddmmyyyy,
'data-value': dataValue,
'data-day': currentDate.getDay()
};
let heatSquare = makeHeatSquare('day', x, y, HEATMAP_SQUARE_SIZE,
this.colors[colorIndex], dataAttr);
colGroup.appendChild(heatSquare);
let nextDate = new Date(currentDate);
addDays(nextDate, 1);
if(nextDate > this.state.end) break;
if(nextDate.getMonth() - currentDate.getMonth()) {
monthChange = 1;
if(this.discreteDomains) {
weekColChange = 1;
}
this.monthStartPoints.push((currentWeekCol + weekColChange) * COL_WIDTH);
}
currentDate = nextDate;
}
return [colGroup, monthChange];
}
renderMonthLabels() {
// this.first_month_label = 1;
// if (this.state.firstWeekStart.getDate() > 8) {
// this.first_month_label = 0;
// }
// this.last_month_label = 1;
// let first_month = this.months.shift();
// let first_month_start = this.monthStartPoints.shift();
// render first month if
// let last_month = this.months.pop();
// let last_month_start = this.monthStartPoints.pop();
// render last month if
this.months.shift();
this.monthStartPoints.shift();
this.months.pop();
this.monthStartPoints.pop();
this.monthStartPoints.map((start, i) => {
let month_name = getMonthName(this.months[i], true);
let text = makeText('y-value-text', start + COL_WIDTH, HEATMAP_SQUARE_SIZE, month_name);
this.domainLabelGroup.appendChild(text);
});
}
bindTooltip() {
Array.prototype.slice.call(
document.querySelectorAll(".data-group .day")
@ -2616,6 +2606,88 @@ class Heatmap extends BaseChart {
});
});
}
getDomains() {
let s = this.state;
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];
const [endMonth, endYear] = [s.end.getMonth(), s.end.getFullYear()];
const noOfMonths = (endMonth - startMonth + 1) + (endYear - startYear) * 12;
let domainConfigs = [];
let startOfMonth = clone(s.start);
for(var i = 0; i < noOfMonths; i++) {
let endDate = s.end;
if(!areInSameMonth(startOfMonth, s.end)) {
let [month, year] = [startOfMonth.getMonth(), startOfMonth.getFullYear()];
endDate = getLastDateInMonth(month, year);
}
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
addDays(endDate, 1);
startOfMonth = endDate;
}
return domainConfigs;
}
getDomainConfig(startDate, endDate='') {
let [month, year] = [startDate.getMonth(), startDate.getFullYear()];
let startOfWeek = setDayToSunday(startDate);
endDate = clone(endDate) || getLastDateInMonth(month, year);
let domainConfig = {
index: month,
cols: []
};
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate);
let cols = [];
for(var i = 0; i < noOfMonthWeeks; i++) {
const col = this.getCol(startOfWeek, month);
cols.push(col);
startOfWeek = new Date(col[NO_OF_DAYS_IN_WEEK - 1].dateStr);
addDays(startOfWeek, 1);
}
if(startOfWeek.getDay() === this.startSubDomainIndex) {
cols.push(new Array(NO_OF_DAYS_IN_WEEK).fill(0));
}
domainConfig.cols = cols;
return domainConfig;
}
getCol(startDate, month) {
// startDate is the start of week
let currentDate = clone(startDate);
let col = [];
for(var i = 0; i < NO_OF_DAYS_IN_WEEK; i++, addDays(currentDate, 1)) {
let config = 0;
if(currentDate.getMonth() === month) {
config = this.getSubDomainConfig(currentDate);
}
col.push(config);
}
return col;
}
getSubDomainConfig(date) {
let YyyyMmDd = getYyyyMmDd(date);
let dataValue = this.data.dataPoints[YyyyMmDd];
let config = {
YyyyMmDd: YyyyMmDd,
dataValue: dataValue || 0,
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)]
};
return config;
}
}
function dataPrep(data, type) {

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

@ -88,7 +88,6 @@ var SEC_IN_DAY = 86400;
var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// https://stackoverflow.com/a/11252167/6495043
function clone(date) {
@ -115,10 +114,19 @@ function timestampToMidnight(timestamp) {
// mutates
// mutates
function addDays(date, numberOfDays) {
date.setDate(date.getDate() + numberOfDays);
}
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850];
@ -231,6 +239,7 @@ var moonData = {
var today = new Date();
var start = clone(today);
addDays(start, 1);
var end = clone(today);
start.setFullYear(start.getFullYear() - 2);
end.setFullYear(end.getFullYear() - 1);
@ -244,7 +253,7 @@ startTs = timestampToMidnight(startTs);
endTs = timestampToMidnight(endTs, true);
while (startTs < endTs) {
dataPoints[parseInt(startTs)] = Math.floor(Math.random() * 5);
dataPoints[parseInt(startTs)] = Math.floor(Math.random() * 17);
startTs += SEC_IN_DAY;
}
@ -254,8 +263,6 @@ var heatmapData = {
end: end
};
// ================================================================================
var c1 = document.querySelector("#chart-composite-1");
var c2 = document.querySelector("#chart-composite-2");

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
import BaseChart from './BaseChart';
import { makeSVGGroup, makeHeatSquare, makeText } from '../utils/draw';
import { addDays, setDayToSunday, getDdMmYyyy, getWeeksBetween, getMonthName, clone,
import { makeSVGGroup, makeText } from '../utils/draw';
import { getComponent } from '../objects/ChartComponents';
import { addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone,
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils';
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
import { HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE,
@ -18,6 +19,11 @@ export default class Heatmap extends BaseChart {
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
this.countLabel = options.countLabel || '';
let validStarts = ['Sunday', 'Monday'];
let startSubDomain = validStarts.includes(options.startSubDomain)
? options.startSubDomain : 'Sunday';
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
this.setup();
}
@ -29,17 +35,6 @@ export default class Heatmap extends BaseChart {
}
}
makeChartArea() {
super.makeChartArea();
this.domainLabelGroup = makeSVGGroup(this.drawArea,
'domain-label-group chart-label');
this.colGroups = makeSVGGroup(this.drawArea,
'data-groups',
`translate(0, 20)`
);
}
prepareData(data=this.data) {
if(data.start && data.end && data.start > data.end) {
throw new Error('Start date cannot be greater than end date.');
@ -49,6 +44,7 @@ export default class Heatmap extends BaseChart {
data.start = new Date();
data.start.setFullYear( data.start.getFullYear() - 1 );
}
console.log(data.start);
if(!data.end) { data.end = new Date(); }
data.dataPoints = data.dataPoints || {};
@ -56,7 +52,7 @@ export default class Heatmap extends BaseChart {
let points = {};
Object.keys(data.dataPoints).forEach(timestampSec => {
let date = new Date(timestampSec * NO_OF_MILLIS);
points[getDdMmYyyy(date)] = data.dataPoints[timestampSec];
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
});
data.dataPoints = points;
}
@ -70,10 +66,42 @@ export default class Heatmap extends BaseChart {
s.start = this.data.start;
s.end = this.data.end;
s.firstWeekStart = setDayToSunday(clone(s.start));
s.noOfWeeks = getWeeksBetween(s.firstWeekStart, s.end);
s.firstWeekStart = setDayToSunday(s.start);
s.noOfWeeks = getWeeksBetween(s.start, s.end);
s.distribution = calcDistribution(
Object.values(this.data.dataPoints), HEATMAP_DISTRIBUTION_SIZE);
s.domainConfigs = this.getDomains();
}
setupComponents() {
let s = this.state;
console.log(s.domainConfigs);
let componentConfigs = s.domainConfigs.map((config, i) => [
'heatDomain',
{
index: i,
colWidth: COL_WIDTH,
rowHeight: ROW_HEIGHT,
squareSize: HEATMAP_SQUARE_SIZE,
xTranslate: s.domainConfigs
.filter((config, j) => j < i)
.map(config => config.cols.length)
.reduce((a, b) => a + b, 0)
* COL_WIDTH
},
function() {
return s.domainConfigs[i];
}.bind(this)
])
this.components = new Map(componentConfigs
.map(args => {
let component = getComponent(...args);
return [args[0], component];
}));
}
update(data) {
@ -85,106 +113,6 @@ export default class Heatmap extends BaseChart {
this.bindTooltip();
}
render() {
this.domainLabelGroup.textContent = '';
this.colGroups.textContent = '';
let currentWeekSunday = new Date(this.state.firstWeekStart);
this.currentWeekCol = 0;
this.currentMonth = currentWeekSunday.getMonth();
this.months = [this.currentMonth + ''];
this.monthWeeks = {},
this.monthStartPoints = [];
this.monthWeeks[this.currentMonth] = 0;
for(var i = 0; i < this.state.noOfWeeks; i++) {
let colGroup, monthChange = 0;
let day = new Date(currentWeekSunday);
[colGroup, monthChange] = this.getWeekSquaresGroup(day, this.currentWeekCol);
this.colGroups.appendChild(colGroup);
this.currentWeekCol += 1 + parseInt(this.discreteDomains && monthChange);
this.monthWeeks[this.currentMonth]++;
if(monthChange) {
this.currentMonth = (this.currentMonth + 1) % NO_OF_YEAR_MONTHS;
this.months.push(this.currentMonth + '');
this.monthWeeks[this.currentMonth] = 1;
}
addDays(currentWeekSunday, NO_OF_DAYS_IN_WEEK);
}
this.renderMonthLabels();
}
getWeekSquaresGroup(currentDate, currentWeekCol) {
let monthChange = 0;
let weekColChange = 0;
let colGroup = makeSVGGroup(this.colGroups, 'data-group');
for(var y = 0, i = 0; i < NO_OF_DAYS_IN_WEEK; i += DAY_INCR, y += ROW_HEIGHT) {
let ddmmyyyy = getDdMmYyyy(currentDate);
let dataValue = this.data.dataPoints[ddmmyyyy] || 0;
let colorIndex = getMaxCheckpoint(dataValue, this.state.distribution);
let x = (currentWeekCol + weekColChange) * COL_WIDTH;
let dataAttr = {
'data-date': ddmmyyyy,
'data-value': dataValue,
'data-day': currentDate.getDay()
};
let heatSquare = makeHeatSquare('day', x, y, HEATMAP_SQUARE_SIZE,
this.colors[colorIndex], dataAttr);
colGroup.appendChild(heatSquare);
let nextDate = new Date(currentDate);
addDays(nextDate, 1);
if(nextDate > this.state.end) break;
if(nextDate.getMonth() - currentDate.getMonth()) {
monthChange = 1;
if(this.discreteDomains) {
weekColChange = 1;
}
this.monthStartPoints.push((currentWeekCol + weekColChange) * COL_WIDTH);
}
currentDate = nextDate;
}
return [colGroup, monthChange];
}
renderMonthLabels() {
// this.first_month_label = 1;
// if (this.state.firstWeekStart.getDate() > 8) {
// this.first_month_label = 0;
// }
// this.last_month_label = 1;
// let first_month = this.months.shift();
// let first_month_start = this.monthStartPoints.shift();
// render first month if
// let last_month = this.months.pop();
// let last_month_start = this.monthStartPoints.pop();
// render last month if
this.months.shift();
this.monthStartPoints.shift();
this.months.pop();
this.monthStartPoints.pop();
this.monthStartPoints.map((start, i) => {
let month_name = getMonthName(this.months[i], true);
let text = makeText('y-value-text', start + COL_WIDTH, HEATMAP_SQUARE_SIZE, month_name);
this.domainLabelGroup.appendChild(text);
});
}
bindTooltip() {
Array.prototype.slice.call(
document.querySelectorAll(".data-group .day")
@ -208,4 +136,87 @@ export default class Heatmap extends BaseChart {
});
});
}
getDomains() {
let s = this.state;
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];
const [endMonth, endYear] = [s.end.getMonth(), s.end.getFullYear()];
const noOfMonths = (endMonth - startMonth + 1) + (endYear - startYear) * 12;
let domainConfigs = [];
let startOfMonth = clone(s.start);
for(var i = 0; i < noOfMonths; i++) {
let endDate = s.end;
if(!areInSameMonth(startOfMonth, s.end)) {
let [month, year] = [startOfMonth.getMonth(), startOfMonth.getFullYear()];
endDate = getLastDateInMonth(month, year);
}
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
addDays(endDate, 1);
startOfMonth = endDate;
}
return domainConfigs;
}
getDomainConfig(startDate, endDate='') {
let [month, year] = [startDate.getMonth(), startDate.getFullYear()];
let startOfWeek = setDayToSunday(startDate);
endDate = clone(endDate) || getLastDateInMonth(month, year);
let s = this.state;
let domainConfig = {
index: month,
cols: []
};
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate);
let cols = [];
for(var i = 0; i < noOfMonthWeeks; i++) {
const col = this.getCol(startOfWeek, month);
cols.push(col);
startOfWeek = new Date(col[NO_OF_DAYS_IN_WEEK - 1].dateStr);
addDays(startOfWeek, 1);
}
if(startOfWeek.getDay() === this.startSubDomainIndex) {
cols.push(new Array(NO_OF_DAYS_IN_WEEK).fill(0));
}
domainConfig.cols = cols;
return domainConfig;
}
getCol(startDate, month) {
// startDate is the start of week
let currentDate = clone(startDate);
let col = [];
for(var i = 0; i < NO_OF_DAYS_IN_WEEK; i++, addDays(currentDate, 1)) {
let config = 0;
if(currentDate.getMonth() === month) {
config = this.getSubDomainConfig(currentDate);
}
col.push(config);
}
return col;
}
getSubDomainConfig(date) {
let YyyyMmDd = getYyyyMmDd(date);
let dataValue = this.data.dataPoints[YyyyMmDd];
let config = {
YyyyMmDd: YyyyMmDd,
dataValue: dataValue || 0,
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)]
}
return config;
}
}

View File

@ -1,5 +1,5 @@
import { makeSVGGroup } from '../utils/draw';
import { makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths } from '../utils/draw';
import { makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths, heatSquare } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils';
import { translateHoriLine, translateVertLine, animateRegion, animateBar,
animateDot, animatePath, animatePathStr } from '../utils/animate';
@ -80,6 +80,22 @@ let componentConfigs = {
);
}
},
percentageBars: {
layerClass: 'percentage-bars',
makeElements(data) {
// return data.sliceStrings.map((s, i) =>{
// let slice = makePath(s, 'pie-path', 'none', data.colors[i]);
// slice.style.transition = 'transform .3s;';
// return slice;
// });
},
animateElements(newData) {
// return this.store.map((slice, i) =>
// animatePathStr(slice, newData.sliceStrings[i])
// );
}
},
yAxis: {
layerClass: 'y axis',
makeElements(data) {
@ -211,6 +227,39 @@ let componentConfigs = {
}
},
heatDomain: {
layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
makeElements(data) {
let {colWidth, rowHeight, squareSize, xTranslate} = this.constants;
let x = xTranslate, y = 0;
this.serializedSubDomains = [];
data.cols.map(week => {
week.map((day, i) => {
let data = {
'data-date': day.YyyyMmDd,
'data-value': day.dataValue,
'data-day': i
};
let square = heatSquare('day', x, y, squareSize, day.fill, data);
this.serializedSubDomains.push(square);
y += rowHeight;
})
y = 0;
x += colWidth;
})
return this.serializedSubDomains;
},
animateElements(newData) {
// return this.store.map((slice, i) =>
// animatePathStr(slice, newData.sliceStrings[i])
// );
}
},
barGraph: {
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; },
makeElements(data) {

View File

@ -18,13 +18,13 @@ function treatAsUtc(date) {
return result;
}
export function getDdMmYyyy(date) {
export function getYyyyMmDd(date) {
let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based
return [
(dd>9 ? '' : '0') + dd,
date.getFullYear(),
(mm>9 ? '' : '0') + mm,
date.getFullYear()
(dd>9 ? '' : '0') + dd
].join('-');
}
@ -44,8 +44,11 @@ export function timestampToMidnight(timestamp, roundAhead = false) {
return midnightTs;
}
export function getMonthsBetween(startDate, endDate) {}
export function getWeeksBetween(startDate, endDate) {
return Math.ceil(getDaysBetween(startDate, endDate) / NO_OF_DAYS_IN_WEEK);
let weekStartDate = setDayToSunday(startDate);
return Math.ceil(getDaysBetween(weekStartDate, endDate) / NO_OF_DAYS_IN_WEEK);
}
export function getDaysBetween(startDate, endDate) {
@ -53,18 +56,28 @@ export function getDaysBetween(startDate, endDate) {
return (treatAsUtc(endDate) - treatAsUtc(startDate)) / millisecondsPerDay;
}
export function areInSameMonth(startDate, endDate) {
return startDate.getMonth() === endDate.getMonth()
&& startDate.getFullYear() === endDate.getFullYear();
}
export function getMonthName(i, short=false) {
let monthName = MONTH_NAMES[i];
return short ? monthName.slice(0, 3) : monthName;
}
export function getLastDateInMonth (month, year) {
return new Date(year, month + 1, 0); // 0: last day in previous month
}
// mutates
export function setDayToSunday(date) {
const day = date.getDay();
if(day !== NO_OF_DAYS_IN_WEEK) {
addDays(date, (-1) * day);
let newDate = clone(date);
const day = newDate.getDay();
if(day !== 0) {
addDays(newDate, (-1) * day);
}
return date;
return newDate;
}
// mutates

View File

@ -109,3 +109,22 @@ export function fire(target, type, properties) {
return target.dispatchEvent(evt);
}
// https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/
export function forEachNode(nodeList, callback, scope) {
if(!nodeList) return;
for (var i = 0; i < nodeList.length; i++) {
callback.call(scope, nodeList[i], i);
}
}
export function activate($parent, $child, commonClass, activeClass='active', index = -1) {
let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`);
forEachNode($children, (node, i) => {
if(index >= 0 && i <= index) return;
node.classList.remove(activeClass);
})
$child.classList.add(activeClass);
}

View File

@ -131,7 +131,7 @@ export function makeGradient(svgDefElem, color, lighter = false) {
return gradientId;
}
export function makeHeatSquare(className, x, y, size, fill='none', data={}) {
export function heatSquare(className, x, y, size, fill='none', data={}) {
let args = {
className: className,
x: x,