Sfoglia il codice sorgente

[refactor] heatmap

tags/1.2.0
Prateeksha Singh 7 anni fa
parent
commit
8acc304fad
15 ha cambiato i file con 435 aggiunte e 263 eliminazioni
  1. +196
    -124
      dist/frappe-charts.esm.js
  2. +1
    -1
      dist/frappe-charts.min.cjs.js
  3. +1
    -1
      dist/frappe-charts.min.esm.js
  4. +1
    -1
      dist/frappe-charts.min.iife.js
  5. +1
    -1
      dist/frappe-charts.min.iife.js.map
  6. +3
    -2
      docs/assets/js/data.js
  7. +1
    -1
      docs/assets/js/frappe-charts.min.js
  8. +1
    -1
      docs/assets/js/frappe-charts.min.js.map
  9. +11
    -4
      docs/assets/js/index.min.js
  10. +1
    -1
      docs/assets/js/index.min.js.map
  11. +127
    -116
      src/js/charts/Heatmap.js
  12. +50
    -1
      src/js/objects/ChartComponents.js
  13. +21
    -8
      src/js/utils/date-utils.js
  14. +19
    -0
      src/js/utils/dom.js
  15. +1
    -1
      src/js/utils/draw.js

+ 196
- 124
dist/frappe-charts.esm.js Vedi 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) {


+ 1
- 1
dist/frappe-charts.min.cjs.js
File diff soppresso perché troppo grande
Vedi File


+ 1
- 1
dist/frappe-charts.min.esm.js
File diff soppresso perché troppo grande
Vedi File


+ 1
- 1
dist/frappe-charts.min.iife.js
File diff soppresso perché troppo grande
Vedi File


+ 1
- 1
dist/frappe-charts.min.iife.js.map
File diff soppresso perché troppo grande
Vedi File


+ 3
- 2
docs/assets/js/data.js Vedi 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;
}



+ 1
- 1
docs/assets/js/frappe-charts.min.js
File diff soppresso perché troppo grande
Vedi File


+ 1
- 1
docs/assets/js/frappe-charts.min.js.map
File diff soppresso perché troppo grande
Vedi File


+ 11
- 4
docs/assets/js/index.min.js Vedi 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");



+ 1
- 1
docs/assets/js/index.min.js.map
File diff soppresso perché troppo grande
Vedi File


+ 127
- 116
src/js/charts/Heatmap.js Vedi 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;
}
}

+ 50
- 1
src/js/objects/ChartComponents.js Vedi 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) {


+ 21
- 8
src/js/utils/date-utils.js Vedi 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


+ 19
- 0
src/js/utils/dom.js Vedi 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);
}

+ 1
- 1
src/js/utils/draw.js Vedi 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,


Caricamento…
Annulla
Salva