@@ -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, | |||
@@ -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; | |||
} | |||
@@ -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 => { | |||
@@ -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'); | |||
@@ -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'] | |||
@@ -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); | |||
@@ -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() {} | |||
@@ -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()]; | |||
@@ -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); | |||
@@ -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: { | |||
@@ -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 | |||
@@ -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); | |||
@@ -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); | |||
} |
@@ -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 | |||
}); | |||
} | |||
@@ -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, | |||
}; | |||
} |