@@ -133,6 +133,9 @@ class SvgTip { | |||
fill() { | |||
let title; | |||
if(this.index) { | |||
this.container.setAttribute('data-point-index', this.index); | |||
} | |||
if(this.titleValueFirst) { | |||
title = `<strong>${this.titleValue}</strong>${this.titleName}`; | |||
} else { | |||
@@ -179,13 +182,14 @@ class SvgTip { | |||
} | |||
} | |||
setValues(x, y, titleName = '', titleValue = '', listValues = [], titleValueFirst = 0) { | |||
this.titleName = titleName; | |||
this.titleValue = titleValue; | |||
setValues(x, y, title = {}, listValues = [], index = -1) { | |||
this.titleName = title.name; | |||
this.titleValue = title.value; | |||
this.listValues = listValues; | |||
this.x = x; | |||
this.y = y; | |||
this.titleValueFirst = titleValueFirst; | |||
this.titleValueFirst = title.valueFirst || 0; | |||
this.index = index; | |||
this.refresh(); | |||
} | |||
@@ -202,7 +206,7 @@ class SvgTip { | |||
} | |||
} | |||
const VERT_SPACE_OUTSIDE_BASE_CHART = 40; | |||
const VERT_SPACE_OUTSIDE_BASE_CHART = 50; | |||
const TRANSLATE_Y_BASE_CHART = 20; | |||
const LEFT_MARGIN_BASE_CHART = 60; | |||
const RIGHT_MARGIN_BASE_CHART = 40; | |||
@@ -220,7 +224,7 @@ const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||
const LINE_CHART_DOT_SIZE = 4; | |||
const DOT_OVERLAY_SIZE_INCR = 4; | |||
const DEFAULT_CHAR_WIDTH = 8; | |||
const DEFAULT_CHAR_WIDTH = 7; | |||
// Universal constants | |||
const ANGLE_RATIO = Math.PI / 180; | |||
@@ -574,7 +578,7 @@ function makeVertLine(x, label, y1, y2, options={}) { | |||
dy: FONT_SIZE + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': 'middle', | |||
innerHTML: label | |||
innerHTML: label + "" | |||
}); | |||
let line = createSVG('g', { | |||
@@ -729,7 +733,7 @@ function yRegion(y1, y2, width, label) { | |||
let labelSvg = createSVG('text', { | |||
className: 'chart-label', | |||
x: width - getStringWidth(label, 4.5) - LABEL_MARGIN, | |||
x: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN, | |||
y: 0, | |||
dy: (FONT_SIZE / -2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
@@ -761,6 +765,8 @@ function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) | |||
height: height || meta.minHeight // TODO: correct y for positive min height | |||
}); | |||
label += ""; | |||
if(!label && !label.length) { | |||
return rect; | |||
} else { | |||
@@ -777,6 +783,7 @@ function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) | |||
}); | |||
let group = createSVG('g', { | |||
'data-point-index': index, | |||
transform: `translate(${x}, ${y})` | |||
}); | |||
group.appendChild(rect); | |||
@@ -795,6 +802,8 @@ function datasetDot(x, y, radius, color, label='', index=0, meta={}) { | |||
r: radius | |||
}); | |||
label += ""; | |||
if(!label && !label.length) { | |||
return dot; | |||
} else { | |||
@@ -812,6 +821,7 @@ function datasetDot(x, y, radius, color, label='', index=0, meta={}) { | |||
}); | |||
let group = createSVG('g', { | |||
'data-point-index': index, | |||
transform: `translate(${x}, ${y})` | |||
}); | |||
group.appendChild(dot); | |||
@@ -873,9 +883,10 @@ let makeOverlay = { | |||
} | |||
let overlay = unit.cloneNode(); | |||
let radius = unit.getAttribute('r'); | |||
overlay.setAttribute('r', radius + DOT_OVERLAY_SIZE_INCR); | |||
overlay.style.fill = '#000000'; | |||
overlay.style.opacity = '0.4'; | |||
let fill = unit.getAttribute('fill'); | |||
overlay.setAttribute('r', parseInt(radius) + DOT_OVERLAY_SIZE_INCR); | |||
overlay.setAttribute('fill', fill); | |||
overlay.style.opacity = '0.6'; | |||
if(transformValue) { | |||
overlay.setAttribute('transform', transformValue); | |||
@@ -1266,7 +1277,10 @@ class BaseChart { | |||
setTimeout(() => {this.update();}, this.initTimeout); | |||
} | |||
this.renderLegend(); | |||
if(!onlyWidthChange) { | |||
this.renderLegend(); | |||
} | |||
this.setupNavigation(init); | |||
} | |||
@@ -1456,10 +1470,11 @@ class AggregationChart extends BaseChart { | |||
renderLegend() { | |||
let s = this.state; | |||
this.statsWrapper.textContent = ''; | |||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints); | |||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels : s.labels; | |||
let xValues = s.labels; | |||
this.legendTotals.map((d, i) => { | |||
if(d) { | |||
let stats = $.create('div', { | |||
@@ -1468,7 +1483,7 @@ class AggregationChart extends BaseChart { | |||
}); | |||
stats.innerHTML = `<span class="indicator"> | |||
<i style="background: ${this.colors[i]}"></i> | |||
<span class="text-muted">${x_values[i]}:</span> | |||
<span class="text-muted">${xValues[i]}:</span> | |||
${d} | |||
</span>`; | |||
} | |||
@@ -1542,7 +1557,7 @@ class PercentageChart extends AggregationChart { | |||
? this.formattedLabels[i] : this.state.labels[i]) + ': '; | |||
let percent = (s.sliceTotals[i]*100/this.grandTotal).toFixed(1); | |||
this.tip.setValues(x, y, title, percent + "%"); | |||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||
this.tip.showTip(); | |||
} | |||
}); | |||
@@ -2008,7 +2023,7 @@ class PieChart extends AggregationChart { | |||
return { | |||
sliceStrings: s.sliceStrings, | |||
colors: this.colors | |||
} | |||
}; | |||
}.bind(this) | |||
] | |||
]; | |||
@@ -2038,7 +2053,7 @@ class PieChart extends AggregationChart { | |||
let title = (this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels[i] : this.state.labels[i]) + ': '; | |||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1); | |||
this.tip.setValues(x, y, title, percent + "%"); | |||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||
this.tip.showTip(); | |||
} else { | |||
transform(path,'translate3d(0,0,0)'); | |||
@@ -2106,8 +2121,6 @@ function addDays(date, numberOfDays) { | |||
date.setDate(date.getDate() + numberOfDays); | |||
} | |||
// export function getMonthName() {} | |||
function normalize(x) { | |||
// Calculates mantissa and exponent of a number | |||
// Returns normalized number and exponent | |||
@@ -2328,15 +2341,15 @@ class Heatmap extends BaseChart { | |||
this.domain = options.domain || ''; | |||
this.subdomain = options.subdomain || ''; | |||
this.data = options.data || {}; | |||
this.discrete_domains = options.discrete_domains || 1; | |||
this.count_label = options.count_label || ''; | |||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1; | |||
this.countLabel = options.countLabel || ''; | |||
let today = new Date(); | |||
this.start = options.start || addDays(today, 365); | |||
let legend_colors = (options.legend_colors || []).slice(0, 5); | |||
this.legend_colors = this.validate_colors(legend_colors) | |||
? legend_colors | |||
let legendColors = (options.legendColors || []).slice(0, 5); | |||
this.legendColors = this.validate_colors(legendColors) | |||
? legendColors | |||
: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||
// Fixed 5-color theme, | |||
@@ -2347,6 +2360,12 @@ class Heatmap extends BaseChart { | |||
this.setup(); | |||
} | |||
setMargins() { | |||
super.setMargins(); | |||
this.leftMargin = 10; | |||
this.translateY = 10; | |||
} | |||
validate_colors(colors) { | |||
if(colors.length < 5) return 0; | |||
@@ -2369,21 +2388,21 @@ class Heatmap extends BaseChart { | |||
this.start = new Date(); | |||
this.start.setFullYear( this.start.getFullYear() - 1 ); | |||
} | |||
this.first_week_start = new Date(this.start.toDateString()); | |||
this.last_week_start = new Date(this.today.toDateString()); | |||
if(this.first_week_start.getDay() !== 7) { | |||
addDays(this.first_week_start, (-1) * this.first_week_start.getDay()); | |||
this.firstWeekStart = new Date(this.start.toDateString()); | |||
this.lastWeekStart = new Date(this.today.toDateString()); | |||
if(this.firstWeekStart.getDay() !== 7) { | |||
addDays(this.firstWeekStart, (-1) * this.firstWeekStart.getDay()); | |||
} | |||
if(this.last_week_start.getDay() !== 7) { | |||
addDays(this.last_week_start, (-1) * this.last_week_start.getDay()); | |||
if(this.lastWeekStart.getDay() !== 7) { | |||
addDays(this.lastWeekStart, (-1) * this.lastWeekStart.getDay()); | |||
} | |||
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1; | |||
this.no_of_cols = getWeeksBetween(this.firstWeekStart + '', this.lastWeekStart + '') + 1; | |||
} | |||
calcWidth() { | |||
this.baseWidth = (this.no_of_cols + 3) * 12 ; | |||
if(this.discrete_domains) { | |||
if(this.discreteDomains) { | |||
this.baseWidth += (12 * 12); | |||
} | |||
} | |||
@@ -2397,21 +2416,20 @@ class Heatmap extends BaseChart { | |||
'data-groups', | |||
`translate(0, 20)` | |||
); | |||
// Array.prototype.slice.call( | |||
// this.container.querySelectorAll('.graph-stats-container, .sub-title, .title') | |||
// ).map(d => { | |||
// d.style.display = 'None'; | |||
// }); | |||
// this.chartWrapper.style.marginTop = '0px'; | |||
// this.chartWrapper.style.paddingTop = '0px'; | |||
this.container.querySelector('.title').style.display = 'None'; | |||
this.container.querySelector('.sub-title').style.display = 'None'; | |||
this.container.querySelector('.graph-stats-container').style.display = 'None'; | |||
this.chartWrapper.style.marginTop = '0px'; | |||
this.chartWrapper.style.paddingTop = '0px'; | |||
} | |||
calc() { | |||
let data_values = Object.keys(this.data).map(key => this.data[key]); | |||
this.distribution = calcDistribution(data_values, this.distribution_size); | |||
let dataValues = Object.keys(this.data).map(key => this.data[key]); | |||
this.distribution = calcDistribution(dataValues, this.distribution_size); | |||
this.month_names = ["January", "February", "March", "April", "May", "June", | |||
this.monthNames = ["January", "February", "March", "April", "May", "June", | |||
"July", "August", "September", "October", "November", "December" | |||
]; | |||
} | |||
@@ -2425,118 +2443,118 @@ class Heatmap extends BaseChart { | |||
this.domainLabelGroup.textContent = ''; | |||
this.dataGroups.textContent = ''; | |||
let current_week_sunday = new Date(this.first_week_start); | |||
this.week_col = 0; | |||
this.current_month = current_week_sunday.getMonth(); | |||
let currentWeekSunday = new Date(this.firstWeekStart); | |||
this.weekCol = 0; | |||
this.currentMonth = currentWeekSunday.getMonth(); | |||
this.months = [this.current_month + '']; | |||
this.month_weeks = {}, this.month_start_points = []; | |||
this.month_weeks[this.current_month] = 0; | |||
this.month_start_points.push(13); | |||
this.months = [this.currentMonth + '']; | |||
this.monthWeeks = {}, this.monthStartPoints = []; | |||
this.monthWeeks[this.currentMonth] = 0; | |||
this.monthStartPoints.push(13); | |||
for(var i = 0; i < no_of_weeks; i++) { | |||
let data_group, month_change = 0; | |||
let day = new Date(current_week_sunday); | |||
[data_group, month_change] = this.get_week_squares_group(day, this.week_col); | |||
this.dataGroups.appendChild(data_group); | |||
this.week_col += 1 + parseInt(this.discrete_domains && month_change); | |||
this.month_weeks[this.current_month]++; | |||
if(month_change) { | |||
this.current_month = (this.current_month + 1) % 12; | |||
this.months.push(this.current_month + ''); | |||
this.month_weeks[this.current_month] = 1; | |||
let dataGroup, monthChange = 0; | |||
let day = new Date(currentWeekSunday); | |||
[dataGroup, monthChange] = this.get_week_squares_group(day, this.weekCol); | |||
this.dataGroups.appendChild(dataGroup); | |||
this.weekCol += 1 + parseInt(this.discreteDomains && monthChange); | |||
this.monthWeeks[this.currentMonth]++; | |||
if(monthChange) { | |||
this.currentMonth = (this.currentMonth + 1) % 12; | |||
this.months.push(this.currentMonth + ''); | |||
this.monthWeeks[this.currentMonth] = 1; | |||
} | |||
addDays(current_week_sunday, 7); | |||
addDays(currentWeekSunday, 7); | |||
} | |||
this.render_month_labels(); | |||
} | |||
get_week_squares_group(current_date, index) { | |||
const no_of_weekdays = 7; | |||
const square_side = 10; | |||
const cell_padding = 2; | |||
get_week_squares_group(currentDate, index) { | |||
const noOfWeekdays = 7; | |||
const squareSide = 10; | |||
const cellPadding = 2; | |||
const step = 1; | |||
const today_time = this.today.getTime(); | |||
const todayTime = this.today.getTime(); | |||
let month_change = 0; | |||
let week_col_change = 0; | |||
let monthChange = 0; | |||
let weekColChange = 0; | |||
let data_group = makeSVGGroup(this.dataGroups, 'data-group'); | |||
let dataGroup = makeSVGGroup(this.dataGroups, 'data-group'); | |||
for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { | |||
let data_value = 0; | |||
for(var y = 0, i = 0; i < noOfWeekdays; i += step, y += (squareSide + cellPadding)) { | |||
let dataValue = 0; | |||
let colorIndex = 0; | |||
let current_timestamp = current_date.getTime()/1000; | |||
let timestamp = Math.floor(current_timestamp - (current_timestamp % 86400)).toFixed(1); | |||
let currentTimestamp = currentDate.getTime()/1000; | |||
let timestamp = Math.floor(currentTimestamp - (currentTimestamp % 86400)).toFixed(1); | |||
if(this.data[timestamp]) { | |||
data_value = this.data[timestamp]; | |||
dataValue = this.data[timestamp]; | |||
} | |||
if(this.data[Math.round(timestamp)]) { | |||
data_value = this.data[Math.round(timestamp)]; | |||
dataValue = this.data[Math.round(timestamp)]; | |||
} | |||
if(data_value) { | |||
colorIndex = getMaxCheckpoint(data_value, this.distribution); | |||
if(dataValue) { | |||
colorIndex = getMaxCheckpoint(dataValue, this.distribution); | |||
} | |||
let x = 13 + (index + week_col_change) * 12; | |||
let x = 13 + (index + weekColChange) * 12; | |||
let dataAttr = { | |||
'data-date': getDdMmYyyy(current_date), | |||
'data-value': data_value, | |||
'data-day': current_date.getDay() | |||
'data-date': getDdMmYyyy(currentDate), | |||
'data-value': dataValue, | |||
'data-day': currentDate.getDay() | |||
}; | |||
let heatSquare = makeHeatSquare('day', x, y, square_side, | |||
this.legend_colors[colorIndex], dataAttr); | |||
let heatSquare = makeHeatSquare('day', x, y, squareSide, | |||
this.legendColors[colorIndex], dataAttr); | |||
data_group.appendChild(heatSquare); | |||
dataGroup.appendChild(heatSquare); | |||
let next_date = new Date(current_date); | |||
addDays(next_date, 1); | |||
if(next_date.getTime() > today_time) break; | |||
let nextDate = new Date(currentDate); | |||
addDays(nextDate, 1); | |||
if(nextDate.getTime() > todayTime) break; | |||
if(next_date.getMonth() - current_date.getMonth()) { | |||
month_change = 1; | |||
if(this.discrete_domains) { | |||
week_col_change = 1; | |||
if(nextDate.getMonth() - currentDate.getMonth()) { | |||
monthChange = 1; | |||
if(this.discreteDomains) { | |||
weekColChange = 1; | |||
} | |||
this.month_start_points.push(13 + (index + week_col_change) * 12); | |||
this.monthStartPoints.push(13 + (index + weekColChange) * 12); | |||
} | |||
current_date = next_date; | |||
currentDate = nextDate; | |||
} | |||
return [data_group, month_change]; | |||
return [dataGroup, monthChange]; | |||
} | |||
render_month_labels() { | |||
// this.first_month_label = 1; | |||
// if (this.first_week_start.getDate() > 8) { | |||
// if (this.firstWeekStart.getDate() > 8) { | |||
// this.first_month_label = 0; | |||
// } | |||
// this.last_month_label = 1; | |||
// let first_month = this.months.shift(); | |||
// let first_month_start = this.month_start_points.shift(); | |||
// let first_month_start = this.monthStartPoints.shift(); | |||
// render first month if | |||
// let last_month = this.months.pop(); | |||
// let last_month_start = this.month_start_points.pop(); | |||
// let last_month_start = this.monthStartPoints.pop(); | |||
// render last month if | |||
this.months.shift(); | |||
this.month_start_points.shift(); | |||
this.monthStartPoints.shift(); | |||
this.months.pop(); | |||
this.month_start_points.pop(); | |||
this.monthStartPoints.pop(); | |||
this.month_start_points.map((start, i) => { | |||
let month_name = this.month_names[this.months[i]].substring(0, 3); | |||
this.monthStartPoints.map((start, i) => { | |||
let month_name = this.monthNames[this.months[i]].substring(0, 3); | |||
let text = makeText('y-value-text', start+12, 10, month_name); | |||
this.domainLabelGroup.appendChild(text); | |||
}); | |||
@@ -2548,19 +2566,19 @@ class Heatmap extends BaseChart { | |||
).map(el => { | |||
el.addEventListener('mouseenter', (e) => { | |||
let count = e.target.getAttribute('data-value'); | |||
let date_parts = e.target.getAttribute('data-date').split('-'); | |||
let dateParts = e.target.getAttribute('data-date').split('-'); | |||
let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3); | |||
let month = this.monthNames[parseInt(dateParts[1])-1].substring(0, 3); | |||
let g_off = this.chartWrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect(); | |||
let gOff = this.chartWrapper.getBoundingClientRect(), pOff = e.target.getBoundingClientRect(); | |||
let width = parseInt(e.target.getAttribute('width')); | |||
let x = p_off.left - g_off.left + (width+2)/2; | |||
let y = p_off.top - g_off.top - (width+2)/2; | |||
let value = count + ' ' + this.count_label; | |||
let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2]; | |||
let x = pOff.left - gOff.left + (width+2)/2; | |||
let y = pOff.top - gOff.top - (width+2)/2; | |||
let value = count + ' ' + this.countLabel; | |||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | |||
this.tip.setValues(x, y, name, value, [], 1); | |||
this.tip.setValues(x, y, {name: name, value: value, valueFirst: 1}, []); | |||
this.tip.showTip(); | |||
}); | |||
}); | |||
@@ -2714,7 +2732,7 @@ class AxisChart extends BaseChart { | |||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span'; | |||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span'; | |||
this.config.xIsSeries = args.axisOptions.xIsSeries || 1; | |||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0; | |||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX; | |||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY; | |||
@@ -2810,7 +2828,7 @@ class AxisChart extends BaseChart { | |||
return; | |||
} | |||
s.yExtremes = new Array(s.datasetLength).fill(9999); | |||
s.datasets.map((d, i) => { | |||
s.datasets.map(d => { | |||
d.yPositions.map((pos, j) => { | |||
if(pos < s.yExtremes[j]) { | |||
s.yExtremes[j] = pos; | |||
@@ -2824,9 +2842,9 @@ class AxisChart extends BaseChart { | |||
if(this.data.yMarkers) { | |||
this.state.yMarkers = this.data.yMarkers.map(d => { | |||
d.position = scale(d.value, s.yAxis); | |||
if(!d.label.includes(':')) { | |||
d.label += ': ' + d.value; | |||
} | |||
// if(!d.label.includes(':')) { | |||
// d.label += ': ' + d.value; | |||
// } | |||
return d; | |||
}); | |||
} | |||
@@ -2928,7 +2946,7 @@ class AxisChart extends BaseChart { | |||
} | |||
let labels = new Array(s.datasetLength).fill(''); | |||
if(this.valuesOverPoints) { | |||
if(this.config.valuesOverPoints) { | |||
if(stacked && d.index === s.datasets.length - 1) { | |||
labels = d.cumulativeYs; | |||
} else { | |||
@@ -3037,12 +3055,15 @@ class AxisChart extends BaseChart { | |||
let s = this.state; | |||
if(!s.yExtremes) return; | |||
let formatY = this.config.formatTooltipY; | |||
let formatX = this.config.formatTooltipX; | |||
let titles = s.xAxis.labels; | |||
if(this.formatTooltipX && this.formatTooltipX(titles[0])) { | |||
titles = titles.map(d=>this.formatTooltipX(d)); | |||
if(formatX && formatX(titles[0])) { | |||
titles = titles.map(d=>formatX(d)); | |||
} | |||
let formatY = this.formatTooltipY && this.formatTooltipY(this.y[0].values[0]); | |||
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; | |||
for(var i=s.datasetLength - 1; i >= 0 ; i--) { | |||
let xVal = s.xAxis.positions[i]; | |||
@@ -3053,19 +3074,37 @@ class AxisChart extends BaseChart { | |||
let values = this.data.datasets.map((set, j) => { | |||
return { | |||
title: set.title, | |||
value: formatY ? this.formatTooltipY(set.values[i]) : set.values[i], | |||
title: set.name, | |||
value: formatY ? formatY(set.values[i]) : set.values[i], | |||
color: this.colors[j], | |||
}; | |||
}); | |||
this.tip.setValues(x, y, titles[i], '', values); | |||
this.tip.setValues(x, y, {name: titles[i], value: ''}, values, i); | |||
this.tip.showTip(); | |||
break; | |||
} | |||
} | |||
} | |||
renderLegend() { | |||
let s = this.data; | |||
this.statsWrapper.textContent = ''; | |||
if(s.datasets.length > 1) { | |||
s.datasets.map((d, i) => { | |||
let stats = $.create('div', { | |||
className: 'stats', | |||
inside: this.statsWrapper | |||
}); | |||
stats.innerHTML = `<span class="indicator"> | |||
<i style="background: ${this.colors[i]}"></i> | |||
${d.name} | |||
</span>`; | |||
}); | |||
} | |||
} | |||
makeOverlay() { | |||
if(this.overlayGuides) { | |||
this.overlayGuides.forEach(g => { | |||
@@ -3079,7 +3118,7 @@ class AxisChart extends BaseChart { | |||
type: c.unitType, | |||
overlay: undefined, | |||
units: c.units, | |||
} | |||
}; | |||
}); | |||
if(this.state.currentIndex === undefined) { | |||
@@ -3105,19 +3144,26 @@ class AxisChart extends BaseChart { | |||
} | |||
bindOverlay() { | |||
// on event, update overlay | |||
this.parent.addEventListener('data-select', (e) => { | |||
this.parent.addEventListener('data-select', () => { | |||
this.updateOverlay(); | |||
}); | |||
} | |||
bindUnits(units_array) { | |||
// units_array.map(unit => { | |||
// unit.addEventListener('click', () => { | |||
// let index = unit.getAttribute('data-point-index'); | |||
// this.setCurrentDataPoint(index); | |||
// }); | |||
// }); | |||
bindUnits() { | |||
this.dataUnitComponents.map(c => { | |||
c.units.map(unit => { | |||
unit.addEventListener('click', () => { | |||
let index = unit.getAttribute('data-point-index'); | |||
this.setCurrentDataPoint(index); | |||
}); | |||
}); | |||
}); | |||
// Note: Doesn't work as tooltip is absolutely positioned | |||
this.tip.container.addEventListener('click', () => { | |||
let index = this.tip.container.getAttribute('data-point-index'); | |||
this.setCurrentDataPoint(index); | |||
}); | |||
} | |||
updateOverlay() { | |||
@@ -3136,16 +3182,12 @@ class AxisChart extends BaseChart { | |||
} | |||
getDataPoint(index=this.state.currentIndex) { | |||
// check for length | |||
let s = this.state; | |||
let data_point = { | |||
index: index | |||
index: index, | |||
label: s.xAxis.labels[index], | |||
values: s.datasets.map(d => d.values[index]) | |||
}; | |||
// let y = this.y[0]; | |||
// ['svg_units', 'yUnitPositions', 'values'].map(key => { | |||
// let data_key = key.slice(0, key.length-1); | |||
// data_point[data_key] = y[key][index]; | |||
// }); | |||
// data_point.label = this.xAxis.labels[index]; | |||
return data_point; | |||
} | |||
@@ -3186,7 +3228,14 @@ class AxisChart extends BaseChart { | |||
// addDataset(dataset, index) {} | |||
// removeDataset(index = 0) {} | |||
// updateDatasets(datasets) {} | |||
updateDatasets(datasets) { | |||
this.data.datasets.map((d, i) => { | |||
if(datasets[i]) { | |||
d.values = datasets[i]; | |||
} | |||
}); | |||
this.update(this.data); | |||
} | |||
// updateDataPoint(dataPoint, index = 0) {} | |||
// addDataPoint(dataPoint, index = 0) {} | |||
@@ -1 +1 @@ | |||
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path,.chart-container .multiaxis-chart .line-horizontal,.chart-container .multiaxis-chart .y-axis-guide{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px} | |||
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-pack:distribute;justify-content:space-around;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:10px}.chart-container .graph-stats-container:after,.chart-container .graph-stats-container:before{content:"";display:block}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path,.chart-container .multiaxis-chart .line-horizontal,.chart-container .multiaxis-chart .y-axis-guide{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px} |
@@ -1,76 +1,100 @@ | |||
// Composite Chart | |||
// ================================================================================ | |||
let report_count_list = [17, 40, 33, 44, 126, 156, | |||
324, 333, 478, 495, 527]; | |||
let reportCountList = [152, 222, 199, 287, 534, 709, | |||
1179, 1256, 1632, 1856, 1850]; | |||
let bar_composite_data = { | |||
let lineCompositeData = { | |||
labels: ["2007", "2008", "2009", "2010", "2011", "2012", | |||
"2013", "2014", "2015", "2016", "2017"], | |||
yMarkers: [ | |||
{ | |||
label: "Marker 1", | |||
value: 420, | |||
}, | |||
{ | |||
label: "Marker 2", | |||
value: 250, | |||
label: "Average 100 reports/month", | |||
value: 1200, | |||
} | |||
], | |||
yRegions: [ | |||
{ | |||
label: "Region Y 1", | |||
start: 100, | |||
end: 300 | |||
}, | |||
], | |||
datasets: [{ | |||
"name": "Events", | |||
"values": report_count_list, | |||
// "formatted": report_count_list.map(d => d + " reports") | |||
"values": reportCountList | |||
}] | |||
}; | |||
let line_composite_data = { | |||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], | |||
datasets: [{ | |||
"values": [37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35], | |||
// "values": [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40], | |||
// "values": [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40], | |||
}] | |||
}; | |||
let more_line_data = [ | |||
[4, 0, 3, 1, 1, 2, 1, 2, 1, 0, 1, 1], | |||
let fireball_5_25 = [ | |||
[4, 0, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1], | |||
[2, 3, 3, 2, 1, 3, 0, 1, 2, 7, 10, 4], | |||
[5, 6, 2, 4, 0, 1, 4, 3, 0, 2, 0, 1], | |||
[0, 2, 6, 2, 1, 1, 2, 3, 6, 3, 7, 8], | |||
[6, 8, 7, 7, 4, 5, 6, 5, 22, 12, 10, 11], | |||
[7, 10, 11, 7, 3, 2, 7, 7, 11, 15, 22, 20], | |||
[13, 16, 21, 18, 19, 17, 12, 17, 31, 28, 25, 29], | |||
[24, 14, 21, 14, 11, 15, 19, 21, 41, 22, 32, 18], | |||
[31, 20, 30, 22, 14, 17, 21, 35, 27, 50, 117, 24], | |||
[32, 24, 21, 27, 11, 27, 43, 37, 44, 40, 48, 32], | |||
[31, 38, 36, 26, 23, 23, 25, 29, 26, 47, 61, 50], | |||
]; | |||
let fireball_2_5 = [ | |||
[22, 6, 6, 9, 7, 8, 6, 14, 19, 10, 8, 20], | |||
[11, 13, 12, 8, 9, 11, 9, 13, 10, 22, 40, 24], | |||
[20, 13, 13, 19, 13, 10, 14, 13, 20, 18, 5, 9], | |||
[7, 13, 16, 19, 12, 11, 21, 27, 27, 24, 33, 33], | |||
[38, 25, 28, 22, 31, 21, 35, 42, 37, 32, 46, 53], | |||
[50, 33, 36, 34, 35, 28, 27, 52, 58, 59, 75, 69], | |||
[54, 67, 67, 45, 66, 51, 38, 64, 90, 113, 116, 87], | |||
[84, 52, 56, 51, 55, 46, 50, 87, 114, 83, 152, 93], | |||
[73, 58, 59, 63, 56, 51, 83, 140, 103, 115, 265, 89], | |||
[106, 95, 94, 71, 77, 75, 99, 136, 129, 154, 168, 156], | |||
[81, 102, 95, 72, 58, 91, 89, 122, 124, 135, 183, 171], | |||
]; | |||
let fireballOver25 = [ | |||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |||
[2, 3, 3, 2, 1, 4, 0, 1, 2, 7, 11, 4], | |||
[7, 7, 2, 4, 0, 1, 5, 3, 1, 2, 0, 1], | |||
[0, 2, 6, 2, 2, 1, 2, 3, 6, 3, 7, 10], | |||
[9, 10, 8, 10, 6, 5, 8, 8, 24, 15, 10, 13], | |||
[9, 13, 16, 9, 4, 5, 7, 10, 14, 22, 23, 24], | |||
[20, 22, 28, 19, 28, 19, 14, 19, 51, 37, 29, 38], | |||
[29, 20, 22, 16, 16, 19, 24, 26, 57, 31, 46, 27], | |||
[36, 24, 38, 27, 15, 22, 24, 38, 32, 57, 139, 26], | |||
[37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35], | |||
[36, 46, 45, 32, 27, 31, 30, 36, 39, 58, 82, 62], | |||
// [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40] | |||
// [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40] | |||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], | |||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0], | |||
[1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0], | |||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2], | |||
[3, 2, 1, 3, 2, 0, 2, 2, 2, 3, 0, 1], | |||
[2, 3, 5, 2, 1, 3, 0, 2, 3, 5, 1, 4], | |||
[7, 4, 6, 1, 9, 2, 2, 2, 20, 9, 4, 9], | |||
[5, 6, 1, 2, 5, 4, 5, 5, 16, 9, 14, 9], | |||
[5, 4, 7, 5, 1, 5, 3, 3, 5, 7, 22, 2], | |||
[5, 13, 11, 6, 1, 7, 9, 8, 14, 17, 16, 3], | |||
[8, 9, 8, 6, 4, 8, 5, 6, 14, 11, 21, 12] | |||
]; | |||
let monthNames = ["January", "February", "March", "April", "May", "June", | |||
"July", "August", "September", "October", "November", "December"]; | |||
let barCompositeData = { | |||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], | |||
datasets: [ | |||
{ | |||
name: "Over 25 reports", | |||
values: fireballOver25[9], | |||
}, | |||
{ | |||
name: "5 to 25 reports", | |||
values: fireball_5_25[9], | |||
}, | |||
{ | |||
name: "2 to 5 reports", | |||
values: fireball_2_5[9] | |||
} | |||
] | |||
}; | |||
let c1 = document.querySelector("#chart-composite-1"); | |||
let c2 = document.querySelector("#chart-composite-2"); | |||
let bar_composite_chart = new Chart (c1, { | |||
let lineCompositeChart = new Chart (c1, { | |||
title: "Fireball/Bolide Events - Yearly (reported)", | |||
data: bar_composite_data, | |||
data: lineCompositeData, | |||
type: 'line', | |||
height: 180, | |||
height: 190, | |||
colors: ['green'], | |||
isNavigable: 1, | |||
isSeries: 1, | |||
// valuesOverPoints: 1, | |||
valuesOverPoints: 1, | |||
lineOptions: { | |||
dotSize: 8 | |||
@@ -79,25 +103,33 @@ let bar_composite_chart = new Chart (c1, { | |||
// regionFill: 1 | |||
}); | |||
let line_composite_chart = new Chart (c2, { | |||
data: line_composite_data, | |||
let barCompositeChart = new Chart (c2, { | |||
data: barCompositeData, | |||
type: 'bar', | |||
height: 180, | |||
colors: ['#46a9f9'], | |||
height: 190, | |||
colors: ['violet', 'light-blue', '#46a9f9'], | |||
isSeries: 1, | |||
valuesOverPoints: 1, | |||
xAxisMode: 'tick' | |||
axisOptions: { | |||
xAxisMode: 'tick' | |||
}, | |||
barOptions: { | |||
stacked: 1 | |||
}, | |||
}); | |||
bar_composite_chart.parent.addEventListener('data-select', (e) => { | |||
line_composite_chart.updateDataset(more_line_data[e.index]); | |||
lineCompositeChart.parent.addEventListener('data-select', (e) => { | |||
let i = e.index; | |||
barCompositeChart.updateDatasets([ | |||
fireballOver25[i], fireball_5_25[i], fireball_2_5[i] | |||
]); | |||
}); | |||
// Demo Chart (bar, linepts, scatter(blobs), percentage) | |||
// ================================================================================ | |||
let type_data = { | |||
let typeData = { | |||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", | |||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"], | |||
@@ -166,15 +198,13 @@ let type_data = { | |||
] | |||
}; | |||
let type_chart = new Chart("#chart-types", { | |||
// title: "My Awesome Chart", | |||
data: type_data, | |||
let typeChart = new Chart("#chart-types", { | |||
title: "My Awesome Chart", | |||
data: typeData, | |||
type: 'bar', | |||
height: 250, | |||
colors: ['purple', 'magenta', 'light-blue'], | |||
isSeries: 1, | |||
xAxisMode: 'tick', | |||
yAxisMode: 'span', | |||
valuesOverPoints: 1, | |||
// maxLegendPoints: 6, | |||
// maxSlices: 3, | |||
@@ -184,86 +214,45 @@ let type_chart = new Chart("#chart-types", { | |||
}, | |||
tooltipOptions: { | |||
formatTooltipX: d => (d + '').toUpperCase(), | |||
formatTooltipY: d => d + ' pts' | |||
formatTooltipY: d => d + ' pts', | |||
} | |||
}); | |||
Array.prototype.slice.call( | |||
document.querySelectorAll('.chart-type-buttons button') | |||
).map(el => { | |||
el.addEventListener('click', (e) => { | |||
let btn = e.target; | |||
let type = btn.getAttribute('data-type'); | |||
let newChart = type_chart.getDifferentChart(type); | |||
if(newChart){ | |||
type_chart = newChart; | |||
} | |||
Array.prototype.slice.call( | |||
btn.parentNode.querySelectorAll('button')).map(el => { | |||
el.classList.remove('active'); | |||
}); | |||
btn.classList.add('active'); | |||
}); | |||
}); | |||
// Trends Chart | |||
// ================================================================================ | |||
let trends_data = { | |||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, | |||
1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, | |||
1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, | |||
1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, | |||
2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016] , | |||
datasets: [ | |||
{ | |||
"values": [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4, | |||
39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8, | |||
33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6, | |||
28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7, | |||
12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8] | |||
} | |||
] | |||
}; | |||
let plotChartArgs = { | |||
title: "Mean Total Sunspot Count - Yearly", | |||
data: trends_data, | |||
type: 'line', | |||
// Aggregation chart | |||
// ================================================================================ | |||
let aggrChart = new Chart("#chart-aggr", { | |||
data: typeData, | |||
type: 'pie', | |||
height: 250, | |||
colors: ['blue'], | |||
colors: ['purple', 'magenta', 'light-blue'], | |||
isSeries: 1, | |||
lineOptions: { | |||
hideDots: 1, | |||
heatline: 1, | |||
maxLegendPoints: 6, | |||
maxSlices: 10, | |||
barOptions: { | |||
stacked: 1 | |||
}, | |||
axisOptions: { | |||
xAxisMode: 'tick', | |||
yAxisMode: 'span', | |||
xIsSeries: 1 | |||
tooltipOptions: { | |||
formatTooltipX: d => (d + '').toUpperCase(), | |||
formatTooltipY: d => d + ' pts', | |||
} | |||
}; | |||
new Chart("#chart-trends", plotChartArgs); | |||
}); | |||
Array.prototype.slice.call( | |||
document.querySelectorAll('.chart-plot-buttons button') | |||
document.querySelectorAll('.aggr-type-buttons button') | |||
).map(el => { | |||
el.addEventListener('click', (e) => { | |||
let btn = e.target; | |||
let type = btn.getAttribute('data-type'); | |||
let config = {}; | |||
config[type] = 1; | |||
if(['regionFill', 'heatline'].includes(type)) { | |||
config.hideDots = 1; | |||
let newChart = aggrChart.getDifferentChart(type); | |||
if(newChart){ | |||
aggrChart = newChart; | |||
} | |||
// plotChartArgs.init = false; | |||
plotChartArgs.lineOptions = config; | |||
new Chart("#chart-trends", plotChartArgs); | |||
Array.prototype.slice.call( | |||
btn.parentNode.querySelectorAll('button')).map(el => { | |||
el.classList.remove('active'); | |||
@@ -337,6 +326,71 @@ chart_update_buttons.querySelector('[data-update="remove"]').addEventListener("c | |||
update_chart.removeDataPoint(); | |||
}); | |||
// Trends Chart | |||
// ================================================================================ | |||
let trends_data = { | |||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, | |||
1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, | |||
1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, | |||
1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, | |||
2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016] , | |||
datasets: [ | |||
{ | |||
values: [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4, | |||
39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8, | |||
33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6, | |||
28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7, | |||
12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8] | |||
} | |||
] | |||
}; | |||
let plotChartArgs = { | |||
title: "Mean Total Sunspot Count - Yearly", | |||
data: trends_data, | |||
type: 'line', | |||
height: 250, | |||
colors: ['blue'], | |||
isSeries: 1, | |||
lineOptions: { | |||
hideDots: 1, | |||
heatline: 1, | |||
}, | |||
axisOptions: { | |||
xAxisMode: 'tick', | |||
yAxisMode: 'span', | |||
xIsSeries: 1 | |||
} | |||
}; | |||
new Chart("#chart-trends", plotChartArgs); | |||
Array.prototype.slice.call( | |||
document.querySelectorAll('.chart-plot-buttons button') | |||
).map(el => { | |||
el.addEventListener('click', (e) => { | |||
let btn = e.target; | |||
let type = btn.getAttribute('data-type'); | |||
let config = {}; | |||
config[type] = 1; | |||
if(['regionFill', 'heatline'].includes(type)) { | |||
config.hideDots = 1; | |||
} | |||
// plotChartArgs.init = false; | |||
plotChartArgs.lineOptions = config; | |||
new Chart("#chart-trends", plotChartArgs); | |||
Array.prototype.slice.call( | |||
btn.parentNode.querySelectorAll('button')).map(el => { | |||
el.classList.remove('active'); | |||
}); | |||
btn.classList.add('active'); | |||
}); | |||
}); | |||
// Event chart | |||
// ================================================================================ | |||
@@ -398,28 +452,24 @@ events_chart.parent.addEventListener('data-select', (e) => { | |||
data_div.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg"; | |||
}); | |||
// Aggregation chart | |||
// ================================================================================ | |||
// Heatmap | |||
// ================================================================================ | |||
let heatmap_data = {}; | |||
let heatmapData = {}; | |||
let current_date = new Date(); | |||
let timestamp = current_date.getTime()/1000; | |||
timestamp = Math.floor(timestamp - (timestamp % 86400)).toFixed(1); // convert to midnight | |||
for (var i = 0; i< 375; i++) { | |||
heatmap_data[parseInt(timestamp)] = Math.floor(Math.random() * 5); | |||
heatmapData[parseInt(timestamp)] = Math.floor(Math.random() * 5); | |||
timestamp = Math.floor(timestamp - 86400).toFixed(1); | |||
} | |||
new Chart("#chart-heatmap", { | |||
data: heatmap_data, | |||
data: heatmapData, | |||
type: 'heatmap', | |||
legend_scale: [0, 1, 2, 4, 5], | |||
legendScale: [0, 1, 2, 4, 5], | |||
height: 115, | |||
discrete_domains: 1 | |||
discreteDomains: 1 | |||
}); | |||
Array.prototype.slice.call( | |||
@@ -428,10 +478,10 @@ Array.prototype.slice.call( | |||
el.addEventListener('click', (e) => { | |||
let btn = e.target; | |||
let mode = btn.getAttribute('data-mode'); | |||
let discrete_domains = 0; | |||
let discreteDomains = 0; | |||
if(mode === 'discrete') { | |||
discrete_domains = 1; | |||
discreteDomains = 1; | |||
} | |||
let colors = []; | |||
@@ -443,12 +493,12 @@ Array.prototype.slice.call( | |||
} | |||
new Chart("#chart-heatmap", { | |||
data: heatmap_data, | |||
data: heatmapData, | |||
type: 'heatmap', | |||
legend_scale: [0, 1, 2, 4, 5], | |||
legendScale: [0, 1, 2, 4, 5], | |||
height: 115, | |||
discrete_domains: discrete_domains, | |||
legend_colors: colors | |||
discreteDomains: discreteDomains, | |||
legendColors: colors | |||
}); | |||
Array.prototype.slice.call( | |||
@@ -471,22 +521,22 @@ Array.prototype.slice.call( | |||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||
} | |||
let discrete_domains = 1; | |||
let discreteDomains = 1; | |||
let view_mode = document | |||
.querySelector('.heatmap-mode-buttons .active') | |||
.getAttribute('data-mode'); | |||
if(view_mode === 'continuous') { | |||
discrete_domains = 0; | |||
discreteDomains = 0; | |||
} | |||
new Chart("#chart-heatmap", { | |||
data: heatmap_data, | |||
data: heatmapData, | |||
type: 'heatmap', | |||
legend_scale: [0, 1, 2, 4, 5], | |||
legendScale: [0, 1, 2, 4, 5], | |||
height: 115, | |||
discrete_domains: discrete_domains, | |||
legend_colors: colors | |||
discreteDomains: discreteDomains, | |||
legendColors: colors | |||
}); | |||
Array.prototype.slice.call( | |||
@@ -44,67 +44,51 @@ | |||
<div class="col-sm-10 push-sm-1"> | |||
<div class="dashboard-section"> | |||
<h6 class="margin-vertical-rem"> | |||
<!--Bars, Lines or <a href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts" target="_blank">Percentages</a>--> | |||
Create a chart | |||
</h6> | |||
<p class="step-explain">Install</p> | |||
<pre><code class="hljs console"> npm install frappe-charts</code></pre> | |||
<p class="step-explain">And include it in your project</p> | |||
<pre><code class="hljs javascript"> import Chart from "frappe-charts/dist/frappe-charts.min.esm"</code></pre> | |||
<p class="step-explain">... or include it directly in your HTML</p> | |||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@0.0.8/dist/frappe-charts.min.iife.js"></script></code></pre> | |||
<p class="step-explain">Make a new Chart</p> | |||
<h6 class="margin-vertical-rem">Create a chart</h6> | |||
<pre><code class="hljs html"> <!--HTML--> | |||
<div id="chart"></div></code></pre> | |||
<pre><code class="hljs javascript"> // Javascript | |||
let data = { | |||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", | |||
let chart = new Chart( "#chart", { // or DOM element | |||
data: { | |||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", | |||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"], | |||
datasets: [ | |||
{ | |||
label: "Some Data", | |||
values: [25, 40, 30, 35, 8, 52, 17, -4] | |||
}, | |||
{ | |||
label: "Another Set", | |||
values: [25, 50, -10, 15, 18, 32, 27, 14] | |||
}, | |||
{ | |||
label: "Yet Another", | |||
values: [15, 20, -3, -15, 58, 12, -17, 37] | |||
} | |||
] | |||
}; | |||
datasets: [ | |||
{ | |||
label: "Some Data", type: 'bar', | |||
values: [25, 40, 30, 35, 8, 52, 17, -4] | |||
}, | |||
{ | |||
label: "Another Set", type: 'bar', | |||
values: [25, 50, -10, 15, 18, 32, 27, 14] | |||
}, | |||
{ | |||
label: "Yet Another", type: 'line', | |||
values: [15, 20, -3, -15, 58, 12, -17, 37] | |||
} | |||
] | |||
}, | |||
let chart = new Chart({ | |||
parent: "#chart", // or a DOM element | |||
title: "My Awesome Chart", | |||
data: data, | |||
type: 'bar', // or 'line', 'scatter', 'pie', 'percentage' | |||
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage' | |||
height: 250, | |||
colors: ['#7cd6fd', 'violet', 'blue'], | |||
// hex-codes or these preset colors; | |||
// defaults (in order): | |||
// ['light-blue', 'blue', 'violet', 'red', | |||
// 'orange', 'yellow', 'green', 'light-green', | |||
// 'purple', 'magenta', 'grey', 'dark-grey'] | |||
format_tooltip_x: d => (d + '').toUpperCase(), | |||
format_tooltip_y: d => d + ' pts' | |||
colors: ['#7cd6fd', 'violet', 'blue'] | |||
});</code></pre> | |||
<div id="chart-types" class="border"></div> | |||
<div class="btn-group chart-type-buttons margin-vertical-px mx-auto" role="group"> | |||
<!-- <div class="btn-group chart-type-buttons margin-vertical-px mx-auto" role="group"> | |||
<button type="button" class="btn btn-sm btn-secondary active" data-type='bar'>Bar Chart</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-type='line'>Line Chart</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button> | |||
</div> --> | |||
<div id="chart-aggr" class="border"></div> | |||
<div class="btn-group aggr-type-buttons margin-vertical-px mx-auto" role="group"> | |||
<button type="button" class="btn btn-sm btn-secondary active" data-type='pie'>Pie Chart</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button> | |||
</div> | |||
<p class="text-muted"> | |||
<!-- <p class="text-muted"> | |||
<a target="_blank" href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts">Why Percentage?</a> | |||
</p> | |||
</p> --> | |||
</div> | |||
</div> | |||
@@ -113,42 +97,12 @@ | |||
<h6 class="margin-vertical-rem"> | |||
Update Values | |||
</h6> | |||
<pre><code class="hljs javascript"> // Update entire datasets | |||
chart.updateData( | |||
[ | |||
{values: new_dataset_1_values}, | |||
{values: new_dataset_2_values} | |||
], | |||
new_labels | |||
); | |||
// Add a new data point | |||
chart.add_data_point( | |||
[new_value_1, new_value_2], | |||
new_label, | |||
index // defaults to last index | |||
); | |||
// Remove a data point | |||
chart.remove_data_point(index);</code></pre> | |||
<div id="chart-update" class="border"></div> | |||
<div class="chart-update-buttons mt-1 mx-auto" role="group"> | |||
<button type="button" class="btn btn-sm btn-secondary" data-update="random">Random Data</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-update="add">Add Value</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-update="remove">Remove Value</button> | |||
</div> | |||
<pre><code class="hljs javascript margin-vertical-px"> ... | |||
// Include specific Y values in input data to be displayed as lines | |||
// (before passing data to a new chart): | |||
data.specific_values = [ | |||
{ | |||
label: "Altitude", | |||
line_type: "dashed", // or "solid" | |||
value: 38 | |||
} | |||
] | |||
...</code></pre> | |||
</div> | |||
</div> | |||
@@ -157,12 +111,6 @@ | |||
<h6 class="margin-vertical-rem"> | |||
Plot Trends | |||
</h6> | |||
<pre><code class="hljs javascript"> ... | |||
xAxisMode: 'tick', // for short label ticks | |||
// or 'span' for long spanning vertical axis lines | |||
yAxisMode: 'span', // for long horizontal lines, or 'tick' | |||
isSeries: 1, // to allow for skipping of X values | |||
...</code></pre> | |||
<div id="chart-trends" class="border"></div> | |||
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group"> | |||
<button type="button" class="btn btn-sm btn-secondary" data-type="hideDots">Line</button> | |||
@@ -170,13 +118,13 @@ | |||
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-type="regionFill">Region</button> | |||
</div> | |||
<pre><code class="hljs javascript margin-vertical-px"> ... | |||
type: 'line', // Line Chart specific properties: | |||
<!-- <pre><code class="hljs javascript margin-vertical-px"> ... | |||
lineOptions: 'line', // Line Chart specific properties: | |||
hideDots: 1, // Hide data points on the line; default 0 | |||
heatline: 1, // Show a value-wise line gradient; default 0 | |||
regionFill: 1, // Fill the area under the graph; default 0 | |||
...</code></pre> | |||
...</code></pre> --> | |||
</div> | |||
</div> | |||
@@ -204,8 +152,7 @@ | |||
</div> | |||
</div> | |||
<pre><code class="hljs javascript margin-vertical-px"> ... | |||
type: 'bar', // Bar Chart specific properties: | |||
isNavigable: 1, // Navigate across bars; default 0 | |||
isNavigable: 1, // Navigate across data points; default 0 | |||
... | |||
chart.parent.addEventListener('data-select', (e) => { | |||
@@ -214,22 +161,6 @@ | |||
</div> | |||
</div> | |||
<div class="col-sm-10 push-sm-1"> | |||
<div class="dashboard-section"> | |||
<h6 class="margin-vertical-rem"> | |||
Simple Aggregations | |||
</h6> | |||
<div id="chart-aggr" class="border"></div> | |||
<div class="chart-aggr-buttons mt-1 mx-auto" role="group"> | |||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="sums">Show Sums</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="average">Show Averages</button> | |||
</div> | |||
<pre><code class="hljs javascript margin-vertical-px"> chart.show_sums(); // and `hide_sums()` | |||
chart.show_averages(); // and `hide_averages()`</code></pre> | |||
</div> | |||
</div> | |||
<div class="col-sm-10 push-sm-1"> | |||
<div class="dashboard-section"> | |||
<h6 class="margin-vertical-rem"> | |||
@@ -249,16 +180,16 @@ | |||
parent: "#heatmap", | |||
type: 'heatmap', | |||
height: 115, | |||
data: heatmap_data, // object with date/timestamp-value pairs | |||
data: heatmapData, // object with date/timestamp-value pairs | |||
discrete_domains: 1 // default: 0 | |||
discreteDomains: 1 // default: 0 | |||
start: start_date, | |||
start: startDate, | |||
// A Date object; | |||
// default: today's date in past year | |||
// for an annual heatmap | |||
legend_colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'], | |||
legendColors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'], | |||
// Set of five incremental colors, | |||
// beginning with a low-saturation color for zero data; | |||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] | |||
@@ -267,6 +198,54 @@ | |||
</div> | |||
</div> | |||
<div class="col-sm-10 push-sm-1"> | |||
<div class="dashboard-section"> | |||
<h6 class="margin-vertical-rem">All available options:</h6> | |||
<pre><code class="hljs javascript"> // Javascript | |||
let data = { | |||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", | |||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"], | |||
datasets: [ | |||
{ | |||
label: "Some Data", type: 'bar', | |||
values: [25, 40, 30, 35, 8, 52, 17, -4] | |||
}, | |||
{ | |||
label: "Another Set", type: 'bar', | |||
values: [25, 50, -10, 15, 18, 32, 27, 14] | |||
}, | |||
{ | |||
label: "Yet Another", type: 'line', | |||
values: [15, 20, -3, -15, 58, 12, -17, 37] | |||
} | |||
] | |||
}; | |||
let chart = new Chart( "#chart", { // or DOM element | |||
title: "My Awesome Chart", | |||
data: data, | |||
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage' | |||
height: 250, | |||
colors: ['#7cd6fd', 'violet', 'blue'] | |||
});</code></pre> | |||
</div> | |||
</div> | |||
<div class="col-sm-10 push-sm-1"> | |||
<div class="dashboard-section"> | |||
<h6 class="margin-vertical-rem">Install</h6> | |||
<p class="step-explain">Install via npm</p> | |||
<pre><code class="hljs console"> npm install frappe-charts</code></pre> | |||
<p class="step-explain">And include it in your project</p> | |||
<pre><code class="hljs javascript"> import Chart from "frappe-charts/dist/frappe-charts.min.esm"</code></pre> | |||
<p class="step-explain">... or include it directly in your HTML</p> | |||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@0.0.8/dist/frappe-charts.min.iife.js"></script></code></pre> | |||
</div> | |||
</div> | |||
<div class="col-sm-10 push-sm-1"> | |||
<div class="dashboard-section"> | |||
<!-- Closing --> | |||
@@ -50,10 +50,11 @@ export default class AggregationChart extends BaseChart { | |||
renderLegend() { | |||
let s = this.state; | |||
this.statsWrapper.textContent = ''; | |||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints); | |||
let x_values = this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels : s.labels; | |||
let xValues = s.labels; | |||
this.legendTotals.map((d, i) => { | |||
if(d) { | |||
let stats = $.create('div', { | |||
@@ -62,7 +63,7 @@ export default class AggregationChart extends BaseChart { | |||
}); | |||
stats.innerHTML = `<span class="indicator"> | |||
<i style="background: ${this.colors[i]}"></i> | |||
<span class="text-muted">${x_values[i]}:</span> | |||
<span class="text-muted">${xValues[i]}:</span> | |||
${d} | |||
</span>`; | |||
} | |||
@@ -2,11 +2,11 @@ import BaseChart from './BaseChart'; | |||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils'; | |||
import { Y_AXIS_MARGIN } from '../utils/constants'; | |||
import { getComponent } from '../objects/ChartComponents'; | |||
import { getOffset, fire } from '../utils/dom'; | |||
import { $, getOffset, fire } from '../utils/dom'; | |||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale } from '../utils/intervals'; | |||
import { floatTwo } from '../utils/helpers'; | |||
import { makeOverlay, updateOverlay } from '../utils/draw'; | |||
import { MIN_BAR_PERCENT_HEIGHT, DEFAULT_AXIS_CHART_TYPE, BAR_CHART_SPACE_RATIO, LINE_CHART_DOT_SIZE } from '../utils/constants'; | |||
import { MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO, LINE_CHART_DOT_SIZE } from '../utils/constants'; | |||
export default class AxisChart extends BaseChart { | |||
constructor(parent, args) { | |||
@@ -28,7 +28,7 @@ export default class AxisChart extends BaseChart { | |||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span'; | |||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span'; | |||
this.config.xIsSeries = args.axisOptions.xIsSeries || 1; | |||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0; | |||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX; | |||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY; | |||
@@ -88,7 +88,7 @@ export default class AxisChart extends BaseChart { | |||
positions: yPts.map(d => zeroLine - d * scaleMultiplier), | |||
scaleMultiplier: scaleMultiplier, | |||
zeroLine: zeroLine, | |||
} | |||
}; | |||
// Dependent if above changes | |||
this.calcDatasetPoints(); | |||
@@ -124,7 +124,7 @@ export default class AxisChart extends BaseChart { | |||
return; | |||
} | |||
s.yExtremes = new Array(s.datasetLength).fill(9999); | |||
s.datasets.map((d, i) => { | |||
s.datasets.map(d => { | |||
d.yPositions.map((pos, j) => { | |||
if(pos < s.yExtremes[j]) { | |||
s.yExtremes[j] = pos; | |||
@@ -138,9 +138,9 @@ export default class AxisChart extends BaseChart { | |||
if(this.data.yMarkers) { | |||
this.state.yMarkers = this.data.yMarkers.map(d => { | |||
d.position = scale(d.value, s.yAxis); | |||
if(!d.label.includes(':')) { | |||
d.label += ': ' + d.value; | |||
} | |||
// if(!d.label.includes(':')) { | |||
// d.label += ': ' + d.value; | |||
// } | |||
return d; | |||
}); | |||
} | |||
@@ -170,7 +170,6 @@ export default class AxisChart extends BaseChart { | |||
} | |||
setupComponents() { | |||
let s = this.state; | |||
let componentConfigs = [ | |||
[ | |||
'yAxis', | |||
@@ -243,7 +242,7 @@ export default class AxisChart extends BaseChart { | |||
} | |||
let labels = new Array(s.datasetLength).fill(''); | |||
if(this.valuesOverPoints) { | |||
if(this.config.valuesOverPoints) { | |||
if(stacked && d.index === s.datasets.length - 1) { | |||
labels = d.cumulativeYs; | |||
} else { | |||
@@ -352,12 +351,15 @@ export default class AxisChart extends BaseChart { | |||
let s = this.state; | |||
if(!s.yExtremes) return; | |||
let formatY = this.config.formatTooltipY; | |||
let formatX = this.config.formatTooltipX; | |||
let titles = s.xAxis.labels; | |||
if(this.formatTooltipX && this.formatTooltipX(titles[0])) { | |||
titles = titles.map(d=>this.formatTooltipX(d)); | |||
if(formatX && formatX(titles[0])) { | |||
titles = titles.map(d=>formatX(d)); | |||
} | |||
let formatY = this.formatTooltipY && this.formatTooltipY(this.y[0].values[0]); | |||
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0; | |||
for(var i=s.datasetLength - 1; i >= 0 ; i--) { | |||
let xVal = s.xAxis.positions[i]; | |||
@@ -368,19 +370,37 @@ export default class AxisChart extends BaseChart { | |||
let values = this.data.datasets.map((set, j) => { | |||
return { | |||
title: set.title, | |||
value: formatY ? this.formatTooltipY(set.values[i]) : set.values[i], | |||
title: set.name, | |||
value: formatY ? formatY(set.values[i]) : set.values[i], | |||
color: this.colors[j], | |||
}; | |||
}); | |||
this.tip.setValues(x, y, titles[i], '', values); | |||
this.tip.setValues(x, y, {name: titles[i], value: ''}, values, i); | |||
this.tip.showTip(); | |||
break; | |||
} | |||
} | |||
} | |||
renderLegend() { | |||
let s = this.data; | |||
this.statsWrapper.textContent = ''; | |||
if(s.datasets.length > 1) { | |||
s.datasets.map((d, i) => { | |||
let stats = $.create('div', { | |||
className: 'stats', | |||
inside: this.statsWrapper | |||
}); | |||
stats.innerHTML = `<span class="indicator"> | |||
<i style="background: ${this.colors[i]}"></i> | |||
${d.name} | |||
</span>`; | |||
}); | |||
} | |||
} | |||
makeOverlay() { | |||
if(this.overlayGuides) { | |||
this.overlayGuides.forEach(g => { | |||
@@ -394,7 +414,7 @@ export default class AxisChart extends BaseChart { | |||
type: c.unitType, | |||
overlay: undefined, | |||
units: c.units, | |||
} | |||
}; | |||
}); | |||
if(this.state.currentIndex === undefined) { | |||
@@ -406,7 +426,7 @@ export default class AxisChart extends BaseChart { | |||
let currentUnit = d.units[this.state.currentIndex]; | |||
d.overlay = makeOverlay[d.type](currentUnit); | |||
this.drawArea.appendChild(d.overlay); | |||
}) | |||
}); | |||
} | |||
@@ -420,26 +440,33 @@ export default class AxisChart extends BaseChart { | |||
} | |||
bindOverlay() { | |||
// on event, update overlay | |||
this.parent.addEventListener('data-select', (e) => { | |||
this.parent.addEventListener('data-select', () => { | |||
this.updateOverlay(); | |||
}); | |||
} | |||
bindUnits(units_array) { | |||
// units_array.map(unit => { | |||
// unit.addEventListener('click', () => { | |||
// let index = unit.getAttribute('data-point-index'); | |||
// this.setCurrentDataPoint(index); | |||
// }); | |||
// }); | |||
bindUnits() { | |||
this.dataUnitComponents.map(c => { | |||
c.units.map(unit => { | |||
unit.addEventListener('click', () => { | |||
let index = unit.getAttribute('data-point-index'); | |||
this.setCurrentDataPoint(index); | |||
}); | |||
}); | |||
}); | |||
// Note: Doesn't work as tooltip is absolutely positioned | |||
this.tip.container.addEventListener('click', () => { | |||
let index = this.tip.container.getAttribute('data-point-index'); | |||
this.setCurrentDataPoint(index); | |||
}); | |||
} | |||
updateOverlay() { | |||
this.overlayGuides.map(d => { | |||
let currentUnit = d.units[this.state.currentIndex]; | |||
updateOverlay[d.type](currentUnit, d.overlay); | |||
}) | |||
}); | |||
} | |||
onLeftArrow() { | |||
@@ -451,16 +478,12 @@ export default class AxisChart extends BaseChart { | |||
} | |||
getDataPoint(index=this.state.currentIndex) { | |||
// check for length | |||
let s = this.state; | |||
let data_point = { | |||
index: index | |||
index: index, | |||
label: s.xAxis.labels[index], | |||
values: s.datasets.map(d => d.values[index]) | |||
}; | |||
// let y = this.y[0]; | |||
// ['svg_units', 'yUnitPositions', 'values'].map(key => { | |||
// let data_key = key.slice(0, key.length-1); | |||
// data_point[data_key] = y[key][index]; | |||
// }); | |||
// data_point.label = this.xAxis.labels[index]; | |||
return data_point; | |||
} | |||
@@ -501,7 +524,14 @@ export default class AxisChart extends BaseChart { | |||
// addDataset(dataset, index) {} | |||
// removeDataset(index = 0) {} | |||
// updateDatasets(datasets) {} | |||
updateDatasets(datasets) { | |||
this.data.datasets.map((d, i) => { | |||
if(datasets[i]) { | |||
d.values = datasets[i]; | |||
} | |||
}); | |||
this.update(this.data); | |||
} | |||
// updateDataPoint(dataPoint, index = 0) {} | |||
// addDataPoint(dataPoint, index = 0) {} | |||
@@ -143,7 +143,10 @@ export default class BaseChart { | |||
setTimeout(() => {this.update();}, this.initTimeout); | |||
} | |||
this.renderLegend(); | |||
if(!onlyWidthChange) { | |||
this.renderLegend(); | |||
} | |||
this.setupNavigation(init); | |||
} | |||
@@ -13,15 +13,15 @@ export default class Heatmap extends BaseChart { | |||
this.domain = options.domain || ''; | |||
this.subdomain = options.subdomain || ''; | |||
this.data = options.data || {}; | |||
this.discrete_domains = options.discrete_domains || 1; | |||
this.count_label = options.count_label || ''; | |||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1; | |||
this.countLabel = options.countLabel || ''; | |||
let today = new Date(); | |||
this.start = options.start || addDays(today, 365); | |||
let legend_colors = (options.legend_colors || []).slice(0, 5); | |||
this.legend_colors = this.validate_colors(legend_colors) | |||
? legend_colors | |||
let legendColors = (options.legendColors || []).slice(0, 5); | |||
this.legendColors = this.validate_colors(legendColors) | |||
? legendColors | |||
: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | |||
// Fixed 5-color theme, | |||
@@ -32,6 +32,12 @@ export default class Heatmap extends BaseChart { | |||
this.setup(); | |||
} | |||
setMargins() { | |||
super.setMargins(); | |||
this.leftMargin = 10; | |||
this.translateY = 10; | |||
} | |||
validate_colors(colors) { | |||
if(colors.length < 5) return 0; | |||
@@ -54,21 +60,21 @@ export default class Heatmap extends BaseChart { | |||
this.start = new Date(); | |||
this.start.setFullYear( this.start.getFullYear() - 1 ); | |||
} | |||
this.first_week_start = new Date(this.start.toDateString()); | |||
this.last_week_start = new Date(this.today.toDateString()); | |||
if(this.first_week_start.getDay() !== 7) { | |||
addDays(this.first_week_start, (-1) * this.first_week_start.getDay()); | |||
this.firstWeekStart = new Date(this.start.toDateString()); | |||
this.lastWeekStart = new Date(this.today.toDateString()); | |||
if(this.firstWeekStart.getDay() !== 7) { | |||
addDays(this.firstWeekStart, (-1) * this.firstWeekStart.getDay()); | |||
} | |||
if(this.last_week_start.getDay() !== 7) { | |||
addDays(this.last_week_start, (-1) * this.last_week_start.getDay()); | |||
if(this.lastWeekStart.getDay() !== 7) { | |||
addDays(this.lastWeekStart, (-1) * this.lastWeekStart.getDay()); | |||
} | |||
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1; | |||
this.no_of_cols = getWeeksBetween(this.firstWeekStart + '', this.lastWeekStart + '') + 1; | |||
} | |||
calcWidth() { | |||
this.baseWidth = (this.no_of_cols + 3) * 12 ; | |||
if(this.discrete_domains) { | |||
if(this.discreteDomains) { | |||
this.baseWidth += (12 * 12); | |||
} | |||
} | |||
@@ -82,21 +88,20 @@ export default class Heatmap extends BaseChart { | |||
'data-groups', | |||
`translate(0, 20)` | |||
); | |||
// Array.prototype.slice.call( | |||
// this.container.querySelectorAll('.graph-stats-container, .sub-title, .title') | |||
// ).map(d => { | |||
// d.style.display = 'None'; | |||
// }); | |||
// this.chartWrapper.style.marginTop = '0px'; | |||
// this.chartWrapper.style.paddingTop = '0px'; | |||
this.container.querySelector('.title').style.display = 'None'; | |||
this.container.querySelector('.sub-title').style.display = 'None'; | |||
this.container.querySelector('.graph-stats-container').style.display = 'None'; | |||
this.chartWrapper.style.marginTop = '0px'; | |||
this.chartWrapper.style.paddingTop = '0px'; | |||
} | |||
calc() { | |||
let data_values = Object.keys(this.data).map(key => this.data[key]); | |||
this.distribution = calcDistribution(data_values, this.distribution_size); | |||
let dataValues = Object.keys(this.data).map(key => this.data[key]); | |||
this.distribution = calcDistribution(dataValues, this.distribution_size); | |||
this.month_names = ["January", "February", "March", "April", "May", "June", | |||
this.monthNames = ["January", "February", "March", "April", "May", "June", | |||
"July", "August", "September", "October", "November", "December" | |||
]; | |||
} | |||
@@ -110,118 +115,118 @@ export default class Heatmap extends BaseChart { | |||
this.domainLabelGroup.textContent = ''; | |||
this.dataGroups.textContent = ''; | |||
let current_week_sunday = new Date(this.first_week_start); | |||
this.week_col = 0; | |||
this.current_month = current_week_sunday.getMonth(); | |||
let currentWeekSunday = new Date(this.firstWeekStart); | |||
this.weekCol = 0; | |||
this.currentMonth = currentWeekSunday.getMonth(); | |||
this.months = [this.current_month + '']; | |||
this.month_weeks = {}, this.month_start_points = []; | |||
this.month_weeks[this.current_month] = 0; | |||
this.month_start_points.push(13); | |||
this.months = [this.currentMonth + '']; | |||
this.monthWeeks = {}, this.monthStartPoints = []; | |||
this.monthWeeks[this.currentMonth] = 0; | |||
this.monthStartPoints.push(13); | |||
for(var i = 0; i < no_of_weeks; i++) { | |||
let data_group, month_change = 0; | |||
let day = new Date(current_week_sunday); | |||
[data_group, month_change] = this.get_week_squares_group(day, this.week_col); | |||
this.dataGroups.appendChild(data_group); | |||
this.week_col += 1 + parseInt(this.discrete_domains && month_change); | |||
this.month_weeks[this.current_month]++; | |||
if(month_change) { | |||
this.current_month = (this.current_month + 1) % 12; | |||
this.months.push(this.current_month + ''); | |||
this.month_weeks[this.current_month] = 1; | |||
let dataGroup, monthChange = 0; | |||
let day = new Date(currentWeekSunday); | |||
[dataGroup, monthChange] = this.get_week_squares_group(day, this.weekCol); | |||
this.dataGroups.appendChild(dataGroup); | |||
this.weekCol += 1 + parseInt(this.discreteDomains && monthChange); | |||
this.monthWeeks[this.currentMonth]++; | |||
if(monthChange) { | |||
this.currentMonth = (this.currentMonth + 1) % 12; | |||
this.months.push(this.currentMonth + ''); | |||
this.monthWeeks[this.currentMonth] = 1; | |||
} | |||
addDays(current_week_sunday, 7); | |||
addDays(currentWeekSunday, 7); | |||
} | |||
this.render_month_labels(); | |||
} | |||
get_week_squares_group(current_date, index) { | |||
const no_of_weekdays = 7; | |||
const square_side = 10; | |||
const cell_padding = 2; | |||
get_week_squares_group(currentDate, index) { | |||
const noOfWeekdays = 7; | |||
const squareSide = 10; | |||
const cellPadding = 2; | |||
const step = 1; | |||
const today_time = this.today.getTime(); | |||
const todayTime = this.today.getTime(); | |||
let month_change = 0; | |||
let week_col_change = 0; | |||
let monthChange = 0; | |||
let weekColChange = 0; | |||
let data_group = makeSVGGroup(this.dataGroups, 'data-group'); | |||
let dataGroup = makeSVGGroup(this.dataGroups, 'data-group'); | |||
for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { | |||
let data_value = 0; | |||
for(var y = 0, i = 0; i < noOfWeekdays; i += step, y += (squareSide + cellPadding)) { | |||
let dataValue = 0; | |||
let colorIndex = 0; | |||
let current_timestamp = current_date.getTime()/1000; | |||
let timestamp = Math.floor(current_timestamp - (current_timestamp % 86400)).toFixed(1); | |||
let currentTimestamp = currentDate.getTime()/1000; | |||
let timestamp = Math.floor(currentTimestamp - (currentTimestamp % 86400)).toFixed(1); | |||
if(this.data[timestamp]) { | |||
data_value = this.data[timestamp]; | |||
dataValue = this.data[timestamp]; | |||
} | |||
if(this.data[Math.round(timestamp)]) { | |||
data_value = this.data[Math.round(timestamp)]; | |||
dataValue = this.data[Math.round(timestamp)]; | |||
} | |||
if(data_value) { | |||
colorIndex = getMaxCheckpoint(data_value, this.distribution); | |||
if(dataValue) { | |||
colorIndex = getMaxCheckpoint(dataValue, this.distribution); | |||
} | |||
let x = 13 + (index + week_col_change) * 12; | |||
let x = 13 + (index + weekColChange) * 12; | |||
let dataAttr = { | |||
'data-date': getDdMmYyyy(current_date), | |||
'data-value': data_value, | |||
'data-day': current_date.getDay() | |||
'data-date': getDdMmYyyy(currentDate), | |||
'data-value': dataValue, | |||
'data-day': currentDate.getDay() | |||
}; | |||
let heatSquare = makeHeatSquare('day', x, y, square_side, | |||
this.legend_colors[colorIndex], dataAttr); | |||
let heatSquare = makeHeatSquare('day', x, y, squareSide, | |||
this.legendColors[colorIndex], dataAttr); | |||
data_group.appendChild(heatSquare); | |||
dataGroup.appendChild(heatSquare); | |||
let next_date = new Date(current_date); | |||
addDays(next_date, 1); | |||
if(next_date.getTime() > today_time) break; | |||
let nextDate = new Date(currentDate); | |||
addDays(nextDate, 1); | |||
if(nextDate.getTime() > todayTime) break; | |||
if(next_date.getMonth() - current_date.getMonth()) { | |||
month_change = 1; | |||
if(this.discrete_domains) { | |||
week_col_change = 1; | |||
if(nextDate.getMonth() - currentDate.getMonth()) { | |||
monthChange = 1; | |||
if(this.discreteDomains) { | |||
weekColChange = 1; | |||
} | |||
this.month_start_points.push(13 + (index + week_col_change) * 12); | |||
this.monthStartPoints.push(13 + (index + weekColChange) * 12); | |||
} | |||
current_date = next_date; | |||
currentDate = nextDate; | |||
} | |||
return [data_group, month_change]; | |||
return [dataGroup, monthChange]; | |||
} | |||
render_month_labels() { | |||
// this.first_month_label = 1; | |||
// if (this.first_week_start.getDate() > 8) { | |||
// if (this.firstWeekStart.getDate() > 8) { | |||
// this.first_month_label = 0; | |||
// } | |||
// this.last_month_label = 1; | |||
// let first_month = this.months.shift(); | |||
// let first_month_start = this.month_start_points.shift(); | |||
// let first_month_start = this.monthStartPoints.shift(); | |||
// render first month if | |||
// let last_month = this.months.pop(); | |||
// let last_month_start = this.month_start_points.pop(); | |||
// let last_month_start = this.monthStartPoints.pop(); | |||
// render last month if | |||
this.months.shift(); | |||
this.month_start_points.shift(); | |||
this.monthStartPoints.shift(); | |||
this.months.pop(); | |||
this.month_start_points.pop(); | |||
this.monthStartPoints.pop(); | |||
this.month_start_points.map((start, i) => { | |||
let month_name = this.month_names[this.months[i]].substring(0, 3); | |||
this.monthStartPoints.map((start, i) => { | |||
let month_name = this.monthNames[this.months[i]].substring(0, 3); | |||
let text = makeText('y-value-text', start+12, 10, month_name); | |||
this.domainLabelGroup.appendChild(text); | |||
}); | |||
@@ -233,19 +238,19 @@ export default class Heatmap extends BaseChart { | |||
).map(el => { | |||
el.addEventListener('mouseenter', (e) => { | |||
let count = e.target.getAttribute('data-value'); | |||
let date_parts = e.target.getAttribute('data-date').split('-'); | |||
let dateParts = e.target.getAttribute('data-date').split('-'); | |||
let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3); | |||
let month = this.monthNames[parseInt(dateParts[1])-1].substring(0, 3); | |||
let g_off = this.chartWrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect(); | |||
let gOff = this.chartWrapper.getBoundingClientRect(), pOff = e.target.getBoundingClientRect(); | |||
let width = parseInt(e.target.getAttribute('width')); | |||
let x = p_off.left - g_off.left + (width+2)/2; | |||
let y = p_off.top - g_off.top - (width+2)/2; | |||
let value = count + ' ' + this.count_label; | |||
let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2]; | |||
let x = pOff.left - gOff.left + (width+2)/2; | |||
let y = pOff.top - gOff.top - (width+2)/2; | |||
let value = count + ' ' + this.countLabel; | |||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | |||
this.tip.setValues(x, y, name, value, [], 1); | |||
this.tip.setValues(x, y, {name: name, value: value, valueFirst: 1}, []); | |||
this.tip.showTip(); | |||
}); | |||
}); | |||
@@ -67,7 +67,7 @@ export default class PercentageChart extends AggregationChart { | |||
? this.formattedLabels[i] : this.state.labels[i]) + ': '; | |||
let percent = (s.sliceTotals[i]*100/this.grandTotal).toFixed(1); | |||
this.tip.setValues(x, y, title, percent + "%"); | |||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||
this.tip.showTip(); | |||
} | |||
}); | |||
@@ -2,7 +2,7 @@ import AggregationChart from './AggregationChart'; | |||
import { getComponent } from '../objects/ChartComponents'; | |||
import { getOffset } from '../utils/dom'; | |||
import { getPositionByAngle } from '../utils/helpers'; | |||
import { makePath, makeArcPathStr } from '../utils/draw'; | |||
import { makeArcPathStr } from '../utils/draw'; | |||
import { lightenDarkenColor } from '../utils/colors'; | |||
import { transform } from '../utils/animation'; | |||
import { FULL_ANGLE } from '../utils/constants'; | |||
@@ -39,7 +39,7 @@ export default class PieChart extends AggregationChart { | |||
this.center = { | |||
x: this.width / 2, | |||
y: this.height / 2 | |||
} | |||
}; | |||
this.radius = (this.height > this.width ? this.center.x : this.center.y); | |||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0); | |||
@@ -102,7 +102,7 @@ export default class PieChart extends AggregationChart { | |||
return { | |||
sliceStrings: s.sliceStrings, | |||
colors: this.colors | |||
} | |||
}; | |||
}.bind(this) | |||
] | |||
]; | |||
@@ -132,7 +132,7 @@ export default class PieChart extends AggregationChart { | |||
let title = (this.formatted_labels && this.formatted_labels.length > 0 | |||
? this.formatted_labels[i] : this.state.labels[i]) + ': '; | |||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1); | |||
this.tip.setValues(x, y, title, percent + "%"); | |||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||
this.tip.showTip(); | |||
} else { | |||
transform(path,'translate3d(0,0,0)'); | |||
@@ -51,6 +51,9 @@ export default class SvgTip { | |||
fill() { | |||
let title; | |||
if(this.index) { | |||
this.container.setAttribute('data-point-index', this.index); | |||
} | |||
if(this.titleValueFirst) { | |||
title = `<strong>${this.titleValue}</strong>${this.titleName}`; | |||
} else { | |||
@@ -97,13 +100,14 @@ export default class SvgTip { | |||
} | |||
} | |||
setValues(x, y, titleName = '', titleValue = '', listValues = [], titleValueFirst = 0) { | |||
this.titleName = titleName; | |||
this.titleValue = titleValue; | |||
setValues(x, y, title = {}, listValues = [], index = -1) { | |||
this.titleName = title.name; | |||
this.titleValue = title.value; | |||
this.listValues = listValues; | |||
this.x = x; | |||
this.y = y; | |||
this.titleValueFirst = titleValueFirst; | |||
this.titleValueFirst = title.valueFirst || 0; | |||
this.index = index; | |||
this.refresh(); | |||
} | |||
@@ -1,4 +1,4 @@ | |||
export const VERT_SPACE_OUTSIDE_BASE_CHART = 40; | |||
export const VERT_SPACE_OUTSIDE_BASE_CHART = 50; | |||
export const TRANSLATE_Y_BASE_CHART = 20; | |||
export const LEFT_MARGIN_BASE_CHART = 60; | |||
export const RIGHT_MARGIN_BASE_CHART = 40; | |||
@@ -16,7 +16,7 @@ export const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||
export const LINE_CHART_DOT_SIZE = 4; | |||
export const DOT_OVERLAY_SIZE_INCR = 4; | |||
export const DEFAULT_CHAR_WIDTH = 8; | |||
export const DEFAULT_CHAR_WIDTH = 7; | |||
// Universal constants | |||
export const ANGLE_RATIO = Math.PI / 180; |
@@ -31,4 +31,9 @@ export function addDays(date, numberOfDays) { | |||
date.setDate(date.getDate() + numberOfDays); | |||
} | |||
// export function getMonthName() {} | |||
export function getMonthName(i) { | |||
let monthNames = ["January", "February", "March", "April", "May", "June", | |||
"July", "August", "September", "October", "November", "December" | |||
]; | |||
return monthNames[i]; | |||
} |
@@ -180,7 +180,7 @@ function makeVertLine(x, label, y1, y2, options={}) { | |||
dy: FONT_SIZE + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': 'middle', | |||
innerHTML: label | |||
innerHTML: label + "" | |||
}); | |||
let line = createSVG('g', { | |||
@@ -337,7 +337,7 @@ export function yRegion(y1, y2, width, label) { | |||
let labelSvg = createSVG('text', { | |||
className: 'chart-label', | |||
x: width - getStringWidth(label, 4.5) - LABEL_MARGIN, | |||
x: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN, | |||
y: 0, | |||
dy: (FONT_SIZE / -2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
@@ -369,6 +369,8 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m | |||
height: height || meta.minHeight // TODO: correct y for positive min height | |||
}); | |||
label += ""; | |||
if(!label && !label.length) { | |||
return rect; | |||
} else { | |||
@@ -385,6 +387,7 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m | |||
}); | |||
let group = createSVG('g', { | |||
'data-point-index': index, | |||
transform: `translate(${x}, ${y})` | |||
}); | |||
group.appendChild(rect); | |||
@@ -403,6 +406,8 @@ export function datasetDot(x, y, radius, color, label='', index=0, meta={}) { | |||
r: radius | |||
}); | |||
label += ""; | |||
if(!label && !label.length) { | |||
return dot; | |||
} else { | |||
@@ -420,6 +425,7 @@ export function datasetDot(x, y, radius, color, label='', index=0, meta={}) { | |||
}); | |||
let group = createSVG('g', { | |||
'data-point-index': index, | |||
transform: `translate(${x}, ${y})` | |||
}); | |||
group.appendChild(dot); | |||
@@ -481,9 +487,10 @@ export let makeOverlay = { | |||
} | |||
let overlay = unit.cloneNode(); | |||
let radius = unit.getAttribute('r'); | |||
overlay.setAttribute('r', radius + DOT_OVERLAY_SIZE_INCR); | |||
overlay.style.fill = '#000000'; | |||
overlay.style.opacity = '0.4'; | |||
let fill = unit.getAttribute('fill'); | |||
overlay.setAttribute('r', parseInt(radius) + DOT_OVERLAY_SIZE_INCR); | |||
overlay.setAttribute('fill', fill); | |||
overlay.style.opacity = '0.6'; | |||
if(transformValue) { | |||
overlay.setAttribute('transform', transformValue); | |||
@@ -28,8 +28,13 @@ | |||
} | |||
.graph-stats-container { | |||
display: flex; | |||
justify-content: space-around; | |||
padding-top: 10px; | |||
justify-content: space-between; | |||
padding: 10px; | |||
&:before, | |||
&:after { | |||
content: ''; | |||
display: block; | |||
} | |||
.stats { | |||
padding-bottom: 15px; | |||
} | |||