@@ -84,30 +84,40 @@ function fire(target, type, properties) { | |||
// https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/ | |||
const ALL_CHART_TYPES = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie']; | |||
const COMPATIBLE_CHARTS = { | |||
bar: ['line', 'scatter', 'percentage', 'pie'], | |||
line: ['scatter', 'bar', 'percentage', 'pie'], | |||
pie: ['line', 'scatter', 'percentage', 'bar'], | |||
percentage: ['bar', 'line', 'scatter', 'pie'], | |||
heatmap: [] | |||
}; | |||
const BASE_MEASURES = { | |||
margins: { | |||
top: 10, | |||
bottom: 10, | |||
left: 20, | |||
right: 20 | |||
}, | |||
paddings: { | |||
top: 20, | |||
bottom: 40, | |||
left: 30, | |||
right: 10 | |||
}, | |||
const DATA_COLOR_DIVISIONS = { | |||
bar: 'datasets', | |||
line: 'datasets', | |||
pie: 'labels', | |||
percentage: 'labels', | |||
heatmap: HEATMAP_DISTRIBUTION_SIZE | |||
baseHeight: 240, | |||
titleHeight: 20, | |||
legendHeight: 30, | |||
titleFontSize: 12, | |||
}; | |||
const BASE_CHART_TOP_MARGIN = 10; | |||
const BASE_CHART_LEFT_MARGIN = 20; | |||
const BASE_CHART_RIGHT_MARGIN = 20; | |||
function getExtraHeight(m) { | |||
let totalExtraHeight = m.margins.top + m.margins.bottom | |||
+ m.paddings.top + m.paddings.bottom | |||
+ m.titleHeight + m.legendHeight; | |||
return totalExtraHeight; | |||
} | |||
const Y_AXIS_LEFT_MARGIN = 60; | |||
const Y_AXIS_RIGHT_MARGIN = 40; | |||
function getExtraWidth(m) { | |||
let totalExtraWidth = m.margins.left + m.margins.right | |||
+ m.paddings.left + m.paddings.right; | |||
return totalExtraWidth; | |||
} | |||
const INIT_CHART_UPDATE_TIMEOUT = 700; | |||
const CHART_POST_ANIMATE_TIMEOUT = 400; | |||
@@ -130,9 +140,6 @@ const PERCENTAGE_BAR_DEFAULT_DEPTH = 2; | |||
// More colors are difficult to parse visually | |||
const HEATMAP_DISTRIBUTION_SIZE = 5; | |||
const HEATMAP_LEFT_MARGIN = 50; | |||
const HEATMAP_TOP_MARGIN = 25; | |||
const HEATMAP_SQUARE_SIZE = 10; | |||
const HEATMAP_GUTTER_SIZE = 2; | |||
@@ -282,10 +289,6 @@ class SvgTip { | |||
} | |||
} | |||
/** | |||
* Returns the value of a number upto 2 decimal places. | |||
* @param {Number} d Any number | |||
*/ | |||
function floatTwo(d) { | |||
return parseFloat(d.toFixed(2)); | |||
} | |||
@@ -489,12 +492,13 @@ function makeSVGDefs(svgContainer) { | |||
}); | |||
} | |||
function makeSVGGroup(parent, className, transform='') { | |||
return createSVG('g', { | |||
function makeSVGGroup(className, transform='', parent=undefined) { | |||
let args = { | |||
className: className, | |||
inside: parent, | |||
transform: transform | |||
}); | |||
}; | |||
if(parent) args.inside = parent; | |||
return createSVG('g', args); | |||
} | |||
@@ -1327,7 +1331,6 @@ class BaseChart { | |||
this.rawChartArgs = options; | |||
this.title = options.title || ''; | |||
this.argHeight = options.height || 240; | |||
this.type = options.type || ''; | |||
this.realData = this.prepareData(options.data); | |||
@@ -1337,10 +1340,18 @@ class BaseChart { | |||
this.config = { | |||
showTooltip: 1, // calculate | |||
showLegend: options.showLegend || 1, | |||
showLegend: 1, // calculate | |||
isNavigable: options.isNavigable || 0, | |||
animate: 1 | |||
}; | |||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES)); | |||
let m = this.measures; | |||
this.setMeasures(options); | |||
if(!this.title.length) { m.titleHeight = 0; } | |||
if(!this.config.showLegend) m.legendHeight = 0; | |||
this.argHeight = options.height || m.baseHeight; | |||
this.state = {}; | |||
this.options = {}; | |||
@@ -1353,12 +1364,12 @@ class BaseChart { | |||
this.configure(options); | |||
} | |||
configure() { | |||
this.setMargins(); | |||
prepareData(data) { | |||
return data; | |||
} | |||
// Bind window events | |||
window.addEventListener('resize', () => this.boundDrawFn); | |||
window.addEventListener('orientationchange', () => this.boundDrawFn); | |||
prepareFirstData(data) { | |||
return data; | |||
} | |||
validateColors(colors, type) { | |||
@@ -1375,17 +1386,22 @@ class BaseChart { | |||
return validColors; | |||
} | |||
setMargins() { | |||
setMeasures() { | |||
// Override measures, including those for title and legend | |||
// set config for legend and title | |||
} | |||
configure() { | |||
let height = this.argHeight; | |||
this.baseHeight = height; | |||
this.height = height - 70; | |||
this.topMargin = BASE_CHART_TOP_MARGIN; | |||
this.height = height - getExtraHeight(this.measures); | |||
// Horizontal margins | |||
this.leftMargin = BASE_CHART_LEFT_MARGIN; | |||
this.rightMargin = BASE_CHART_RIGHT_MARGIN; | |||
// Bind window events | |||
window.addEventListener('resize', () => this.draw(true)); | |||
window.addEventListener('orientationchange', () => this.draw(true)); | |||
} | |||
// Has to be called manually | |||
setup() { | |||
this.makeContainer(); | |||
this.updateWidth(); | |||
@@ -1394,10 +1410,6 @@ class BaseChart { | |||
this.draw(false, true); | |||
} | |||
setupComponents() { | |||
this.components = new Map(); | |||
} | |||
makeContainer() { | |||
// Chart needs a dedicated parent element | |||
this.parent.innerHTML = ''; | |||
@@ -1444,11 +1456,71 @@ class BaseChart { | |||
this.setupNavigation(init); | |||
} | |||
calc() {} // builds state | |||
updateWidth() { | |||
this.baseWidth = getElementContentWidth(this.parent); | |||
this.width = this.baseWidth - (this.leftMargin + this.rightMargin); | |||
this.width = this.baseWidth - getExtraWidth(this.measures); | |||
} | |||
makeChartArea() { | |||
if(this.svg) { | |||
this.container.removeChild(this.svg); | |||
} | |||
let m = this.measures; | |||
this.svg = makeSVGContainer( | |||
this.container, | |||
'frappe-chart chart', | |||
this.baseWidth, | |||
this.baseHeight | |||
); | |||
this.svgDefs = makeSVGDefs(this.svg); | |||
if(this.title.length) { | |||
this.titleEL = makeText( | |||
'title', | |||
m.margins.left, | |||
m.margins.top, | |||
this.title, | |||
{ | |||
fontSize: m.titleFontSize, | |||
fill: '#666666', | |||
dy: m.titleFontSize | |||
} | |||
); | |||
} | |||
let top = m.margins.top + m.titleHeight + m.paddings.top; | |||
this.drawArea = makeSVGGroup( | |||
this.type + '-chart chart-draw-area', | |||
`translate(${m.margins.left + m.paddings.left}, ${top})` | |||
); | |||
if(this.config.showLegend) { | |||
top += this.height + m.paddings.bottom; | |||
this.legendArea = makeSVGGroup( | |||
'chart-legend', | |||
`translate(${m.margins.left + m.paddings.left}, ${top})` | |||
); | |||
} | |||
if(this.title.length) { this.svg.appendChild(this.titleEL); } | |||
this.svg.appendChild(this.drawArea); | |||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); } | |||
this.updateTipOffset(m.margins.left + m.paddings.left, m.margins.top + m.paddings.top + m.titleHeight); | |||
} | |||
updateTipOffset(x, y) { | |||
this.tip.offset = { | |||
x: x, | |||
y: y | |||
}; | |||
} | |||
setupComponents() { this.components = new Map(); } | |||
update(data) { | |||
if(!data) { | |||
console.error('No data to update.'); | |||
@@ -1458,16 +1530,6 @@ class BaseChart { | |||
this.render(); | |||
} | |||
prepareData(data=this.data) { | |||
return data; | |||
} | |||
prepareFirstData(data=this.data) { | |||
return data; | |||
} | |||
calc() {} // builds state | |||
render(components=this.components, animate=true) { | |||
if(this.config.isNavigable) { | |||
// Remove all existing overlays | |||
@@ -1498,68 +1560,6 @@ class BaseChart { | |||
} | |||
} | |||
makeChartArea() { | |||
if(this.svg) { | |||
this.container.removeChild(this.svg); | |||
} | |||
let titleAreaHeight = 0; | |||
let legendAreaHeight = 0; | |||
if(this.title.length) { | |||
titleAreaHeight = 40; | |||
} | |||
if(this.config.showLegend) { | |||
legendAreaHeight = 30; | |||
} | |||
this.svg = makeSVGContainer( | |||
this.container, | |||
'frappe-chart chart', | |||
this.baseWidth, | |||
this.baseHeight + titleAreaHeight + legendAreaHeight | |||
); | |||
this.svgDefs = makeSVGDefs(this.svg); | |||
// console.log(this.baseHeight, titleAreaHeight, legendAreaHeight); | |||
if(this.title.length) { | |||
this.titleEL = makeText( | |||
'title', | |||
this.leftMargin - AXIS_TICK_LENGTH * 6, | |||
this.topMargin, | |||
this.title, | |||
{ | |||
fontSize: 12, | |||
fill: '#666666' | |||
} | |||
); | |||
this.svg.appendChild(this.titleEL); | |||
} | |||
let top = this.topMargin + titleAreaHeight; | |||
this.drawArea = makeSVGGroup( | |||
this.svg, | |||
this.type + '-chart', | |||
`translate(${this.leftMargin}, ${top})` | |||
); | |||
top = this.baseHeight - titleAreaHeight; | |||
this.legendArea = makeSVGGroup( | |||
this.svg, | |||
'chart-legend', | |||
`translate(${this.leftMargin}, ${top})` | |||
); | |||
this.updateTipOffset(this.leftMargin, this.topMargin + titleAreaHeight); | |||
} | |||
updateTipOffset(x, y) { | |||
this.tip.offset = { | |||
x: x, | |||
y: y | |||
}; | |||
} | |||
renderLegend() {} | |||
setupNavigation(init=false) { | |||
@@ -1606,39 +1606,13 @@ class BaseChart { | |||
updateDataset() {} | |||
getDifferentChart(type) { | |||
const currentType = this.type; | |||
let args = this.rawChartArgs; | |||
if(type === currentType) return; | |||
if(!ALL_CHART_TYPES.includes(type)) { | |||
console.error(`'${type}' is not a valid chart type.`); | |||
} | |||
if(!COMPATIBLE_CHARTS[currentType].includes(type)) { | |||
console.error(`'${currentType}' chart cannot be converted to a '${type}' chart.`); | |||
} | |||
// whether the new chart can use the existing colors | |||
const useColor = DATA_COLOR_DIVISIONS[currentType] === DATA_COLOR_DIVISIONS[type]; | |||
// Okay, this is anticlimactic | |||
// this function will need to actually be 'changeChartType(type)' | |||
// that will update only the required elements, but for now ... | |||
args.type = type; | |||
args.colors = useColor ? args.colors : undefined; | |||
return new Chart(this.parent, args); | |||
} | |||
boundDrawFn() { | |||
this.draw(true); | |||
} | |||
unbindWindowEvents(){ | |||
window.removeEventListener('resize', () => this.boundDrawFn); | |||
window.removeEventListener('orientationchange', () => this.boundDrawFn); | |||
window.removeEventListener('resize', () => this.boundDrawFn.bind(this)); | |||
window.removeEventListener('orientationchange', () => this.boundDrawFn.bind(this)); | |||
} | |||
export() { | |||
@@ -1834,7 +1808,7 @@ class ChartComponent { | |||
} | |||
setup(parent) { | |||
this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform); | |||
this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent); | |||
} | |||
make() { | |||
@@ -2039,9 +2013,9 @@ let componentConfigs = { | |||
data.cols.map((week, weekNo) => { | |||
if(weekNo === 1) { | |||
this.labels.push( | |||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true), | |||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(), | |||
{ | |||
fontSize: 11 | |||
fontSize: 9 | |||
} | |||
) | |||
); | |||
@@ -2690,26 +2664,26 @@ class Heatmap extends BaseChart { | |||
this.setup(); | |||
} | |||
configure(options) { | |||
setMeasures(options) { | |||
let m = this.measures; | |||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1; | |||
super.configure(options); | |||
} | |||
setMargins() { | |||
super.setMargins(); | |||
this.leftMargin = HEATMAP_LEFT_MARGIN; | |||
this.topMargin = HEATMAP_TOP_MARGIN; | |||
m.paddings.top = ROW_HEIGHT * 3; | |||
m.paddings.bottom = 0; | |||
m.legendHeight = ROW_HEIGHT * 2; | |||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK | |||
+ getExtraHeight(m); | |||
let d = this.data; | |||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0; | |||
this.independentWidth = (getWeeksBetween(d.start, d.end) | |||
+ spacing) * COL_WIDTH + this.rightMargin + this.leftMargin; | |||
+ spacing) * COL_WIDTH + m.margins.right + m.margins.left; | |||
} | |||
updateWidth() { | |||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0; | |||
this.baseWidth = (this.state.noOfWeeks + spacing) * COL_WIDTH | |||
+ this.rightMargin + this.leftMargin; | |||
+ getExtraWidth(this.measures); | |||
} | |||
prepareData(data=this.data) { | |||
@@ -2910,7 +2884,7 @@ class Heatmap extends BaseChart { | |||
addDays(startOfWeek, 1); | |||
} | |||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue) { | |||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) { | |||
addDays(startOfWeek, 1); | |||
cols.push(this.getCol(startOfWeek, month, true)); | |||
} | |||
@@ -3091,26 +3065,27 @@ class AxisChart extends BaseChart { | |||
this.setup(); | |||
} | |||
configure(args) { | |||
super.configure(args); | |||
setMeasures(options) { | |||
if(this.data.datasets.length <= 1) { | |||
this.config.showLegend = 0; | |||
this.measures.paddings.bottom = 30; | |||
} | |||
} | |||
args.axisOptions = args.axisOptions || {}; | |||
args.tooltipOptions = args.tooltipOptions || {}; | |||
configure(options) { | |||
super.configure(options); | |||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span'; | |||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span'; | |||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0; | |||
options.axisOptions = options.axisOptions || {}; | |||
options.tooltipOptions = options.tooltipOptions || {}; | |||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX; | |||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY; | |||
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span'; | |||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span'; | |||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0; | |||
this.config.valuesOverPoints = args.valuesOverPoints; | |||
} | |||
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX; | |||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY; | |||
setMargins() { | |||
super.setMargins(); | |||
this.leftMargin = Y_AXIS_LEFT_MARGIN; | |||
this.rightMargin = Y_AXIS_RIGHT_MARGIN; | |||
this.config.valuesOverPoints = options.valuesOverPoints; | |||
} | |||
prepareData(data=this.data) { | |||
@@ -3434,11 +3409,13 @@ class AxisChart extends BaseChart { | |||
bindTooltip() { | |||
// NOTE: could be in tooltip itself, as it is a given functionality for its parent | |||
this.container.addEventListener('mousemove', (e) => { | |||
let m = this.measures; | |||
let o = getOffset(this.container); | |||
let relX = e.pageX - o.left - this.leftMargin; | |||
let relY = e.pageY - o.top - this.topMargin; | |||
let relX = e.pageX - o.left - m.margins.left - m.paddings.left; | |||
let relY = e.pageY - o.top; | |||
if(relY < this.height + this.topMargin * 2) { | |||
if(relY < this.height + m.titleHeight + m.margins.top + m.paddings.top | |||
&& relY > m.titleHeight + m.margins.top + m.paddings.top) { | |||
this.mapTooltipXPosition(relX); | |||
} else { | |||
this.tip.hideTip(); | |||
@@ -3452,6 +3429,7 @@ class AxisChart extends BaseChart { | |||
let index = getClosestInArray(relX, s.xAxis.positions, true); | |||
console.log(relX, s.xAxis.positions[index], s.xAxis.positions, this.tip.offset.x); | |||
this.tip.setValues( | |||
s.xAxis.positions[index] + this.tip.offset.x, | |||
s.yExtremes[index] + this.tip.offset.y, | |||
@@ -3471,12 +3449,11 @@ class AxisChart extends BaseChart { | |||
renderLegend() { | |||
let s = this.data; | |||
this.legendArea.textContent = ''; | |||
if(s.datasets.length > 1) { | |||
this.legendArea.textContent = ''; | |||
s.datasets.map((d, i) => { | |||
let barWidth = AXIS_LEGEND_BAR_SIZE; | |||
// let rightEndPoint = this.baseWidth - this.leftMargin - this.rightMargin; | |||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right; | |||
// let multiplier = s.datasets.length - i; | |||
let rect = legendBar( | |||
// rightEndPoint - multiplier * barWidth, // To right align | |||
@@ -3638,7 +3615,6 @@ class AxisChart extends BaseChart { | |||
// removeDataPoint(index = 0) {} | |||
} | |||
// import MultiAxisChart from './charts/MultiAxisChart'; | |||
const chartTypes = { | |||
bar: AxisChart, | |||
line: AxisChart, | |||
@@ -1 +1 @@ | |||
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.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{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}.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}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.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)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px} | |||
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.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{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 .legend-dataset-text{fill:#6c7680;font-weight:600}.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}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.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)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px} |
@@ -29,7 +29,7 @@ let lineCompositeChart = new Chart (c1, { | |||
let barCompositeChart = new Chart (c2, { | |||
data: barCompositeData, | |||
type: 'bar', | |||
height: 190, | |||
height: 210, | |||
colors: ['violet', 'light-blue', '#46a9f9'], | |||
valuesOverPoints: 1, | |||
axisOptions: { | |||
@@ -55,7 +55,7 @@ let typeChartArgs = { | |||
title: "My Awesome Chart", | |||
data: typeData, | |||
type: 'axis-mixed', | |||
height: 250, | |||
height: 300, | |||
colors: customColors, | |||
maxLegendPoints: 6, | |||
@@ -139,7 +139,7 @@ let updateData = { | |||
let updateChart = new Chart("#chart-update", { | |||
data: updateData, | |||
type: 'line', | |||
height: 250, | |||
height: 300, | |||
colors: ['#ff6c03'], | |||
lineOptions: { | |||
// hideLine: 1, | |||
@@ -198,7 +198,7 @@ let plotChartArgs = { | |||
title: "Mean Total Sunspot Count - Yearly", | |||
data: trendsData, | |||
type: 'line', | |||
height: 250, | |||
height: 300, | |||
colors: ['#238e38'], | |||
lineOptions: { | |||
hideDots: 1, | |||
@@ -263,7 +263,7 @@ let eventsChart = new Chart("#chart-events", { | |||
title: "Jupiter's Moons: Semi-major Axis (1000 km)", | |||
data: eventsData, | |||
type: 'bar', | |||
height: 250, | |||
height: 330, | |||
colors: ['grey'], | |||
isNavigable: 1, | |||
}); | |||
@@ -286,8 +286,8 @@ let heatmapArgs = { | |||
title: "Monthly Distribution", | |||
data: heatmapData, | |||
type: 'heatmap', | |||
height: 115, | |||
discreteDomains: 1, | |||
countLabel: 'Level', | |||
colors: HEATMAP_COLORS_BLUE, | |||
legendScale: [0, 1, 2, 4, 5] | |||
}; | |||
@@ -39,9 +39,6 @@ function __$styleInject(css, ref) { | |||
var HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e']; | |||
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||
@@ -319,7 +316,7 @@ var lineCompositeChart = new Chart(c1, { | |||
var barCompositeChart = new Chart(c2, { | |||
data: barCompositeData, | |||
type: 'bar', | |||
height: 190, | |||
height: 210, | |||
colors: ['violet', 'light-blue', '#46a9f9'], | |||
valuesOverPoints: 1, | |||
axisOptions: { | |||
@@ -343,7 +340,7 @@ var typeChartArgs = { | |||
title: "My Awesome Chart", | |||
data: typeData, | |||
type: 'axis-mixed', | |||
height: 250, | |||
height: 300, | |||
colors: customColors, | |||
maxLegendPoints: 6, | |||
@@ -430,7 +427,7 @@ var updateData = { | |||
var updateChart = new Chart("#chart-update", { | |||
data: updateData, | |||
type: 'line', | |||
height: 250, | |||
height: 300, | |||
colors: ['#ff6c03'], | |||
lineOptions: { | |||
// hideLine: 1, | |||
@@ -483,7 +480,7 @@ var plotChartArgs = { | |||
title: "Mean Total Sunspot Count - Yearly", | |||
data: trendsData, | |||
type: 'line', | |||
height: 250, | |||
height: 300, | |||
colors: ['#238e38'], | |||
lineOptions: { | |||
hideDots: 1, | |||
@@ -543,7 +540,7 @@ var eventsChart = new Chart("#chart-events", { | |||
title: "Jupiter's Moons: Semi-major Axis (1000 km)", | |||
data: eventsData, | |||
type: 'bar', | |||
height: 250, | |||
height: 330, | |||
colors: ['grey'], | |||
isNavigable: 1 | |||
}); | |||
@@ -566,8 +563,8 @@ var heatmapArgs = { | |||
title: "Monthly Distribution", | |||
data: heatmapData, | |||
type: 'heatmap', | |||
height: 115, | |||
discreteDomains: 1, | |||
countLabel: 'Level', | |||
colors: HEATMAP_COLORS_BLUE, | |||
legendScale: [0, 1, 2, 4, 5] | |||
}; | |||
@@ -27,7 +27,7 @@ | |||
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;"> | |||
<div class="jumbotron" style="background: transparent;"> | |||
<h1>Frappe Charts</h1> | |||
<p class="mt-2">GitHub-inspired simple and modern charts for the web</p> | |||
<p class="mt-2">GitHub-inspired simple and modern SVG charts for the web</p> | |||
<p class="mt-2">with zero dependencies.</p> | |||
</div> | |||
@@ -75,7 +75,7 @@ | |||
title: "My Awesome Chart", | |||
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage' | |||
height: 250, | |||
height: 300, | |||
colors: ['purple', '#ffa3ef', 'red'] | |||
}); | |||
@@ -206,21 +206,19 @@ | |||
</div> | |||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", { | |||
type: 'heatmap', | |||
height: 115, | |||
data: heatmapData, // object with date/timestamp-value pairs | |||
discreteDomains: 1 // default: 0 | |||
start: startDate, | |||
// A Date object; | |||
// default: today's date in past year | |||
// for an annual heatmap | |||
title: "Monthly Distribution", | |||
data: { | |||
dataPoints: {'1524064033': 8, /* ... */}, | |||
// object with timestamp-value pairs | |||
start: startDate | |||
end: endDate // Date objects | |||
}, | |||
countLabel: 'Level', | |||
discreteDomains: 0 // default: 1 | |||
colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'], | |||
// Set of five incremental colors, | |||
// beginning with a low-saturation color for zero data; | |||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] | |||
// preferably with a low-saturation color for zero data; | |||
// def: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] | |||
});</code></pre> | |||
</div> | |||
</div> | |||
@@ -18,7 +18,6 @@ import precss from 'precss'; | |||
import CleanCSS from 'clean-css'; | |||
import autoprefixer from 'autoprefixer'; | |||
import fs from 'fs'; | |||
import { HEATMAP_LEFT_MARGIN } from './src/js/utils/constants'; | |||
fs.readFile('src/css/charts.scss', (err, css) => { | |||
postcss([precss, autoprefixer]) | |||
@@ -49,6 +49,10 @@ | |||
text-anchor: middle; | |||
} | |||
} | |||
.legend-dataset-text { | |||
fill: #6c7680; | |||
font-weight: 600; | |||
} | |||
} | |||
.graph-svg-tip { | |||
@@ -1,6 +1,6 @@ | |||
import BaseChart from './BaseChart'; | |||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils'; | |||
import { Y_AXIS_LEFT_MARGIN, Y_AXIS_RIGHT_MARGIN, AXIS_LEGEND_BAR_SIZE } from '../utils/constants'; | |||
import { AXIS_LEGEND_BAR_SIZE } from '../utils/constants'; | |||
import { getComponent } from '../objects/ChartComponents'; | |||
import { getOffset, fire } from '../utils/dom'; | |||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals'; | |||
@@ -21,26 +21,27 @@ export default class AxisChart extends BaseChart { | |||
this.setup(); | |||
} | |||
configure(args) { | |||
super.configure(args); | |||
setMeasures(options) { | |||
if(this.data.datasets.length <= 1) { | |||
this.config.showLegend = 0; | |||
this.measures.paddings.bottom = 30; | |||
} | |||
} | |||
args.axisOptions = args.axisOptions || {}; | |||
args.tooltipOptions = args.tooltipOptions || {}; | |||
configure(options) { | |||
super.configure(options); | |||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span'; | |||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span'; | |||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0; | |||
options.axisOptions = options.axisOptions || {}; | |||
options.tooltipOptions = options.tooltipOptions || {}; | |||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX; | |||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY; | |||
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span'; | |||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span'; | |||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0; | |||
this.config.valuesOverPoints = args.valuesOverPoints; | |||
} | |||
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX; | |||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY; | |||
setMargins() { | |||
super.setMargins(); | |||
this.leftMargin = Y_AXIS_LEFT_MARGIN; | |||
this.rightMargin = Y_AXIS_RIGHT_MARGIN; | |||
this.config.valuesOverPoints = options.valuesOverPoints; | |||
} | |||
prepareData(data=this.data) { | |||
@@ -364,11 +365,13 @@ export default class AxisChart extends BaseChart { | |||
bindTooltip() { | |||
// NOTE: could be in tooltip itself, as it is a given functionality for its parent | |||
this.container.addEventListener('mousemove', (e) => { | |||
let m = this.measures; | |||
let o = getOffset(this.container); | |||
let relX = e.pageX - o.left - this.leftMargin; | |||
let relY = e.pageY - o.top - this.topMargin; | |||
let relX = e.pageX - o.left - m.margins.left - m.paddings.left; | |||
let relY = e.pageY - o.top; | |||
if(relY < this.height + this.topMargin * 2) { | |||
if(relY < this.height + m.titleHeight + m.margins.top + m.paddings.top | |||
&& relY > m.titleHeight + m.margins.top + m.paddings.top) { | |||
this.mapTooltipXPosition(relX); | |||
} else { | |||
this.tip.hideTip(); | |||
@@ -382,6 +385,7 @@ export default class AxisChart extends BaseChart { | |||
let index = getClosestInArray(relX, s.xAxis.positions, true); | |||
console.log(relX, s.xAxis.positions[index], s.xAxis.positions, this.tip.offset.x); | |||
this.tip.setValues( | |||
s.xAxis.positions[index] + this.tip.offset.x, | |||
s.yExtremes[index] + this.tip.offset.y, | |||
@@ -401,12 +405,11 @@ export default class AxisChart extends BaseChart { | |||
renderLegend() { | |||
let s = this.data; | |||
this.legendArea.textContent = ''; | |||
if(s.datasets.length > 1) { | |||
this.legendArea.textContent = ''; | |||
s.datasets.map((d, i) => { | |||
let barWidth = AXIS_LEGEND_BAR_SIZE; | |||
// let rightEndPoint = this.baseWidth - this.leftMargin - this.rightMargin; | |||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right; | |||
// let multiplier = s.datasets.length - i; | |||
let rect = legendBar( | |||
// rightEndPoint - multiplier * barWidth, // To right align | |||
@@ -1,13 +1,11 @@ | |||
import SvgTip from '../objects/SvgTip'; | |||
import { $, isElementInViewport, getElementContentWidth } from '../utils/dom'; | |||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText, AXIS_TICK_LENGTH } from '../utils/draw'; | |||
import { BASE_CHART_TOP_MARGIN, BASE_CHART_LEFT_MARGIN, | |||
BASE_CHART_RIGHT_MARGIN, INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS, | |||
ALL_CHART_TYPES, COMPATIBLE_CHARTS, DATA_COLOR_DIVISIONS} from '../utils/constants'; | |||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText, yLine } from '../utils/draw'; | |||
import { BASE_MEASURES, getExtraHeight, getExtraWidth, INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, | |||
DEFAULT_COLORS} from '../utils/constants'; | |||
import { getColor, isValidColor } from '../utils/colors'; | |||
import { runSMILAnimation } from '../utils/animation'; | |||
import { downloadFile, prepareForExport } from '../utils/export'; | |||
import { Chart } from '../chart'; | |||
export default class BaseChart { | |||
constructor(parent, options) { | |||
@@ -23,7 +21,6 @@ export default class BaseChart { | |||
this.rawChartArgs = options; | |||
this.title = options.title || ''; | |||
this.argHeight = options.height || 240; | |||
this.type = options.type || ''; | |||
this.realData = this.prepareData(options.data); | |||
@@ -33,10 +30,18 @@ export default class BaseChart { | |||
this.config = { | |||
showTooltip: 1, // calculate | |||
showLegend: options.showLegend || 1, | |||
showLegend: 1, // calculate | |||
isNavigable: options.isNavigable || 0, | |||
animate: 1 | |||
}; | |||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES)); | |||
let m = this.measures; | |||
this.setMeasures(options); | |||
if(!this.title.length) { m.titleHeight = 0; } | |||
if(!this.config.showLegend) m.legendHeight = 0; | |||
this.argHeight = options.height || m.baseHeight; | |||
this.state = {}; | |||
this.options = {}; | |||
@@ -49,12 +54,12 @@ export default class BaseChart { | |||
this.configure(options); | |||
} | |||
configure() { | |||
this.setMargins(); | |||
prepareData(data) { | |||
return data; | |||
} | |||
// Bind window events | |||
window.addEventListener('resize', () => this.boundDrawFn); | |||
window.addEventListener('orientationchange', () => this.boundDrawFn); | |||
prepareFirstData(data) { | |||
return data; | |||
} | |||
validateColors(colors, type) { | |||
@@ -71,17 +76,22 @@ export default class BaseChart { | |||
return validColors; | |||
} | |||
setMargins() { | |||
setMeasures() { | |||
// Override measures, including those for title and legend | |||
// set config for legend and title | |||
} | |||
configure() { | |||
let height = this.argHeight; | |||
this.baseHeight = height; | |||
this.height = height - 70; | |||
this.topMargin = BASE_CHART_TOP_MARGIN; | |||
this.height = height - getExtraHeight(this.measures); | |||
// Horizontal margins | |||
this.leftMargin = BASE_CHART_LEFT_MARGIN; | |||
this.rightMargin = BASE_CHART_RIGHT_MARGIN; | |||
// Bind window events | |||
window.addEventListener('resize', () => this.draw(true)); | |||
window.addEventListener('orientationchange', () => this.draw(true)); | |||
} | |||
// Has to be called manually | |||
setup() { | |||
this.makeContainer(); | |||
this.updateWidth(); | |||
@@ -90,10 +100,6 @@ export default class BaseChart { | |||
this.draw(false, true); | |||
} | |||
setupComponents() { | |||
this.components = new Map(); | |||
} | |||
makeContainer() { | |||
// Chart needs a dedicated parent element | |||
this.parent.innerHTML = ''; | |||
@@ -140,11 +146,71 @@ export default class BaseChart { | |||
this.setupNavigation(init); | |||
} | |||
calc() {} // builds state | |||
updateWidth() { | |||
this.baseWidth = getElementContentWidth(this.parent); | |||
this.width = this.baseWidth - (this.leftMargin + this.rightMargin); | |||
this.width = this.baseWidth - getExtraWidth(this.measures); | |||
} | |||
makeChartArea() { | |||
if(this.svg) { | |||
this.container.removeChild(this.svg); | |||
} | |||
let m = this.measures; | |||
this.svg = makeSVGContainer( | |||
this.container, | |||
'frappe-chart chart', | |||
this.baseWidth, | |||
this.baseHeight | |||
); | |||
this.svgDefs = makeSVGDefs(this.svg); | |||
if(this.title.length) { | |||
this.titleEL = makeText( | |||
'title', | |||
m.margins.left, | |||
m.margins.top, | |||
this.title, | |||
{ | |||
fontSize: m.titleFontSize, | |||
fill: '#666666', | |||
dy: m.titleFontSize | |||
} | |||
); | |||
} | |||
let top = m.margins.top + m.titleHeight + m.paddings.top; | |||
this.drawArea = makeSVGGroup( | |||
this.type + '-chart chart-draw-area', | |||
`translate(${m.margins.left + m.paddings.left}, ${top})` | |||
); | |||
if(this.config.showLegend) { | |||
top += this.height + m.paddings.bottom; | |||
this.legendArea = makeSVGGroup( | |||
'chart-legend', | |||
`translate(${m.margins.left + m.paddings.left}, ${top})` | |||
); | |||
} | |||
if(this.title.length) { this.svg.appendChild(this.titleEL); } | |||
this.svg.appendChild(this.drawArea); | |||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); } | |||
this.updateTipOffset(m.margins.left + m.paddings.left, m.margins.top + m.paddings.top + m.titleHeight); | |||
} | |||
updateTipOffset(x, y) { | |||
this.tip.offset = { | |||
x: x, | |||
y: y | |||
}; | |||
} | |||
setupComponents() { this.components = new Map(); } | |||
update(data) { | |||
if(!data) { | |||
console.error('No data to update.'); | |||
@@ -154,16 +220,6 @@ export default class BaseChart { | |||
this.render(); | |||
} | |||
prepareData(data=this.data) { | |||
return data; | |||
} | |||
prepareFirstData(data=this.data) { | |||
return data; | |||
} | |||
calc() {} // builds state | |||
render(components=this.components, animate=true) { | |||
if(this.config.isNavigable) { | |||
// Remove all existing overlays | |||
@@ -194,68 +250,6 @@ export default class BaseChart { | |||
} | |||
} | |||
makeChartArea() { | |||
if(this.svg) { | |||
this.container.removeChild(this.svg); | |||
} | |||
let titleAreaHeight = 0; | |||
let legendAreaHeight = 0; | |||
if(this.title.length) { | |||
titleAreaHeight = 40; | |||
} | |||
if(this.config.showLegend) { | |||
legendAreaHeight = 30; | |||
} | |||
this.svg = makeSVGContainer( | |||
this.container, | |||
'frappe-chart chart', | |||
this.baseWidth, | |||
this.baseHeight + titleAreaHeight + legendAreaHeight | |||
); | |||
this.svgDefs = makeSVGDefs(this.svg); | |||
// console.log(this.baseHeight, titleAreaHeight, legendAreaHeight); | |||
if(this.title.length) { | |||
this.titleEL = makeText( | |||
'title', | |||
this.leftMargin - AXIS_TICK_LENGTH * 6, | |||
this.topMargin, | |||
this.title, | |||
{ | |||
fontSize: 12, | |||
fill: '#666666' | |||
} | |||
); | |||
this.svg.appendChild(this.titleEL); | |||
} | |||
let top = this.topMargin + titleAreaHeight; | |||
this.drawArea = makeSVGGroup( | |||
this.svg, | |||
this.type + '-chart', | |||
`translate(${this.leftMargin}, ${top})` | |||
); | |||
top = this.baseHeight - titleAreaHeight; | |||
this.legendArea = makeSVGGroup( | |||
this.svg, | |||
'chart-legend', | |||
`translate(${this.leftMargin}, ${top})` | |||
); | |||
this.updateTipOffset(this.leftMargin, this.topMargin + titleAreaHeight); | |||
} | |||
updateTipOffset(x, y) { | |||
this.tip.offset = { | |||
x: x, | |||
y: y | |||
}; | |||
} | |||
renderLegend() {} | |||
setupNavigation(init=false) { | |||
@@ -302,39 +296,13 @@ export default class BaseChart { | |||
updateDataset() {} | |||
getDifferentChart(type) { | |||
const currentType = this.type; | |||
let args = this.rawChartArgs; | |||
if(type === currentType) return; | |||
if(!ALL_CHART_TYPES.includes(type)) { | |||
console.error(`'${type}' is not a valid chart type.`); | |||
} | |||
if(!COMPATIBLE_CHARTS[currentType].includes(type)) { | |||
console.error(`'${currentType}' chart cannot be converted to a '${type}' chart.`); | |||
} | |||
// whether the new chart can use the existing colors | |||
const useColor = DATA_COLOR_DIVISIONS[currentType] === DATA_COLOR_DIVISIONS[type]; | |||
// Okay, this is anticlimactic | |||
// this function will need to actually be 'changeChartType(type)' | |||
// that will update only the required elements, but for now ... | |||
args.type = type; | |||
args.colors = useColor ? args.colors : undefined; | |||
return new Chart(this.parent, args); | |||
} | |||
boundDrawFn() { | |||
this.draw(true); | |||
} | |||
unbindWindowEvents(){ | |||
window.removeEventListener('resize', () => this.boundDrawFn); | |||
window.removeEventListener('orientationchange', () => this.boundDrawFn); | |||
window.removeEventListener('resize', () => this.boundDrawFn.bind(this)); | |||
window.removeEventListener('orientationchange', () => this.boundDrawFn.bind(this)); | |||
} | |||
export() { | |||
@@ -4,7 +4,7 @@ import { makeText, heatSquare } from '../utils/draw'; | |||
import { DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone, | |||
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils'; | |||
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals'; | |||
import { HEATMAP_TOP_MARGIN, HEATMAP_LEFT_MARGIN, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, | |||
import { getExtraHeight, getExtraWidth, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, | |||
HEATMAP_GUTTER_SIZE } from '../utils/constants'; | |||
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE; | |||
@@ -26,26 +26,26 @@ export default class Heatmap extends BaseChart { | |||
this.setup(); | |||
} | |||
configure(options) { | |||
setMeasures(options) { | |||
let m = this.measures; | |||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1; | |||
super.configure(options); | |||
} | |||
setMargins() { | |||
super.setMargins(); | |||
this.leftMargin = HEATMAP_LEFT_MARGIN; | |||
this.topMargin = HEATMAP_TOP_MARGIN; | |||
m.paddings.top = ROW_HEIGHT * 3; | |||
m.paddings.bottom = 0; | |||
m.legendHeight = ROW_HEIGHT * 2; | |||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK | |||
+ getExtraHeight(m); | |||
let d = this.data; | |||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0; | |||
this.independentWidth = (getWeeksBetween(d.start, d.end) | |||
+ spacing) * COL_WIDTH + this.rightMargin + this.leftMargin; | |||
+ spacing) * COL_WIDTH + m.margins.right + m.margins.left; | |||
} | |||
updateWidth() { | |||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0; | |||
this.baseWidth = (this.state.noOfWeeks + spacing) * COL_WIDTH | |||
+ this.rightMargin + this.leftMargin; | |||
+ getExtraWidth(this.measures); | |||
} | |||
prepareData(data=this.data) { | |||
@@ -246,7 +246,7 @@ export default class Heatmap extends BaseChart { | |||
addDays(startOfWeek, 1); | |||
} | |||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue) { | |||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) { | |||
addDays(startOfWeek, 1); | |||
cols.push(this.getCol(startOfWeek, month, true)); | |||
} | |||
@@ -14,11 +14,11 @@ export default class MultiAxisChart extends AxisChart { | |||
this.type = 'multiaxis'; | |||
} | |||
setMargins() { | |||
super.setMargins(); | |||
setMeasures() { | |||
super.setMeasures(); | |||
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length; | |||
this.leftMargin = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN; | |||
this.rightMargin = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN; | |||
this.measures.margins.left = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN; | |||
this.measures.margins.right = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN; | |||
} | |||
prepareYAxis() { } | |||
@@ -38,7 +38,7 @@ class ChartComponent { | |||
} | |||
setup(parent) { | |||
this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform); | |||
this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent); | |||
} | |||
make() { | |||
@@ -243,9 +243,9 @@ let componentConfigs = { | |||
data.cols.map((week, weekNo) => { | |||
if(weekNo === 1) { | |||
this.labels.push( | |||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true), | |||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(), | |||
{ | |||
fontSize: 11 | |||
fontSize: 9 | |||
} | |||
) | |||
); | |||
@@ -16,12 +16,40 @@ export const DATA_COLOR_DIVISIONS = { | |||
heatmap: HEATMAP_DISTRIBUTION_SIZE | |||
}; | |||
export const BASE_CHART_TOP_MARGIN = 10; | |||
export const BASE_CHART_LEFT_MARGIN = 20; | |||
export const BASE_CHART_RIGHT_MARGIN = 20; | |||
export const BASE_MEASURES = { | |||
margins: { | |||
top: 10, | |||
bottom: 10, | |||
left: 20, | |||
right: 20 | |||
}, | |||
paddings: { | |||
top: 20, | |||
bottom: 40, | |||
left: 30, | |||
right: 10 | |||
}, | |||
baseHeight: 240, | |||
titleHeight: 20, | |||
legendHeight: 30, | |||
titleFontSize: 12, | |||
}; | |||
export function getExtraHeight(m) { | |||
let totalExtraHeight = m.margins.top + m.margins.bottom | |||
+ m.paddings.top + m.paddings.bottom | |||
+ m.titleHeight + m.legendHeight; | |||
return totalExtraHeight; | |||
} | |||
export const Y_AXIS_LEFT_MARGIN = 60; | |||
export const Y_AXIS_RIGHT_MARGIN = 40; | |||
export function getExtraWidth(m) { | |||
let totalExtraWidth = m.margins.left + m.margins.right | |||
+ m.paddings.left + m.paddings.right; | |||
return totalExtraWidth; | |||
} | |||
export const INIT_CHART_UPDATE_TIMEOUT = 700; | |||
export const CHART_POST_ANIMATE_TIMEOUT = 400; | |||
@@ -44,9 +72,6 @@ export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2; | |||
// More colors are difficult to parse visually | |||
export const HEATMAP_DISTRIBUTION_SIZE = 5; | |||
export const HEATMAP_LEFT_MARGIN = 50; | |||
export const HEATMAP_TOP_MARGIN = 25; | |||
export const HEATMAP_SQUARE_SIZE = 10; | |||
export const HEATMAP_GUTTER_SIZE = 2; | |||
@@ -81,12 +81,13 @@ export function makeSVGDefs(svgContainer) { | |||
}); | |||
} | |||
export function makeSVGGroup(parent, className, transform='') { | |||
return createSVG('g', { | |||
export function makeSVGGroup(className, transform='', parent=undefined) { | |||
let args = { | |||
className: className, | |||
inside: parent, | |||
transform: transform | |||
}); | |||
}; | |||
if(parent) args.inside = parent; | |||
return createSVG('g', args); | |||
} | |||
export function wrapInSVGGroup(elements, className='') { | |||