@@ -130,6 +130,9 @@ const PERCENTAGE_BAR_DEFAULT_DEPTH = 2; | |||||
// More colors are difficult to parse visually | // More colors are difficult to parse visually | ||||
const HEATMAP_DISTRIBUTION_SIZE = 5; | const HEATMAP_DISTRIBUTION_SIZE = 5; | ||||
const HEATMAP_LEFT_MARGIN = 50; | |||||
const HEATMAP_TOP_MARGIN = 25; | |||||
const HEATMAP_SQUARE_SIZE = 10; | const HEATMAP_SQUARE_SIZE = 10; | ||||
const HEATMAP_GUTTER_SIZE = 2; | const HEATMAP_GUTTER_SIZE = 2; | ||||
@@ -137,16 +140,18 @@ const DEFAULT_CHAR_WIDTH = 7; | |||||
const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; | const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; | ||||
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||||
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | ||||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | 'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | ||||
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||||
const DEFAULT_COLORS = { | const DEFAULT_COLORS = { | ||||
bar: DEFAULT_CHART_COLORS, | bar: DEFAULT_CHART_COLORS, | ||||
line: DEFAULT_CHART_COLORS, | line: DEFAULT_CHART_COLORS, | ||||
pie: DEFAULT_CHART_COLORS, | pie: DEFAULT_CHART_COLORS, | ||||
percentage: DEFAULT_CHART_COLORS, | percentage: DEFAULT_CHART_COLORS, | ||||
heatmap: HEATMAP_COLORS | |||||
heatmap: HEATMAP_COLORS_GREEN | |||||
}; | }; | ||||
// Universal constants | // Universal constants | ||||
@@ -277,6 +282,10 @@ class SvgTip { | |||||
} | } | ||||
} | } | ||||
/** | |||||
* Returns the value of a number upto 2 decimal places. | |||||
* @param {Number} d Any number | |||||
*/ | |||||
function floatTwo(d) { | function floatTwo(d) { | ||||
return parseFloat(d.toFixed(2)); | return parseFloat(d.toFixed(2)); | ||||
} | } | ||||
@@ -321,10 +330,13 @@ function getStringWidth(string, charWidth) { | |||||
// https://stackoverflow.com/a/29325222 | |||||
function getPositionByAngle(angle, radius) { | function getPositionByAngle(angle, radius) { | ||||
return { | return { | ||||
x:Math.sin(angle * ANGLE_RATIO) * radius, | |||||
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, | function percentageBar(x, y, width, height, | ||||
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { | |||||
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { | |||||
let args = { | let args = { | ||||
className: 'percentage-bar', | className: 'percentage-bar', | ||||
@@ -620,14 +632,19 @@ function legendDot(x, y, size, fill='none', label) { | |||||
return group; | return group; | ||||
} | } | ||||
function makeText(className, x, y, content, fontSize = FONT_SIZE) { | |||||
function makeText(className, x, y, content, options = {}) { | |||||
let fontSize = options.fontSize || FONT_SIZE; | |||||
let dy = options.dy !== undefined ? options.dy : (fontSize / 2); | |||||
let fill = options.fill || FONT_FILL; | |||||
let textAnchor = options.textAnchor || 'start'; | |||||
return createSVG('text', { | return createSVG('text', { | ||||
className: className, | className: className, | ||||
x: x, | x: x, | ||||
y: y, | y: y, | ||||
dy: (fontSize / 2) + 'px', | |||||
dy: dy + 'px', | |||||
'font-size': fontSize + 'px', | 'font-size': fontSize + 'px', | ||||
fill: FONT_FILL, | |||||
fill: fill, | |||||
'text-anchor': textAnchor, | |||||
innerHTML: content | innerHTML: content | ||||
}); | }); | ||||
} | } | ||||
@@ -1449,7 +1466,7 @@ class BaseChart { | |||||
let titleAreaHeight = 0; | let titleAreaHeight = 0; | ||||
let legendAreaHeight = 0; | let legendAreaHeight = 0; | ||||
if(this.title.length) { | if(this.title.length) { | ||||
titleAreaHeight = 30; | |||||
titleAreaHeight = 40; | |||||
} | } | ||||
if(this.config.showLegend) { | if(this.config.showLegend) { | ||||
legendAreaHeight = 30; | legendAreaHeight = 30; | ||||
@@ -1468,10 +1485,13 @@ class BaseChart { | |||||
if(this.title.length) { | if(this.title.length) { | ||||
this.titleEL = makeText( | this.titleEL = makeText( | ||||
'title', | 'title', | ||||
this.leftMargin - AXIS_TICK_LENGTH, | |||||
this.leftMargin - AXIS_TICK_LENGTH * 6, | |||||
this.topMargin, | this.topMargin, | ||||
this.title, | this.title, | ||||
11 | |||||
{ | |||||
fontSize: 12, | |||||
fill: '#666666' | |||||
} | |||||
); | ); | ||||
this.svg.appendChild(this.titleEL); | this.svg.appendChild(this.titleEL); | ||||
} | } | ||||
@@ -1664,6 +1684,9 @@ const MONTH_NAMES = ["January", "February", "March", "April", "May", "June", | |||||
const DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | |||||
// https://stackoverflow.com/a/11252167/6495043 | // https://stackoverflow.com/a/11252167/6495043 | ||||
function treatAsUtc(date) { | function treatAsUtc(date) { | ||||
let result = new Date(date); | let result = new Date(date); | ||||
@@ -1689,7 +1712,7 @@ function clone(date) { | |||||
// export function getMonthsBetween(startDate, endDate) {} | |||||
function getWeeksBetween(startDate, endDate) { | function getWeeksBetween(startDate, endDate) { | ||||
let weekStartDate = setDayToSunday(startDate); | let weekStartDate = setDayToSunday(startDate); | ||||
@@ -1821,7 +1844,9 @@ let componentConfigs = { | |||||
}); | }); | ||||
}, | }, | ||||
animateElements(newData) { } | |||||
animateElements(newData) { | |||||
if(newData) return []; | |||||
} | |||||
}, | }, | ||||
yAxis: { | yAxis: { | ||||
layerClass: 'y axis', | layerClass: 'y axis', | ||||
@@ -1957,16 +1982,20 @@ let componentConfigs = { | |||||
heatDomain: { | heatDomain: { | ||||
layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, | layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, | ||||
makeElements(data) { | makeElements(data) { | ||||
let {index, colWidth, rowHeight, squareSize, xTranslate, discreteDomains} = this.constants; | |||||
let 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 = []; | this.serializedSubDomains = []; | ||||
data.cols.map((week, weekNo) => { | data.cols.map((week, weekNo) => { | ||||
if(weekNo === 1) { | if(weekNo === 1) { | ||||
this.labels.push( | this.labels.push( | ||||
makeText('domain-name', x, 0, getMonthName(index, true), 11) | |||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true), | |||||
{ | |||||
fontSize: 11 | |||||
} | |||||
) | |||||
); | ); | ||||
} | } | ||||
week.map((day, i) => { | week.map((day, i) => { | ||||
@@ -1981,14 +2010,16 @@ let componentConfigs = { | |||||
} | } | ||||
y += rowHeight; | y += rowHeight; | ||||
}); | }); | ||||
y = monthNameHeight; | |||||
y = 0; | |||||
x += colWidth; | x += colWidth; | ||||
}); | }); | ||||
return this.serializedSubDomains; | return this.serializedSubDomains; | ||||
}, | }, | ||||
animateElements(newData) { } | |||||
animateElements(newData) { | |||||
if(newData) return []; | |||||
} | |||||
}, | }, | ||||
barGraph: { | barGraph: { | ||||
@@ -2192,7 +2223,7 @@ class PercentageChart extends AggregationChart { | |||||
s.widths = []; | s.widths = []; | ||||
let xPos = 0; | let xPos = 0; | ||||
s.sliceTotals.map((value, i) => { | |||||
s.sliceTotals.map((value) => { | |||||
let width = this.width * value / s.grandTotal; | let width = this.width * value / s.grandTotal; | ||||
s.widths.push(width); | s.widths.push(width); | ||||
s.xPositions.push(xPos); | s.xPositions.push(xPos); | ||||
@@ -2612,6 +2643,12 @@ class Heatmap extends BaseChart { | |||||
this.setup(); | this.setup(); | ||||
} | } | ||||
setMargins() { | |||||
super.setMargins(); | |||||
this.leftMargin = HEATMAP_LEFT_MARGIN; | |||||
this.topMargin = HEATMAP_TOP_MARGIN; | |||||
} | |||||
updateWidth() { | updateWidth() { | ||||
this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH; | this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH; | ||||
@@ -2687,6 +2724,21 @@ class Heatmap extends BaseChart { | |||||
let component = getComponent(...args); | let component = getComponent(...args); | ||||
return [args[0] + '-' + i, component]; | return [args[0] + '-' + i, component]; | ||||
})); | })); | ||||
let y = 0; | |||||
DAY_NAMES_SHORT.forEach((dayName, i) => { | |||||
if([1, 3, 5].includes(i)) { | |||||
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName, | |||||
{ | |||||
fontSize: HEATMAP_SQUARE_SIZE, | |||||
dy: 8, | |||||
textAnchor: 'end' | |||||
} | |||||
); | |||||
this.drawArea.appendChild(dayText); | |||||
} | |||||
y += ROW_HEIGHT; | |||||
}); | |||||
} | } | ||||
update(data) { | update(data) { | ||||
@@ -2714,8 +2766,8 @@ class Heatmap extends BaseChart { | |||||
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); | let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); | ||||
let width = parseInt(e.target.getAttribute('width')); | let width = parseInt(e.target.getAttribute('width')); | ||||
let x = pOff.left - gOff.left + (width+2)/2; | |||||
let 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 value = count + ' ' + this.countLabel; | ||||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | ||||
@@ -2726,6 +2778,36 @@ class Heatmap extends BaseChart { | |||||
}); | }); | ||||
} | } | ||||
renderLegend() { | |||||
this.legendArea.textContent = ''; | |||||
let x = 0; | |||||
let y = ROW_HEIGHT; | |||||
let lessText = makeText('subdomain-name', x, y, 'Less', | |||||
{ | |||||
fontSize: HEATMAP_SQUARE_SIZE + 1, | |||||
dy: 9 | |||||
} | |||||
); | |||||
x = (COL_WIDTH * 2) + COL_WIDTH/2; | |||||
this.legendArea.appendChild(lessText); | |||||
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => { | |||||
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i, | |||||
y, HEATMAP_SQUARE_SIZE, color); | |||||
this.legendArea.appendChild(square); | |||||
}); | |||||
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4; | |||||
let moreText = makeText('subdomain-name', moreTextX, y, 'More', | |||||
{ | |||||
fontSize: HEATMAP_SQUARE_SIZE + 1, | |||||
dy: 9 | |||||
} | |||||
); | |||||
this.legendArea.appendChild(moreText); | |||||
} | |||||
getDomains() { | getDomains() { | ||||
let s = this.state; | let s = this.state; | ||||
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()]; | const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()]; | ||||
@@ -3275,7 +3357,7 @@ class AxisChart extends BaseChart { | |||||
let s = this.state; | let s = this.state; | ||||
let formatY = this.config.formatTooltipY; | |||||
// let formatY = this.config.formatTooltipY; | |||||
let formatX = this.config.formatTooltipX; | let formatX = this.config.formatTooltipX; | ||||
let titles = s.xAxis.labels; | let titles = s.xAxis.labels; | ||||
@@ -3283,7 +3365,7 @@ class AxisChart extends BaseChart { | |||||
titles = titles.map(d=>formatX(d)); | titles = titles.map(d=>formatX(d)); | ||||
} | } | ||||
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; | |||||
// formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; | |||||
// yVal = formatY ? formatY(set.values[i]) : set.values[i] | // yVal = formatY ? formatY(set.values[i]) : set.values[i] | ||||
} | } | ||||
@@ -3495,6 +3577,7 @@ class AxisChart extends BaseChart { | |||||
// removeDataPoint(index = 0) {} | // removeDataPoint(index = 0) {} | ||||
} | } | ||||
// import MultiAxisChart from './charts/MultiAxisChart'; | |||||
const chartTypes = { | const chartTypes = { | ||||
// multiaxis: MultiAxisChart, | // multiaxis: MultiAxisChart, | ||||
percentage: PercentageChart, | percentage: PercentageChart, | ||||
@@ -1,4 +1,5 @@ | |||||
import { DAYS_IN_YEAR, SEC_IN_DAY, MONTH_NAMES_SHORT, clone, timestampToMidnight, timestampSec, addDays } from '../../../src/js/utils/date-utils'; | |||||
import { SEC_IN_DAY, MONTH_NAMES_SHORT, clone, timestampToMidnight, timestampSec, addDays } from '../../../src/js/utils/date-utils'; | |||||
import { getRandomBias } from '../../../src/js/utils/helpers'; | |||||
// Composite Chart | // Composite Chart | ||||
// ================================================================================ | // ================================================================================ | ||||
@@ -176,8 +177,8 @@ export const moonData = { | |||||
let today = new Date(); | let today = new Date(); | ||||
let start = clone(today); | let start = clone(today); | ||||
addDays(start, 1); | |||||
let end = clone(today); | |||||
addDays(start, 5); | |||||
let end = clone(start); | |||||
start.setFullYear( start.getFullYear() - 2 ); | start.setFullYear( start.getFullYear() - 2 ); | ||||
end.setFullYear( end.getFullYear() - 1 ); | end.setFullYear( end.getFullYear() - 1 ); | ||||
@@ -190,7 +191,7 @@ startTs = timestampToMidnight(startTs); | |||||
endTs = timestampToMidnight(endTs, true); | endTs = timestampToMidnight(endTs, true); | ||||
while (startTs < endTs) { | while (startTs < endTs) { | ||||
dataPoints[parseInt(startTs)] = Math.floor(Math.random() * 17); | |||||
dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1)); | |||||
startTs += SEC_IN_DAY; | startTs += SEC_IN_DAY; | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
import { shuffle } from '../../../src/js/utils/helpers'; | import { shuffle } from '../../../src/js/utils/helpers'; | ||||
import { HEATMAP_COLORS_YELLOW, HEATMAP_COLORS_BLUE } from '../../../src/js/utils/constants'; | |||||
import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData, | import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData, | ||||
barCompositeData, typeData, trendsData, moonData, heatmapData } from './data'; | barCompositeData, typeData, trendsData, moonData, heatmapData } from './data'; | ||||
@@ -263,15 +264,16 @@ eventsChart.parent.addEventListener('data-select', (e) => { | |||||
// Heatmap | // Heatmap | ||||
// ================================================================================ | // ================================================================================ | ||||
let heatmap = new Chart("#chart-heatmap", { | |||||
let heatmapArgs = { | |||||
title: "Monthly Distribution", | |||||
data: heatmapData, | data: heatmapData, | ||||
type: 'heatmap', | type: 'heatmap', | ||||
height: 115, | height: 115, | ||||
discreteDomains: 1, | discreteDomains: 1, | ||||
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'] | |||||
}); | |||||
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( | Array.prototype.slice.call( | ||||
document.querySelectorAll('.heatmap-mode-buttons button') | document.querySelectorAll('.heatmap-mode-buttons button') | ||||
@@ -290,17 +292,14 @@ Array.prototype.slice.call( | |||||
.querySelector('.heatmap-color-buttons .active') | .querySelector('.heatmap-color-buttons .active') | ||||
.getAttribute('data-color'); | .getAttribute('data-color'); | ||||
if(colors_mode === 'halloween') { | if(colors_mode === 'halloween') { | ||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | } | ||||
new Chart("#chart-heatmap", { | |||||
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( | Array.prototype.slice.call( | ||||
btn.parentNode.querySelectorAll('button')).map(el => { | btn.parentNode.querySelectorAll('button')).map(el => { | ||||
@@ -319,7 +318,9 @@ Array.prototype.slice.call( | |||||
let colors = []; | let colors = []; | ||||
if(colors_mode === 'halloween') { | if(colors_mode === 'halloween') { | ||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | } | ||||
let discreteDomains = 1; | let discreteDomains = 1; | ||||
@@ -331,14 +332,9 @@ Array.prototype.slice.call( | |||||
discreteDomains = 0; | discreteDomains = 0; | ||||
} | } | ||||
new Chart("#chart-heatmap", { | |||||
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( | Array.prototype.slice.call( | ||||
btn.parentNode.querySelectorAll('button')).map(el => { | 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 | // Universal constants | ||||
/** | /** | ||||
@@ -84,6 +90,19 @@ function shuffle(array) { | |||||
* @param {Number} charWidth Width of single char in pixels | * @param {Number} charWidth Width of single char in pixels | ||||
*/ | */ | ||||
// https://stackoverflow.com/a/29325222 | |||||
function getRandomBias(min, max, bias, influence) { | |||||
var range = max - min; | |||||
var biasValue = range * bias + min; | |||||
var rnd = Math.random() * range + min, | |||||
// random in range | |||||
mix = Math.random() * influence; // random mixer | |||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias | |||||
} | |||||
// Playing around with dates | // Playing around with dates | ||||
@@ -98,6 +117,10 @@ var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", | |||||
// https://stackoverflow.com/a/11252167/6495043 | |||||
function clone(date) { | function clone(date) { | ||||
return new Date(date.getTime()); | return new Date(date.getTime()); | ||||
} | } | ||||
@@ -116,7 +139,7 @@ function timestampToMidnight(timestamp) { | |||||
return midnightTs; | return midnightTs; | ||||
} | } | ||||
// export function getMonthsBetween(startDate, endDate) {} | |||||
@@ -136,6 +159,8 @@ function addDays(date, numberOfDays) { | |||||
date.setDate(date.getDate() + numberOfDays); | date.setDate(date.getDate() + numberOfDays); | ||||
} | } | ||||
// Composite Chart | |||||
// ================================================================================ | |||||
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850]; | var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850]; | ||||
var lineCompositeData = { | var lineCompositeData = { | ||||
@@ -247,8 +272,8 @@ var moonData = { | |||||
var today = new Date(); | var today = new Date(); | ||||
var start = clone(today); | var start = clone(today); | ||||
addDays(start, 1); | |||||
var end = clone(today); | |||||
addDays(start, 5); | |||||
var end = clone(start); | |||||
start.setFullYear(start.getFullYear() - 2); | start.setFullYear(start.getFullYear() - 2); | ||||
end.setFullYear(end.getFullYear() - 1); | end.setFullYear(end.getFullYear() - 1); | ||||
@@ -261,7 +286,7 @@ startTs = timestampToMidnight(startTs); | |||||
endTs = timestampToMidnight(endTs, true); | endTs = timestampToMidnight(endTs, true); | ||||
while (startTs < endTs) { | while (startTs < endTs) { | ||||
dataPoints[parseInt(startTs)] = Math.floor(Math.random() * 17); | |||||
dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1)); | |||||
startTs += SEC_IN_DAY; | startTs += SEC_IN_DAY; | ||||
} | } | ||||
@@ -520,15 +545,16 @@ eventsChart.parent.addEventListener('data-select', function (e) { | |||||
// Heatmap | // Heatmap | ||||
// ================================================================================ | // ================================================================================ | ||||
var heatmap = new Chart("#chart-heatmap", { | |||||
var heatmapArgs = { | |||||
title: "Monthly Distribution", | |||||
data: heatmapData, | data: heatmapData, | ||||
type: 'heatmap', | type: 'heatmap', | ||||
height: 115, | height: 115, | ||||
discreteDomains: 1, | discreteDomains: 1, | ||||
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'] | |||||
}); | |||||
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) { | Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons button')).map(function (el) { | ||||
el.addEventListener('click', function (e) { | el.addEventListener('click', function (e) { | ||||
@@ -543,17 +569,14 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons butt | |||||
var colors = []; | var colors = []; | ||||
var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color'); | var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color'); | ||||
if (colors_mode === 'halloween') { | if (colors_mode === 'halloween') { | ||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | } | ||||
new Chart("#chart-heatmap", { | |||||
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) { | Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { | ||||
el.classList.remove('active'); | el.classList.remove('active'); | ||||
@@ -569,7 +592,9 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons but | |||||
var colors = []; | var colors = []; | ||||
if (colors_mode === 'halloween') { | if (colors_mode === 'halloween') { | ||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | } | ||||
var discreteDomains = 1; | var discreteDomains = 1; | ||||
@@ -579,14 +604,9 @@ Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons but | |||||
discreteDomains = 0; | discreteDomains = 0; | ||||
} | } | ||||
new Chart("#chart-heatmap", { | |||||
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) { | Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { | ||||
el.classList.remove('active'); | el.classList.remove('active'); | ||||
@@ -188,7 +188,8 @@ | |||||
</div> | </div> | ||||
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group"> | <div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group"> | ||||
<button type="button" class="btn btn-sm btn-secondary" data-color="default">Default green</button> | <button type="button" class="btn btn-sm btn-secondary" data-color="default">Default green</button> | ||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="halloween">GitHub's Halloween</button> | |||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="blue">Blue</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button> | |||||
</div> | </div> | ||||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", { | <pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", { | ||||
type: 'heatmap', | type: 'heatmap', | ||||
@@ -202,7 +203,7 @@ | |||||
// default: today's date in past year | // default: today's date in past year | ||||
// for an annual heatmap | // for an annual heatmap | ||||
colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'], | |||||
colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'], | |||||
// Set of five incremental colors, | // Set of five incremental colors, | ||||
// beginning with a low-saturation color for zero data; | // beginning with a low-saturation color for zero data; | ||||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] | // default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] | ||||
@@ -348,7 +348,7 @@ export default class AxisChart extends BaseChart { | |||||
let s = this.state; | let s = this.state; | ||||
let formatY = this.config.formatTooltipY; | |||||
// let formatY = this.config.formatTooltipY; | |||||
let formatX = this.config.formatTooltipX; | let formatX = this.config.formatTooltipX; | ||||
let titles = s.xAxis.labels; | let titles = s.xAxis.labels; | ||||
@@ -356,7 +356,7 @@ export default class AxisChart extends BaseChart { | |||||
titles = titles.map(d=>formatX(d)); | titles = titles.map(d=>formatX(d)); | ||||
} | } | ||||
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; | |||||
// formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; | |||||
// yVal = formatY ? formatY(set.values[i]) : set.values[i] | // yVal = formatY ? formatY(set.values[i]) : set.values[i] | ||||
} | } | ||||
@@ -377,7 +377,7 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
mapTooltipXPosition(relX) { | mapTooltipXPosition(relX) { | ||||
let s = this.state, d = this.data; | |||||
let s = this.state; | |||||
if(!s.yExtremes) return; | if(!s.yExtremes) return; | ||||
let index = getClosestInArray(relX, s.xAxis.positions, true); | let index = getClosestInArray(relX, s.xAxis.positions, true); | ||||
@@ -194,7 +194,7 @@ export default class BaseChart { | |||||
let titleAreaHeight = 0; | let titleAreaHeight = 0; | ||||
let legendAreaHeight = 0; | let legendAreaHeight = 0; | ||||
if(this.title.length) { | if(this.title.length) { | ||||
titleAreaHeight = 30; | |||||
titleAreaHeight = 40; | |||||
} | } | ||||
if(this.config.showLegend) { | if(this.config.showLegend) { | ||||
legendAreaHeight = 30; | legendAreaHeight = 30; | ||||
@@ -213,10 +213,13 @@ export default class BaseChart { | |||||
if(this.title.length) { | if(this.title.length) { | ||||
this.titleEL = makeText( | this.titleEL = makeText( | ||||
'title', | 'title', | ||||
this.leftMargin - AXIS_TICK_LENGTH, | |||||
this.leftMargin - AXIS_TICK_LENGTH * 6, | |||||
this.topMargin, | this.topMargin, | ||||
this.title, | this.title, | ||||
11 | |||||
{ | |||||
fontSize: 12, | |||||
fill: '#666666' | |||||
} | |||||
); | ); | ||||
this.svg.appendChild(this.titleEL); | this.svg.appendChild(this.titleEL); | ||||
} | } | ||||
@@ -242,7 +245,7 @@ export default class BaseChart { | |||||
this.tip.offset = { | this.tip.offset = { | ||||
x: x, | x: x, | ||||
y: y | y: y | ||||
} | |||||
}; | |||||
} | } | ||||
renderLegend() {} | renderLegend() {} | ||||
@@ -1,9 +1,10 @@ | |||||
import BaseChart from './BaseChart'; | import BaseChart from './BaseChart'; | ||||
import { getComponent } from '../objects/ChartComponents'; | import { getComponent } from '../objects/ChartComponents'; | ||||
import { addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone, | |||||
import { makeText, heatSquare } from '../utils/draw'; | |||||
import { DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone, | |||||
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils'; | NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils'; | ||||
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals'; | import { calcDistribution, getMaxCheckpoint } from '../utils/intervals'; | ||||
import { HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, | |||||
import { HEATMAP_TOP_MARGIN, HEATMAP_LEFT_MARGIN, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, | |||||
HEATMAP_GUTTER_SIZE } from '../utils/constants'; | HEATMAP_GUTTER_SIZE } from '../utils/constants'; | ||||
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE; | const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE; | ||||
@@ -26,6 +27,12 @@ export default class Heatmap extends BaseChart { | |||||
this.setup(); | this.setup(); | ||||
} | } | ||||
setMargins() { | |||||
super.setMargins(); | |||||
this.leftMargin = HEATMAP_LEFT_MARGIN; | |||||
this.topMargin = HEATMAP_TOP_MARGIN; | |||||
} | |||||
updateWidth() { | updateWidth() { | ||||
this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH; | this.baseWidth = (this.state.noOfWeeks + 99) * COL_WIDTH; | ||||
@@ -101,6 +108,21 @@ export default class Heatmap extends BaseChart { | |||||
let component = getComponent(...args); | let component = getComponent(...args); | ||||
return [args[0] + '-' + i, component]; | return [args[0] + '-' + i, component]; | ||||
})); | })); | ||||
let y = 0; | |||||
DAY_NAMES_SHORT.forEach((dayName, i) => { | |||||
if([1, 3, 5].includes(i)) { | |||||
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName, | |||||
{ | |||||
fontSize: HEATMAP_SQUARE_SIZE, | |||||
dy: 8, | |||||
textAnchor: 'end' | |||||
} | |||||
); | |||||
this.drawArea.appendChild(dayText); | |||||
} | |||||
y += ROW_HEIGHT; | |||||
}); | |||||
} | } | ||||
update(data) { | update(data) { | ||||
@@ -128,8 +150,8 @@ export default class Heatmap extends BaseChart { | |||||
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); | let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); | ||||
let width = parseInt(e.target.getAttribute('width')); | let width = parseInt(e.target.getAttribute('width')); | ||||
let x = pOff.left - gOff.left + (width+2)/2; | |||||
let 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 value = count + ' ' + this.countLabel; | ||||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | ||||
@@ -140,6 +162,36 @@ export default class Heatmap extends BaseChart { | |||||
}); | }); | ||||
} | } | ||||
renderLegend() { | |||||
this.legendArea.textContent = ''; | |||||
let x = 0; | |||||
let y = ROW_HEIGHT; | |||||
let lessText = makeText('subdomain-name', x, y, 'Less', | |||||
{ | |||||
fontSize: HEATMAP_SQUARE_SIZE + 1, | |||||
dy: 9 | |||||
} | |||||
); | |||||
x = (COL_WIDTH * 2) + COL_WIDTH/2; | |||||
this.legendArea.appendChild(lessText); | |||||
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => { | |||||
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i, | |||||
y, HEATMAP_SQUARE_SIZE, color); | |||||
this.legendArea.appendChild(square); | |||||
}); | |||||
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4; | |||||
let moreText = makeText('subdomain-name', moreTextX, y, 'More', | |||||
{ | |||||
fontSize: HEATMAP_SQUARE_SIZE + 1, | |||||
dy: 9 | |||||
} | |||||
); | |||||
this.legendArea.appendChild(moreText); | |||||
} | |||||
getDomains() { | getDomains() { | ||||
let s = this.state; | let s = this.state; | ||||
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()]; | const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()]; | ||||
@@ -1,5 +1,5 @@ | |||||
import AggregationChart from './AggregationChart'; | import AggregationChart from './AggregationChart'; | ||||
import { $, getOffset } from '../utils/dom'; | |||||
import { getOffset } from '../utils/dom'; | |||||
import { getComponent } from '../objects/ChartComponents'; | import { getComponent } from '../objects/ChartComponents'; | ||||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants'; | import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants'; | ||||
@@ -52,7 +52,7 @@ export default class PercentageChart extends AggregationChart { | |||||
s.widths = []; | s.widths = []; | ||||
let xPos = 0; | let xPos = 0; | ||||
s.sliceTotals.map((value, i) => { | |||||
s.sliceTotals.map((value) => { | |||||
let width = this.width * value / s.grandTotal; | let width = this.width * value / s.grandTotal; | ||||
s.widths.push(width); | s.widths.push(width); | ||||
s.xPositions.push(xPos); | s.xPositions.push(xPos); | ||||
@@ -96,7 +96,9 @@ let componentConfigs = { | |||||
}); | }); | ||||
}, | }, | ||||
animateElements(newData) { } | |||||
animateElements(newData) { | |||||
if(newData) return []; | |||||
} | |||||
}, | }, | ||||
yAxis: { | yAxis: { | ||||
layerClass: 'y axis', | layerClass: 'y axis', | ||||
@@ -232,16 +234,20 @@ let componentConfigs = { | |||||
heatDomain: { | heatDomain: { | ||||
layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, | layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, | ||||
makeElements(data) { | makeElements(data) { | ||||
let {index, colWidth, rowHeight, squareSize, xTranslate, discreteDomains} = this.constants; | |||||
let 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 = []; | this.serializedSubDomains = []; | ||||
data.cols.map((week, weekNo) => { | data.cols.map((week, weekNo) => { | ||||
if(weekNo === 1) { | if(weekNo === 1) { | ||||
this.labels.push( | this.labels.push( | ||||
makeText('domain-name', x, 0, getMonthName(index, true), 11) | |||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true), | |||||
{ | |||||
fontSize: 11 | |||||
} | |||||
) | |||||
); | ); | ||||
} | } | ||||
week.map((day, i) => { | week.map((day, i) => { | ||||
@@ -255,15 +261,17 @@ let componentConfigs = { | |||||
this.serializedSubDomains.push(square); | this.serializedSubDomains.push(square); | ||||
} | } | ||||
y += rowHeight; | y += rowHeight; | ||||
}) | |||||
y = monthNameHeight; | |||||
}); | |||||
y = 0; | |||||
x += colWidth; | x += colWidth; | ||||
}); | }); | ||||
return this.serializedSubDomains; | return this.serializedSubDomains; | ||||
}, | }, | ||||
animateElements(newData) { } | |||||
animateElements(newData) { | |||||
if(newData) return []; | |||||
} | |||||
}, | }, | ||||
barGraph: { | barGraph: { | ||||
@@ -44,6 +44,9 @@ export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2; | |||||
// More colors are difficult to parse visually | // More colors are difficult to parse visually | ||||
export const HEATMAP_DISTRIBUTION_SIZE = 5; | export const HEATMAP_DISTRIBUTION_SIZE = 5; | ||||
export const HEATMAP_LEFT_MARGIN = 50; | |||||
export const HEATMAP_TOP_MARGIN = 25; | |||||
export const HEATMAP_SQUARE_SIZE = 10; | export const HEATMAP_SQUARE_SIZE = 10; | ||||
export const HEATMAP_GUTTER_SIZE = 2; | export const HEATMAP_GUTTER_SIZE = 2; | ||||
@@ -51,16 +54,18 @@ export const DEFAULT_CHAR_WIDTH = 7; | |||||
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; | export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; | ||||
const HEATMAP_COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||||
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | ||||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | 'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | ||||
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||||
export const HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e']; | |||||
export const HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||||
export const DEFAULT_COLORS = { | export const DEFAULT_COLORS = { | ||||
bar: DEFAULT_CHART_COLORS, | bar: DEFAULT_CHART_COLORS, | ||||
line: DEFAULT_CHART_COLORS, | line: DEFAULT_CHART_COLORS, | ||||
pie: DEFAULT_CHART_COLORS, | pie: DEFAULT_CHART_COLORS, | ||||
percentage: DEFAULT_CHART_COLORS, | percentage: DEFAULT_CHART_COLORS, | ||||
heatmap: HEATMAP_COLORS | |||||
heatmap: HEATMAP_COLORS_GREEN | |||||
}; | }; | ||||
// Universal constants | // Universal constants | ||||
@@ -11,6 +11,9 @@ export const MONTH_NAMES = ["January", "February", "March", "April", "May", "Jun | |||||
export const MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | export const MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | ||||
export const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | |||||
export const DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | |||||
// https://stackoverflow.com/a/11252167/6495043 | // https://stackoverflow.com/a/11252167/6495043 | ||||
function treatAsUtc(date) { | function treatAsUtc(date) { | ||||
let result = new Date(date); | let result = new Date(date); | ||||
@@ -44,7 +47,7 @@ export function timestampToMidnight(timestamp, roundAhead = false) { | |||||
return midnightTs; | return midnightTs; | ||||
} | } | ||||
export function getMonthsBetween(startDate, endDate) {} | |||||
// export function getMonthsBetween(startDate, endDate) {} | |||||
export function getWeeksBetween(startDate, endDate) { | export function getWeeksBetween(startDate, endDate) { | ||||
let weekStartDate = setDayToSunday(startDate); | let weekStartDate = setDayToSunday(startDate); | ||||
@@ -124,7 +124,7 @@ export function activate($parent, $child, commonClass, activeClass='active', ind | |||||
forEachNode($children, (node, i) => { | forEachNode($children, (node, i) => { | ||||
if(index >= 0 && i <= index) return; | if(index >= 0 && i <= index) return; | ||||
node.classList.remove(activeClass); | node.classList.remove(activeClass); | ||||
}) | |||||
}); | |||||
$child.classList.add(activeClass); | $child.classList.add(activeClass); | ||||
} | } |
@@ -134,7 +134,7 @@ export function makeGradient(svgDefElem, color, lighter = false) { | |||||
} | } | ||||
export function percentageBar(x, y, width, height, | export function percentageBar(x, y, width, height, | ||||
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { | |||||
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { | |||||
let args = { | let args = { | ||||
className: 'percentage-bar', | className: 'percentage-bar', | ||||
@@ -230,14 +230,19 @@ export function legendDot(x, y, size, fill='none', label) { | |||||
return group; | return group; | ||||
} | } | ||||
export function makeText(className, x, y, content, fontSize = FONT_SIZE) { | |||||
export function makeText(className, x, y, content, options = {}) { | |||||
let fontSize = options.fontSize || FONT_SIZE; | |||||
let dy = options.dy !== undefined ? options.dy : (fontSize / 2); | |||||
let fill = options.fill || FONT_FILL; | |||||
let textAnchor = options.textAnchor || 'start'; | |||||
return createSVG('text', { | return createSVG('text', { | ||||
className: className, | className: className, | ||||
x: x, | x: x, | ||||
y: y, | y: y, | ||||
dy: (fontSize / 2) + 'px', | |||||
dy: dy + 'px', | |||||
'font-size': fontSize + 'px', | 'font-size': fontSize + 'px', | ||||
fill: FONT_FILL, | |||||
fill: fill, | |||||
'text-anchor': textAnchor, | |||||
innerHTML: content | innerHTML: content | ||||
}); | }); | ||||
} | } | ||||
@@ -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) { | export function getPositionByAngle(angle, radius) { | ||||
return { | 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, | |||||
}; | }; | ||||
} | } |