[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
const HEATMAP_DISTRIBUTION_SIZE = 5;
const HEATMAP_LEFT_MARGIN = 50;
const HEATMAP_TOP_MARGIN = 25;
const HEATMAP_SQUARE_SIZE = 10;
const HEATMAP_GUTTER_SIZE = 2;
@ -137,16 +140,18 @@ const DEFAULT_CHAR_WIDTH = 7;
const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5;
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey'];
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
const DEFAULT_COLORS = {
bar: DEFAULT_CHART_COLORS,
line: DEFAULT_CHART_COLORS,
pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS
heatmap: HEATMAP_COLORS_GREEN
};
// 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) {
return parseFloat(d.toFixed(2));
}
@ -321,10 +330,13 @@ function getStringWidth(string, charWidth) {
// https://stackoverflow.com/a/29325222
function getPositionByAngle(angle, radius) {
return {
x:Math.sin(angle * ANGLE_RATIO) * radius,
y:Math.cos(angle * ANGLE_RATIO) * radius,
x: Math.sin(angle * ANGLE_RATIO) * radius,
y: Math.cos(angle * ANGLE_RATIO) * radius,
};
}
@ -524,7 +536,7 @@ function makeGradient(svgDefElem, color, lighter = false) {
}
function percentageBar(x, y, width, height,
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') {
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') {
let args = {
className: 'percentage-bar',
@ -620,14 +632,19 @@ function legendDot(x, y, size, fill='none', label) {
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', {
className: className,
x: x,
y: y,
dy: (fontSize / 2) + 'px',
dy: dy + 'px',
'font-size': fontSize + 'px',
fill: FONT_FILL,
fill: fill,
'text-anchor': textAnchor,
innerHTML: content
});
}
@ -1449,7 +1466,7 @@ class BaseChart {
let titleAreaHeight = 0;
let legendAreaHeight = 0;
if(this.title.length) {
titleAreaHeight = 30;
titleAreaHeight = 40;
}
if(this.config.showLegend) {
legendAreaHeight = 30;
@ -1468,10 +1485,13 @@ class BaseChart {
if(this.title.length) {
this.titleEL = makeText(
'title',
this.leftMargin - AXIS_TICK_LENGTH,
this.leftMargin - AXIS_TICK_LENGTH * 6,
this.topMargin,
this.title,
11
{
fontSize: 12,
fill: '#666666'
}
);
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
function treatAsUtc(date) {
let result = new Date(date);
@ -1689,7 +1712,7 @@ function clone(date) {
// export function getMonthsBetween(startDate, endDate) {}
function getWeeksBetween(startDate, endDate) {
let weekStartDate = setDayToSunday(startDate);
@ -1821,7 +1844,9 @@ let componentConfigs = {
});
},
animateElements(newData) { }
animateElements(newData) {
if(newData) return [];
}
},
yAxis: {
layerClass: 'y axis',
@ -1957,16 +1982,20 @@ let componentConfigs = {
heatDomain: {
layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
makeElements(data) {
let {index, colWidth, rowHeight, squareSize, xTranslate, discreteDomains} = this.constants;
let monthNameHeight = 12;
let x = xTranslate, y = monthNameHeight;
let {index, colWidth, rowHeight, squareSize, xTranslate} = this.constants;
let monthNameHeight = -12;
let x = xTranslate, y = 0;
this.serializedSubDomains = [];
data.cols.map((week, weekNo) => {
if(weekNo === 1) {
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) => {
@ -1981,14 +2010,16 @@ let componentConfigs = {
}
y += rowHeight;
});
y = monthNameHeight;
y = 0;
x += colWidth;
});
return this.serializedSubDomains;
},
animateElements(newData) { }
animateElements(newData) {
if(newData) return [];
}
},
barGraph: {
@ -2192,7 +2223,7 @@ class PercentageChart extends AggregationChart {
s.widths = [];
let xPos = 0;
s.sliceTotals.map((value, i) => {
s.sliceTotals.map((value) => {
let width = this.width * value / s.grandTotal;
s.widths.push(width);
s.xPositions.push(xPos);
@ -2612,6 +2643,12 @@ class Heatmap extends BaseChart {
this.setup();
}
setMargins() {
super.setMargins();
this.leftMargin = HEATMAP_LEFT_MARGIN;
this.topMargin = HEATMAP_TOP_MARGIN;
}
updateWidth() {
this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH;
@ -2687,6 +2724,21 @@ class Heatmap extends BaseChart {
let component = getComponent(...args);
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) {
@ -2714,8 +2766,8 @@ class Heatmap extends BaseChart {
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect();
let width = parseInt(e.target.getAttribute('width'));
let x = pOff.left - gOff.left + (width+2)/2;
let y = pOff.top - gOff.top - (width+2)/2;
let x = pOff.left - gOff.left + width/2;
let y = pOff.top - gOff.top;
let value = count + ' ' + this.countLabel;
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() {
let s = this.state;
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];
@ -3275,7 +3357,7 @@ class AxisChart extends BaseChart {
let s = this.state;
let formatY = this.config.formatTooltipY;
// let formatY = this.config.formatTooltipY;
let formatX = this.config.formatTooltipX;
let titles = s.xAxis.labels;
@ -3283,7 +3365,7 @@ class AxisChart extends BaseChart {
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]
}
@ -3495,6 +3577,7 @@ class AxisChart extends BaseChart {
// removeDataPoint(index = 0) {}
}
// import MultiAxisChart from './charts/MultiAxisChart';
const chartTypes = {
// multiaxis: MultiAxisChart,
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
// ================================================================================
@ -176,8 +177,8 @@ export const moonData = {
let today = new Date();
let start = clone(today);
addDays(start, 1);
let end = clone(today);
addDays(start, 5);
let end = clone(start);
start.setFullYear( start.getFullYear() - 2 );
end.setFullYear( end.getFullYear() - 1 );
@ -190,7 +191,7 @@ startTs = timestampToMidnight(startTs);
endTs = timestampToMidnight(endTs, true);
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;
}

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 { HEATMAP_COLORS_YELLOW, HEATMAP_COLORS_BLUE } from '../../../src/js/utils/constants';
import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData,
barCompositeData, typeData, trendsData, moonData, heatmapData } from './data';
@ -263,15 +264,16 @@ eventsChart.parent.addEventListener('data-select', (e) => {
// Heatmap
// ================================================================================
let heatmap = new Chart("#chart-heatmap", {
let heatmapArgs = {
title: "Monthly Distribution",
data: heatmapData,
type: 'heatmap',
height: 115,
discreteDomains: 1,
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']
});
console.log('heatmapData', Object.assign({}, heatmapData));
colors: HEATMAP_COLORS_BLUE,
legendScale: [0, 1, 2, 4, 5]
};
new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(
document.querySelectorAll('.heatmap-mode-buttons button')
@ -290,17 +292,14 @@ Array.prototype.slice.call(
.querySelector('.heatmap-color-buttons .active')
.getAttribute('data-color');
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", {
data: heatmapData,
type: 'heatmap',
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
heatmapArgs.discreteDomains = discreteDomains;
heatmapArgs.colors = colors;
new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(
btn.parentNode.querySelectorAll('button')).map(el => {
@ -319,7 +318,9 @@ Array.prototype.slice.call(
let colors = [];
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;
@ -331,14 +332,9 @@ Array.prototype.slice.call(
discreteDomains = 0;
}
new Chart("#chart-heatmap", {
data: heatmapData,
type: 'heatmap',
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
heatmapArgs.discreteDomains = discreteDomains;
heatmapArgs.colors = colors;
new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(
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
/**
@ -84,6 +90,19 @@ function shuffle(array) {
* @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
@ -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) {
return new Date(date.getTime());
}
@ -116,7 +139,7 @@ function timestampToMidnight(timestamp) {
return midnightTs;
}
// export function getMonthsBetween(startDate, endDate) {}
@ -136,6 +159,8 @@ function addDays(date, numberOfDays) {
date.setDate(date.getDate() + numberOfDays);
}
// Composite Chart
// ================================================================================
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850];
var lineCompositeData = {
@ -247,8 +272,8 @@ var moonData = {
var today = new Date();
var start = clone(today);
addDays(start, 1);
var end = clone(today);
addDays(start, 5);
var end = clone(start);
start.setFullYear(start.getFullYear() - 2);
end.setFullYear(end.getFullYear() - 1);
@ -261,7 +286,7 @@ startTs = timestampToMidnight(startTs);
endTs = timestampToMidnight(endTs, true);
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;
}
@ -520,15 +545,16 @@ eventsChart.parent.addEventListener('data-select', function (e) {
// Heatmap
// ================================================================================
var heatmap = new Chart("#chart-heatmap", {
var heatmapArgs = {
title: "Monthly Distribution",
data: heatmapData,
type: 'heatmap',
height: 115,
discreteDomains: 1,
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']
});
console.log('heatmapData', Object.assign({}, heatmapData));
colors: HEATMAP_COLORS_BLUE,
legendScale: [0, 1, 2, 4, 5]
};
new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons button')).map(function (el) {
el.addEventListener('click', function (e) {
@ -543,17 +569,14 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons butt
var colors = [];
var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color');
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", {
data: heatmapData,
type: 'heatmap',
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
heatmapArgs.discreteDomains = discreteDomains;
heatmapArgs.colors = colors;
new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
el.classList.remove('active');
@ -569,7 +592,9 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons but
var colors = [];
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;
@ -579,14 +604,9 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons but
discreteDomains = 0;
}
new Chart("#chart-heatmap", {
data: heatmapData,
type: 'heatmap',
legendScale: [0, 1, 2, 4, 5],
height: 115,
discreteDomains: discreteDomains,
colors: colors
});
heatmapArgs.discreteDomains = discreteDomains;
heatmapArgs.colors = colors;
new Chart("#chart-heatmap", heatmapArgs);
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
el.classList.remove('active');

File diff suppressed because one or more lines are too long

View File

@ -188,7 +188,8 @@
</div>
<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 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>
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", {
type: 'heatmap',
@ -202,7 +203,7 @@
// default: today's date in past year
// for an annual heatmap
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'],
colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'],
// Set of five incremental colors,
// beginning with a low-saturation color for zero data;
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']

View File

@ -348,7 +348,7 @@ export default class AxisChart extends BaseChart {
let s = this.state;
let formatY = this.config.formatTooltipY;
// let formatY = this.config.formatTooltipY;
let formatX = this.config.formatTooltipX;
let titles = s.xAxis.labels;
@ -356,7 +356,7 @@ export default class AxisChart extends BaseChart {
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]
}
@ -377,7 +377,7 @@ export default class AxisChart extends BaseChart {
}
mapTooltipXPosition(relX) {
let s = this.state, d = this.data;
let s = this.state;
if(!s.yExtremes) return;
let index = getClosestInArray(relX, s.xAxis.positions, true);

View File

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

View File

@ -1,9 +1,10 @@
import BaseChart from './BaseChart';
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';
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';
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE;
@ -26,6 +27,12 @@ export default class Heatmap extends BaseChart {
this.setup();
}
setMargins() {
super.setMargins();
this.leftMargin = HEATMAP_LEFT_MARGIN;
this.topMargin = HEATMAP_TOP_MARGIN;
}
updateWidth() {
this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH;
@ -101,6 +108,21 @@ export default class Heatmap extends BaseChart {
let component = getComponent(...args);
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) {
@ -128,8 +150,8 @@ export default class Heatmap extends BaseChart {
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect();
let width = parseInt(e.target.getAttribute('width'));
let x = pOff.left - gOff.left + (width+2)/2;
let y = pOff.top - gOff.top - (width+2)/2;
let x = pOff.left - gOff.left + width/2;
let y = pOff.top - gOff.top;
let value = count + ' ' + this.countLabel;
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() {
let s = this.state;
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];

View File

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

View File

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

View File

@ -44,6 +44,9 @@ export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
// More colors are difficult to parse visually
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_GUTTER_SIZE = 2;
@ -51,16 +54,18 @@ export const DEFAULT_CHAR_WIDTH = 7;
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',
'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 = {
bar: DEFAULT_CHART_COLORS,
line: DEFAULT_CHART_COLORS,
pie: DEFAULT_CHART_COLORS,
percentage: DEFAULT_CHART_COLORS,
heatmap: HEATMAP_COLORS
heatmap: HEATMAP_COLORS_GREEN
};
// 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 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
function treatAsUtc(date) {
let result = new Date(date);
@ -44,7 +47,7 @@ export function timestampToMidnight(timestamp, roundAhead = false) {
return midnightTs;
}
export function getMonthsBetween(startDate, endDate) {}
// export function getMonthsBetween(startDate, endDate) {}
export function getWeeksBetween(startDate, endDate) {
let weekStartDate = setDayToSunday(startDate);

View File

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

View File

@ -134,7 +134,7 @@ export function makeGradient(svgDefElem, color, lighter = false) {
}
export function percentageBar(x, y, width, height,
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') {
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') {
let args = {
className: 'percentage-bar',
@ -230,14 +230,19 @@ export function legendDot(x, y, size, fill='none', label) {
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', {
className: className,
x: x,
y: y,
dy: (fontSize / 2) + 'px',
dy: dy + 'px',
'font-size': fontSize + 'px',
fill: FONT_FILL,
fill: fill,
'text-anchor': textAnchor,
innerHTML: content
});
}

View File

@ -77,9 +77,18 @@ 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) {
return {
x:Math.sin(angle * ANGLE_RATIO) * radius,
y:Math.cos(angle * ANGLE_RATIO) * radius,
x: Math.sin(angle * ANGLE_RATIO) * radius,
y: Math.cos(angle * ANGLE_RATIO) * radius,
};
}