@@ -247,62 +247,6 @@ function fillArray(array, count, element, start=false) { | |||
const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||
function getXLineProps(totalHeight, mode) { | |||
let startAt = totalHeight + 6, height, textStartAt, axisLineClass = ''; | |||
if(mode === 'span') { // long spanning lines | |||
startAt = -7; | |||
height = totalHeight + 15; | |||
textStartAt = totalHeight + 25; | |||
} else if(mode === 'tick'){ // short label lines | |||
startAt = totalHeight; | |||
height = 6; | |||
textStartAt = 9; | |||
axisLineClass = 'x-axis-label'; | |||
} | |||
return [startAt, height, textStartAt, axisLineClass]; | |||
} | |||
// export function getYLineProps(totalWidth, mode, specific=false) { | |||
function getYLineProps(totalWidth, mode) { | |||
// if(specific) { | |||
// return[totalWidth, totalWidth + 5, 'specific-value', 0]; | |||
// } | |||
let width, text_end_at = -9, axisLineClass = '', startAt = 0; | |||
if(mode === 'span') { // long spanning lines | |||
width = totalWidth + 6; | |||
startAt = -6; | |||
} else if(mode === 'tick'){ // short label lines | |||
width = -6; | |||
axisLineClass = 'y-axis-label'; | |||
} | |||
return [width, text_end_at, axisLineClass, startAt]; | |||
} | |||
// let char_width = 8; | |||
// let allowed_space = avg_unit_width * 1.5; | |||
// let allowed_letters = allowed_space / 8; | |||
// return values.map((value, i) => { | |||
// let space_taken = getStringWidth(value, char_width) + 2; | |||
// if(space_taken > allowed_space) { | |||
// if(is_series) { | |||
// // Skip some axis lines if X axis is a series | |||
// let skips = 1; | |||
// while((space_taken/skips)*2 > allowed_space) { | |||
// skips++; | |||
// } | |||
// if(i % skips !== 0) { | |||
// return; | |||
// } | |||
// } else { | |||
// value = value.slice(0, allowed_letters-3) + " ..."; | |||
// } | |||
// } | |||
function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) { | |||
let height, y; | |||
if (yTop <= zeroLine) { | |||
@@ -338,11 +282,30 @@ function equilizeNoOfElements(array1, array2, | |||
return [array1, array2]; | |||
} | |||
const X_LABEL_CLASS = 'x-value-text'; | |||
const Y_LABEL_CLASS = 'y-value-text'; | |||
// let char_width = 8; | |||
// let allowed_space = avgUnitWidth * 1.5; | |||
// let allowed_letters = allowed_space / 8; | |||
// return values.map((value, i) => { | |||
// let space_taken = getStringWidth(value, char_width) + 2; | |||
// if(space_taken > allowed_space) { | |||
// if(is_series) { | |||
// // Skip some axis lines if X axis is a series | |||
// let skips = 1; | |||
// while((space_taken/skips)*2 > allowed_space) { | |||
// skips++; | |||
// } | |||
// if(i % skips !== 0) { | |||
// return; | |||
// } | |||
// } else { | |||
// value = value.slice(0, allowed_letters-3) + " ..."; | |||
// } | |||
// } | |||
// const X_AXIS_LINE_CLASS = 'x-value-text'; | |||
// const Y_AXIS_LINE_CLASS = 'y-value-text'; | |||
const AXIS_TICK_LENGTH = 6; | |||
const LABEL_MARGIN = 4; | |||
const FONT_SIZE = 10; | |||
function $$1(expr, con) { | |||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | |||
@@ -472,125 +435,136 @@ function makeText(className, x, y, content) { | |||
className: className, | |||
x: x, | |||
y: y, | |||
dy: '.32em', | |||
dy: (FONT_SIZE / 2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
innerHTML: content | |||
}); | |||
} | |||
var AxisChartRenderer = (function() { | |||
var AxisChartRenderer = function(totalHeight, totalWidth, zeroLine, avgUnitWidth, xAxisMode, yAxisMode) { | |||
this.totalHeight = totalHeight; | |||
this.totalWidth = totalWidth; | |||
this.zeroLine = zeroLine; | |||
this.avgUnitWidth = avgUnitWidth; | |||
this.xAxisMode = xAxisMode; | |||
this.yAxisMode = yAxisMode; | |||
}; | |||
function makeVertXLine(x, label, totalHeight, mode) { | |||
let height = mode === 'span' ? -1 * AXIS_TICK_LENGTH : totalHeight; | |||
AxisChartRenderer.prototype = { | |||
bar: function (x, yTop, args, color, index, datasetIndex, noOfDatasets) { | |||
let totalWidth = this.avgUnitWidth - args.spaceWidth; | |||
let startX = x - totalWidth/2; | |||
let l = createSVG('line', { | |||
x1: 0, | |||
x2: 0, | |||
y1: totalHeight + AXIS_TICK_LENGTH, | |||
y2: height | |||
}); | |||
let width = totalWidth / noOfDatasets; | |||
let currentX = startX + width * datasetIndex; | |||
let text = createSVG('text', { | |||
x: 0, | |||
y: totalHeight + AXIS_TICK_LENGTH + LABEL_MARGIN, | |||
dy: FONT_SIZE + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': 'middle', | |||
innerHTML: label | |||
}); | |||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight); | |||
let line = createSVG('g', { | |||
transform: `translate(${ x }, 0)` | |||
}); | |||
return createSVG('rect', { | |||
className: `bar mini`, | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
x: currentX, | |||
y: y, | |||
width: width, | |||
height: height | |||
}); | |||
}, | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
dot: function(x, y, args, color, index) { | |||
return createSVG('circle', { | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
cx: x, | |||
cy: y, | |||
r: args.radius | |||
}); | |||
}, | |||
return line; | |||
} | |||
xLine: function(x, label, mode=this.xAxisMode) { | |||
// Draw X axis line in span/tick mode with optional label | |||
let [startAt, height, textStartAt, axisLineClass] = getXLineProps(this.totalHeight, mode); | |||
let l = createSVG('line', { | |||
x1: 0, | |||
x2: 0, | |||
y1: startAt, | |||
y2: height | |||
}); | |||
function makeHoriYLine(y, label, totalWidth, mode) { | |||
let lineType = ''; | |||
let width = mode === 'span' ? totalWidth + AXIS_TICK_LENGTH : AXIS_TICK_LENGTH; | |||
let text = createSVG('text', { | |||
className: X_LABEL_CLASS, | |||
x: 0, | |||
y: textStartAt, | |||
dy: '.71em', | |||
innerHTML: label | |||
}); | |||
let l = createSVG('line', { | |||
className: lineType === "dashed" ? "dashed": "", | |||
x1: -1 * AXIS_TICK_LENGTH, | |||
x2: width, | |||
y1: 0, | |||
y2: 0 | |||
}); | |||
let line = createSVG('g', { | |||
className: `tick ${axisLineClass}`, | |||
transform: `translate(${ x }, 0)` | |||
}); | |||
let text = createSVG('text', { | |||
x: -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH), | |||
y: 0, | |||
dy: (FONT_SIZE / 2 - 2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': 'end', | |||
innerHTML: label+"" | |||
}); | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
let line = createSVG('g', { | |||
transform: `translate(0, ${y})`, | |||
'stroke-opacity': 1 | |||
}); | |||
return line; | |||
}, | |||
if(text === 0 || text === '0') { | |||
line.style.stroke = "rgba(27, 31, 35, 0.6)"; | |||
} | |||
yLine: function(y, label, mode=this.yAxisMode) { | |||
// TODO: stroke type | |||
let lineType = ''; | |||
let [width, textEndAt, axisLineClass, startAt] = getYLineProps(this.totalWidth, mode); | |||
let l = createSVG('line', { | |||
className: lineType === "dashed" ? "dashed": "", | |||
x1: startAt, | |||
x2: width, | |||
y1: 0, | |||
y2: 0 | |||
}); | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
let text = createSVG('text', { | |||
className: Y_LABEL_CLASS, | |||
x: textEndAt, | |||
y: 0, | |||
dy: '.32em', | |||
innerHTML: label+"" | |||
}); | |||
return line; | |||
} | |||
let line = createSVG('g', { | |||
className: `tick ${axisLineClass}`, | |||
transform: `translate(0, ${y})`, | |||
'stroke-opacity': 1 | |||
}); | |||
class AxisChartRenderer { | |||
constructor(state) { | |||
this.updateState(state); | |||
} | |||
// if(darker) { | |||
// line.style.stroke = "rgba(27, 31, 35, 0.6)"; | |||
// } | |||
updateState(state) { | |||
this.totalHeight = state.totalHeight; | |||
this.totalWidth = state.totalWidth; | |||
this.zeroLine = state.zeroLine; | |||
this.avgUnitWidth = state.avgUnitWidth; | |||
this.xAxisMode = state.xAxisMode; | |||
this.yAxisMode = state.yAxisMode; | |||
} | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) { | |||
let totalWidth = this.avgUnitWidth - args.spaceWidth; | |||
let startX = x - totalWidth/2; | |||
return line; | |||
}, | |||
let width = totalWidth / noOfDatasets; | |||
let currentX = startX + width * datasetIndex; | |||
xRegion: function(x1, x2, label) { }, | |||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight); | |||
yRegion: function(y1, y2, label) { } | |||
}; | |||
return createSVG('rect', { | |||
className: `bar mini`, | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
x: currentX, | |||
y: y, | |||
width: width, | |||
height: height | |||
}); | |||
} | |||
return AxisChartRenderer; | |||
})(); | |||
dot(x, y, args, color, index) { | |||
return createSVG('circle', { | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
cx: x, | |||
cy: y, | |||
r: args.radius | |||
}); | |||
} | |||
xLine(x, label, mode=this.xAxisMode) { | |||
// Draw X axis line in span/tick mode with optional label | |||
return makeVertXLine(x, label, this.totalHeight, mode); | |||
} | |||
yLine(y, label, mode=this.yAxisMode) { | |||
return makeHoriYLine(y, label, this.totalWidth, mode); | |||
} | |||
xMarker() {} | |||
yMarker() {} | |||
xRegion() {} | |||
yRegion() {} | |||
} | |||
const PRESET_COLOR_MAP = { | |||
'light-blue': '#7cd6fd', | |||
@@ -725,10 +699,12 @@ class BaseChart { | |||
this.setColors(); | |||
this.setMargins(); | |||
// constants | |||
this.config = { | |||
showTooltip: 1, | |||
showLegend: 1, | |||
isNavigable: 0 | |||
isNavigable: 0, | |||
animate: 1 | |||
}; | |||
} | |||
@@ -770,17 +746,21 @@ class BaseChart { | |||
parseData() { | |||
let data = this.rawChartArgs.data; | |||
// Check and all | |||
// If all good | |||
this.data = data; | |||
let valid = this.checkData(data); | |||
if(!valid) return false; | |||
if(!this.config.animate) { | |||
this.data = data; | |||
} else { | |||
[this.data, this.firstUpdateData] = | |||
this.getFirstUpdateData(data); | |||
} | |||
return true; | |||
} | |||
checkData() {} | |||
getFirstUpdateData(data) {} | |||
setup() { | |||
if(this.validate()) { | |||
this._setup(); | |||
@@ -791,19 +771,56 @@ class BaseChart { | |||
this.bindWindowEvents(); | |||
this.setupConstants(); | |||
// this.setupComponents(); | |||
this.makeContainer(); | |||
this.makeTooltip(); // without binding | |||
this.draw(true); | |||
} | |||
bindWindowEvents() { | |||
window.addEventListener('resize orientationchange', () => this.draw()); | |||
} | |||
setupConstants() {} | |||
setupComponents() { | |||
// Components config | |||
this.components = []; | |||
} | |||
makeContainer() { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | |||
<div class="frappe-chart graphics"></div> | |||
<div class="graph-stats-container"></div>` | |||
}); | |||
// Chart needs a dedicated parent element | |||
this.parent.innerHTML = ''; | |||
this.parent.appendChild(this.container); | |||
this.chartWrapper = this.container.querySelector('.frappe-chart'); | |||
this.statsWrapper = this.container.querySelector('.graph-stats-container'); | |||
} | |||
makeTooltip() { | |||
this.tip = new SvgTip({ | |||
parent: this.chartWrapper, | |||
colors: this.colors | |||
}); | |||
this.bindTooltip(); | |||
} | |||
draw(init=false) { | |||
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.) | |||
// (draw everything, layers, groups, units) | |||
this.calc(); | |||
this.setupRenderer(); // this chart's rendered with the config | |||
this.refreshRenderer(); // this chart's rendered with the config | |||
this.setupComponents(); | |||
this.makeChartArea(); | |||
this.makeLayers(); | |||
@@ -811,14 +828,18 @@ class BaseChart { | |||
this.renderLegend(); | |||
this.setupNavigation(init); | |||
if(init) this.update(this.data); | |||
if(this.config.animate) this.update(this.firstUpdateData); | |||
} | |||
bindWindowEvents() { | |||
window.addEventListener('resize', () => this.draw()); | |||
window.addEventListener('orientationchange', () => this.draw()); | |||
update() { | |||
// difference from draw(): yes you do rerender everything here as well, | |||
// but not things like the chart itself, mosty only at component level | |||
this.reCalc(); | |||
this.reRender(); | |||
} | |||
refreshRenderer() {} | |||
calcWidth() { | |||
let outerAnnotationsWidth = 0; | |||
// let charWidth = 8; | |||
@@ -832,37 +853,11 @@ class BaseChart { | |||
this.width = this.baseWidth - this.translateX * 2; | |||
} | |||
setupConstants() {} | |||
calc() { | |||
this.calcWidth(); | |||
this.reCalc(); | |||
} | |||
setupRenderer() {} | |||
setupComponents() { | |||
// Components config | |||
this.components = []; | |||
} | |||
makeContainer() { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | |||
<div class="frappe-chart graphics"></div> | |||
<div class="graph-stats-container"></div>` | |||
}); | |||
// Chart needs a dedicated parent element | |||
this.parent.innerHTML = ''; | |||
this.parent.appendChild(this.container); | |||
this.chartWrapper = this.container.querySelector('.frappe-chart'); | |||
this.statsWrapper = this.container.querySelector('.graph-stats-container'); | |||
} | |||
makeChartArea() { | |||
this.svg = makeSVGContainer( | |||
this.chartWrapper, | |||
@@ -896,11 +891,6 @@ class BaseChart { | |||
}); | |||
} | |||
update() { | |||
this.reCalc(); | |||
this.reRender(); | |||
} | |||
reCalc() { | |||
// Will update values(state) | |||
// Will recalc specific parts depending on the update | |||
@@ -931,13 +921,6 @@ class BaseChart { | |||
calcInitStage() {} | |||
makeTooltip() { | |||
this.tip = new SvgTip({ | |||
parent: this.chartWrapper, | |||
colors: this.colors | |||
}); | |||
this.bindTooltip(); | |||
} | |||
renderLegend() {} | |||
@@ -1391,7 +1374,7 @@ class AxisChart extends BaseChart { | |||
this.is_series = args.is_series; | |||
this.format_tooltip_y = args.format_tooltip_y; | |||
this.format_tooltip_x = args.format_tooltip_x; | |||
this.zero_line = this.height; | |||
this.zeroLine = this.height; | |||
} | |||
parseData() { | |||
@@ -1457,22 +1440,27 @@ class AxisChart extends BaseChart { | |||
} | |||
// this should be inherent in BaseChart | |||
getRenderer() { | |||
// These args are basically the current state/config of the chart, | |||
refreshRenderer() { | |||
// These args are basically the current state of the chart, | |||
// with constant and alive params mixed | |||
return new AxisChartRenderer(this.height, this.width, | |||
this.zero_line, this.avg_unit_width, this.xAxisMode, this.yAxisMode); | |||
this.renderer = new AxisChartRenderer({ | |||
totalHeight: this.height, | |||
totalWidth: this.width, | |||
zeroLine: this.zeroLine, | |||
avgUnitWidth: this.avgUnitWidth, | |||
xAxisMode: this.xAxisMode, | |||
yAxisMode: this.yAxisMode | |||
}); | |||
} | |||
setupComponents() { | |||
// Must have access to all current data things | |||
let self = this; | |||
let renderer = this.getRenderer(); | |||
this.yAxis = { | |||
layerClass: 'y axis', | |||
layer: undefined, | |||
make: self.makeYLines, | |||
makeArgs: [renderer, self.yAxisPositions, self.yAxisLabels], | |||
make: self.makeYLines.bind(self), | |||
makeArgs: [self.yAxisPositions, self.yAxisLabels], | |||
store: [], | |||
// animate? or update? will come to while implementing | |||
animate: self.animateYLines, | |||
@@ -1481,10 +1469,9 @@ class AxisChart extends BaseChart { | |||
this.xAxis = { | |||
layerClass: 'x axis', | |||
layer: undefined, | |||
make: self.makeXLines, | |||
// TODO: better context of renderer | |||
make: self.makeXLines.bind(self), | |||
// TODO: will implement series skip with avgUnitWidth and isSeries later | |||
makeArgs: [renderer, self.xPositions, self.xAxisLabels], | |||
makeArgs: [self.xPositions, self.xAxisLabels], | |||
store: [], | |||
animate: self.animateXLines | |||
}; | |||
@@ -1535,12 +1522,12 @@ class AxisChart extends BaseChart { | |||
} | |||
setup_x() { | |||
this.set_avg_unit_width_and_x_offset(); | |||
this.set_avgUnitWidth_and_x_offset(); | |||
if(this.xPositions) { | |||
this.x_old_axis_positions = this.xPositions.slice(); | |||
} | |||
this.xPositions = this.xAxisLabels.map((d, i) => | |||
floatTwo(this.x_offset + i * this.avg_unit_width)); | |||
floatTwo(this.x_offset + i * this.avgUnitWidth)); | |||
if(!this.x_old_axis_positions) { | |||
this.x_old_axis_positions = this.xPositions.slice(); | |||
@@ -1592,28 +1579,27 @@ class AxisChart extends BaseChart { | |||
zero_index = (-1) * max / interval + (y_pts.length - 1); | |||
} | |||
if(this.zero_line) this.old_zero_line = this.zero_line; | |||
this.zero_line = this.height - (zero_index * interval_height); | |||
if(!this.old_zero_line) this.old_zero_line = this.zero_line; | |||
if(this.zeroLine) this.old_zeroLine = this.zeroLine; | |||
this.zeroLine = this.height - (zero_index * interval_height); | |||
if(!this.old_zeroLine) this.old_zeroLine = this.zeroLine; | |||
// Make positions arrays for y elements | |||
if(this.yAxisPositions) this.oldYAxisPositions = this.yAxisPositions; | |||
this.yAxisPositions = this.yAxisLabels.map(d => this.zero_line - d * this.multiplier); | |||
this.yAxisPositions = this.yAxisLabels.map(d => this.zeroLine - d * this.multiplier); | |||
if(!this.oldYAxisPositions) this.oldYAxisPositions = this.yAxisPositions; | |||
// if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
// this.yAnnotationPositions = this.specific_values.map(d => this.zero_line - d.value * this.multiplier); | |||
// this.yAnnotationPositions = this.specific_values.map(d => this.zeroLine - d.value * this.multiplier); | |||
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
} | |||
makeXLines(renderer, positions, values) { | |||
// TODO: draw as per condition | |||
return positions.map((position, i) => renderer.xLine(position, values[i])); | |||
makeXLines(positions, values) { | |||
// TODO: draw as per condition (with/without label etc.) | |||
return positions.map((position, i) => this.renderer.xLine(position, values[i])); | |||
} | |||
makeYLines(renderer, positions, values) { | |||
return positions.map((position, i) => renderer.yLine(position, values[i])); | |||
makeYLines(positions, values) { | |||
return positions.map((position, i) => this.renderer.yLine(position, values[i])); | |||
} | |||
draw_graph(init=false) { | |||
@@ -1642,7 +1628,7 @@ class AxisChart extends BaseChart { | |||
let data = []; | |||
this.y.map((d, i) => { | |||
// Anim: Don't draw initial values, store them and update later | |||
d.yUnitPositions = new Array(d.values.length).fill(this.zero_line); // no value | |||
d.yUnitPositions = new Array(d.values.length).fill(this.zeroLine); // no value | |||
data.push({values: d.values}); | |||
d.svg_units = []; | |||
@@ -1686,7 +1672,7 @@ class AxisChart extends BaseChart { | |||
units_group.textContent = ''; | |||
units_array.length = 0; | |||
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zero_line, this.avg_unit_width); | |||
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zeroLine, this.avgUnitWidth); | |||
y_values.map((y, i) => { | |||
let data_unit = unit_AxisChartRenderer[unit.type]( | |||
@@ -1734,8 +1720,8 @@ class AxisChart extends BaseChart { | |||
for(var i=this.xPositions.length - 1; i >= 0 ; i--) { | |||
let x_val = this.xPositions[i]; | |||
// let delta = i === 0 ? this.avg_unit_width : x_val - this.xPositions[i-1]; | |||
if(relX > x_val - this.avg_unit_width/2) { | |||
// let delta = i === 0 ? this.avgUnitWidth : x_val - this.xPositions[i-1]; | |||
if(relX > x_val - this.avgUnitWidth/2) { | |||
let x = x_val + this.translateX; | |||
let y = this.y_min_tops[i] + this.translateY; | |||
@@ -1778,7 +1764,7 @@ class AxisChart extends BaseChart { | |||
this.calcYDependencies(); | |||
// Got the values? Now begin drawing | |||
this.animator = new Animator(this.height, this.width, this.zero_line, this.avg_unit_width); | |||
this.animator = new Animator(this.height, this.width, this.zeroLine, this.avgUnitWidth); | |||
this.animate_graphs(); | |||
@@ -1927,9 +1913,9 @@ class AxisChart extends BaseChart { | |||
fire(this.parent, "data-select", this.getDataPoint()); | |||
} | |||
set_avg_unit_width_and_x_offset() { | |||
set_avgUnitWidth_and_x_offset() { | |||
// Set the ... you get it | |||
this.avg_unit_width = this.width/(this.xAxisLabels.length - 1); | |||
this.avgUnitWidth = this.width/(this.xAxisLabels.length - 1); | |||
this.x_offset = 0; | |||
} | |||
@@ -1948,7 +1934,7 @@ class AxisChart extends BaseChart { | |||
calcYDependencies() { | |||
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999); | |||
this.y.map(d => { | |||
d.yUnitPositions = d.values.map( val => floatTwo(this.zero_line - val * this.multiplier)); | |||
d.yUnitPositions = d.values.map( val => floatTwo(this.zeroLine - val * this.multiplier)); | |||
d.yUnitPositions.map( (yUnitPosition, i) => { | |||
if(yUnitPosition < this.y_min_tops[i]) { | |||
this.y_min_tops[i] = yUnitPosition; | |||
@@ -1972,11 +1958,11 @@ class BarChart extends AxisChart { | |||
setup_values() { | |||
super.setup_values(); | |||
this.x_offset = this.avg_unit_width; | |||
this.x_offset = this.avgUnitWidth; | |||
this.unit_args = { | |||
type: 'bar', | |||
args: { | |||
spaceWidth: this.avg_unit_width/2, | |||
spaceWidth: this.avgUnitWidth/2, | |||
} | |||
}; | |||
} | |||
@@ -2034,9 +2020,9 @@ class BarChart extends AxisChart { | |||
this.updateCurrentDataPoint(this.currentIndex + 1); | |||
} | |||
set_avg_unit_width_and_x_offset() { | |||
this.avg_unit_width = this.width/(this.xAxisLabels.length + 1); | |||
this.x_offset = this.avg_unit_width; | |||
set_avgUnitWidth_and_x_offset() { | |||
this.avgUnitWidth = this.width/(this.xAxisLabels.length + 1); | |||
this.x_offset = this.avgUnitWidth; | |||
} | |||
} | |||
@@ -2118,7 +2104,7 @@ class LineChart extends AxisChart { | |||
fill_region_for_dataset(d, color, points_str) { | |||
let gradient_id = makeGradient(this.svg_defs, color, true); | |||
let pathStr = "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`; | |||
let pathStr = "M" + `0,${this.zeroLine}L` + points_str + `L${this.width},${this.zeroLine}`; | |||
d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`); | |||
this.paths_groups[d.index].appendChild(d.regionPath); | |||
@@ -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{font-size:11px;fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .data-points circle{stroke:#fff;stroke-width:2}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .tick.x-axis-label{display:block}.chart-container .tick .specific-value{text-anchor:start}.chart-container .tick .y-value-text{text-anchor:end}.chart-container .tick .x-value-text{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{-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 .data-points circle{stroke:#fff;stroke-width:2}.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} |
@@ -13,7 +13,7 @@ export default class AxisChart extends BaseChart { | |||
this.is_series = args.is_series; | |||
this.format_tooltip_y = args.format_tooltip_y; | |||
this.format_tooltip_x = args.format_tooltip_x; | |||
this.zero_line = this.height; | |||
this.zeroLine = this.height; | |||
} | |||
parseData() { | |||
@@ -79,22 +79,27 @@ export default class AxisChart extends BaseChart { | |||
} | |||
// this should be inherent in BaseChart | |||
getRenderer() { | |||
// These args are basically the current state/config of the chart, | |||
refreshRenderer() { | |||
// These args are basically the current state of the chart, | |||
// with constant and alive params mixed | |||
return new AxisChartRenderer(this.height, this.width, | |||
this.zero_line, this.avg_unit_width, this.xAxisMode, this.yAxisMode); | |||
this.renderer = new AxisChartRenderer({ | |||
totalHeight: this.height, | |||
totalWidth: this.width, | |||
zeroLine: this.zeroLine, | |||
avgUnitWidth: this.avgUnitWidth, | |||
xAxisMode: this.xAxisMode, | |||
yAxisMode: this.yAxisMode | |||
}); | |||
} | |||
setupComponents() { | |||
// Must have access to all current data things | |||
let self = this; | |||
let renderer = this.getRenderer(); | |||
this.yAxis = { | |||
layerClass: 'y axis', | |||
layer: undefined, | |||
make: self.makeYLines, | |||
makeArgs: [renderer, self.yAxisPositions, self.yAxisLabels], | |||
make: self.makeYLines.bind(self), | |||
makeArgs: [self.yAxisPositions, self.yAxisLabels], | |||
store: [], | |||
// animate? or update? will come to while implementing | |||
animate: self.animateYLines, | |||
@@ -103,10 +108,9 @@ export default class AxisChart extends BaseChart { | |||
this.xAxis = { | |||
layerClass: 'x axis', | |||
layer: undefined, | |||
make: self.makeXLines, | |||
// TODO: better context of renderer | |||
make: self.makeXLines.bind(self), | |||
// TODO: will implement series skip with avgUnitWidth and isSeries later | |||
makeArgs: [renderer, self.xPositions, self.xAxisLabels], | |||
makeArgs: [self.xPositions, self.xAxisLabels], | |||
store: [], | |||
animate: self.animateXLines | |||
}; | |||
@@ -157,12 +161,12 @@ export default class AxisChart extends BaseChart { | |||
} | |||
setup_x() { | |||
this.set_avg_unit_width_and_x_offset(); | |||
this.set_avgUnitWidth_and_x_offset(); | |||
if(this.xPositions) { | |||
this.x_old_axis_positions = this.xPositions.slice(); | |||
} | |||
this.xPositions = this.xAxisLabels.map((d, i) => | |||
floatTwo(this.x_offset + i * this.avg_unit_width)); | |||
floatTwo(this.x_offset + i * this.avgUnitWidth)); | |||
if(!this.x_old_axis_positions) { | |||
this.x_old_axis_positions = this.xPositions.slice(); | |||
@@ -214,28 +218,27 @@ export default class AxisChart extends BaseChart { | |||
zero_index = (-1) * max / interval + (y_pts.length - 1); | |||
} | |||
if(this.zero_line) this.old_zero_line = this.zero_line; | |||
this.zero_line = this.height - (zero_index * interval_height); | |||
if(!this.old_zero_line) this.old_zero_line = this.zero_line; | |||
if(this.zeroLine) this.old_zeroLine = this.zeroLine; | |||
this.zeroLine = this.height - (zero_index * interval_height); | |||
if(!this.old_zeroLine) this.old_zeroLine = this.zeroLine; | |||
// Make positions arrays for y elements | |||
if(this.yAxisPositions) this.oldYAxisPositions = this.yAxisPositions; | |||
this.yAxisPositions = this.yAxisLabels.map(d => this.zero_line - d * this.multiplier); | |||
this.yAxisPositions = this.yAxisLabels.map(d => this.zeroLine - d * this.multiplier); | |||
if(!this.oldYAxisPositions) this.oldYAxisPositions = this.yAxisPositions; | |||
// if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
// this.yAnnotationPositions = this.specific_values.map(d => this.zero_line - d.value * this.multiplier); | |||
// this.yAnnotationPositions = this.specific_values.map(d => this.zeroLine - d.value * this.multiplier); | |||
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions; | |||
} | |||
makeXLines(renderer, positions, values) { | |||
// TODO: draw as per condition | |||
return positions.map((position, i) => renderer.xLine(position, values[i])); | |||
makeXLines(positions, values) { | |||
// TODO: draw as per condition (with/without label etc.) | |||
return positions.map((position, i) => this.renderer.xLine(position, values[i])); | |||
} | |||
makeYLines(renderer, positions, values) { | |||
return positions.map((position, i) => renderer.yLine(position, values[i])); | |||
makeYLines(positions, values) { | |||
return positions.map((position, i) => this.renderer.yLine(position, values[i])); | |||
} | |||
draw_graph(init=false) { | |||
@@ -264,7 +267,7 @@ export default class AxisChart extends BaseChart { | |||
let data = []; | |||
this.y.map((d, i) => { | |||
// Anim: Don't draw initial values, store them and update later | |||
d.yUnitPositions = new Array(d.values.length).fill(this.zero_line); // no value | |||
d.yUnitPositions = new Array(d.values.length).fill(this.zeroLine); // no value | |||
data.push({values: d.values}); | |||
d.svg_units = []; | |||
@@ -308,7 +311,7 @@ export default class AxisChart extends BaseChart { | |||
units_group.textContent = ''; | |||
units_array.length = 0; | |||
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zero_line, this.avg_unit_width); | |||
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zeroLine, this.avgUnitWidth); | |||
y_values.map((y, i) => { | |||
let data_unit = unit_AxisChartRenderer[unit.type]( | |||
@@ -356,8 +359,8 @@ export default class AxisChart extends BaseChart { | |||
for(var i=this.xPositions.length - 1; i >= 0 ; i--) { | |||
let x_val = this.xPositions[i]; | |||
// let delta = i === 0 ? this.avg_unit_width : x_val - this.xPositions[i-1]; | |||
if(relX > x_val - this.avg_unit_width/2) { | |||
// let delta = i === 0 ? this.avgUnitWidth : x_val - this.xPositions[i-1]; | |||
if(relX > x_val - this.avgUnitWidth/2) { | |||
let x = x_val + this.translateX; | |||
let y = this.y_min_tops[i] + this.translateY; | |||
@@ -400,7 +403,7 @@ export default class AxisChart extends BaseChart { | |||
this.calcYDependencies(); | |||
// Got the values? Now begin drawing | |||
this.animator = new Animator(this.height, this.width, this.zero_line, this.avg_unit_width); | |||
this.animator = new Animator(this.height, this.width, this.zeroLine, this.avgUnitWidth); | |||
this.animate_graphs(); | |||
@@ -549,9 +552,9 @@ export default class AxisChart extends BaseChart { | |||
fire(this.parent, "data-select", this.getDataPoint()); | |||
} | |||
set_avg_unit_width_and_x_offset() { | |||
set_avgUnitWidth_and_x_offset() { | |||
// Set the ... you get it | |||
this.avg_unit_width = this.width/(this.xAxisLabels.length - 1); | |||
this.avgUnitWidth = this.width/(this.xAxisLabels.length - 1); | |||
this.x_offset = 0; | |||
} | |||
@@ -570,7 +573,7 @@ export default class AxisChart extends BaseChart { | |||
calcYDependencies() { | |||
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999); | |||
this.y.map(d => { | |||
d.yUnitPositions = d.values.map( val => floatTwo(this.zero_line - val * this.multiplier)); | |||
d.yUnitPositions = d.values.map( val => floatTwo(this.zeroLine - val * this.multiplier)); | |||
d.yUnitPositions.map( (yUnitPosition, i) => { | |||
if(yUnitPosition < this.y_min_tops[i]) { | |||
this.y_min_tops[i] = yUnitPosition; | |||
@@ -12,11 +12,11 @@ export default class BarChart extends AxisChart { | |||
setup_values() { | |||
super.setup_values(); | |||
this.x_offset = this.avg_unit_width; | |||
this.x_offset = this.avgUnitWidth; | |||
this.unit_args = { | |||
type: 'bar', | |||
args: { | |||
spaceWidth: this.avg_unit_width/2, | |||
spaceWidth: this.avgUnitWidth/2, | |||
} | |||
}; | |||
} | |||
@@ -74,8 +74,8 @@ export default class BarChart extends AxisChart { | |||
this.updateCurrentDataPoint(this.currentIndex + 1); | |||
} | |||
set_avg_unit_width_and_x_offset() { | |||
this.avg_unit_width = this.width/(this.xAxisLabels.length + 1); | |||
this.x_offset = this.avg_unit_width; | |||
set_avgUnitWidth_and_x_offset() { | |||
this.avgUnitWidth = this.width/(this.xAxisLabels.length + 1); | |||
this.x_offset = this.avgUnitWidth; | |||
} | |||
} |
@@ -41,10 +41,12 @@ export default class BaseChart { | |||
this.setColors(); | |||
this.setMargins(); | |||
// constants | |||
this.config = { | |||
showTooltip: 1, | |||
showLegend: 1, | |||
isNavigable: 0 | |||
isNavigable: 0, | |||
animate: 1 | |||
}; | |||
} | |||
@@ -88,17 +90,21 @@ export default class BaseChart { | |||
parseData() { | |||
let data = this.rawChartArgs.data; | |||
// Check and all | |||
// If all good | |||
this.data = data; | |||
let valid = this.checkData(data); | |||
if(!valid) return false; | |||
if(!this.config.animate) { | |||
this.data = data; | |||
} else { | |||
[this.data, this.firstUpdateData] = | |||
this.getFirstUpdateData(data); | |||
} | |||
return true; | |||
} | |||
checkData() {} | |||
getFirstUpdateData(data) {} | |||
setup() { | |||
if(this.validate()) { | |||
this._setup(); | |||
@@ -109,19 +115,56 @@ export default class BaseChart { | |||
this.bindWindowEvents(); | |||
this.setupConstants(); | |||
// this.setupComponents(); | |||
this.makeContainer(); | |||
this.makeTooltip(); // without binding | |||
this.draw(true); | |||
} | |||
bindWindowEvents() { | |||
window.addEventListener('resize orientationchange', () => this.draw()); | |||
} | |||
setupConstants() {} | |||
setupComponents() { | |||
// Components config | |||
this.components = []; | |||
} | |||
makeContainer() { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | |||
<div class="frappe-chart graphics"></div> | |||
<div class="graph-stats-container"></div>` | |||
}); | |||
// Chart needs a dedicated parent element | |||
this.parent.innerHTML = ''; | |||
this.parent.appendChild(this.container); | |||
this.chartWrapper = this.container.querySelector('.frappe-chart'); | |||
this.statsWrapper = this.container.querySelector('.graph-stats-container'); | |||
} | |||
makeTooltip() { | |||
this.tip = new SvgTip({ | |||
parent: this.chartWrapper, | |||
colors: this.colors | |||
}); | |||
this.bindTooltip(); | |||
} | |||
draw(init=false) { | |||
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.) | |||
// (draw everything, layers, groups, units) | |||
this.calc(); | |||
this.setupRenderer(); // this chart's rendered with the config | |||
this.refreshRenderer() // this chart's rendered with the config | |||
this.setupComponents(); | |||
this.makeChartArea(); | |||
this.makeLayers(); | |||
@@ -129,14 +172,18 @@ export default class BaseChart { | |||
this.renderLegend(); | |||
this.setupNavigation(init); | |||
if(init) this.update(this.data); | |||
if(this.config.animate) this.update(this.firstUpdateData); | |||
} | |||
bindWindowEvents() { | |||
window.addEventListener('resize', () => this.draw()); | |||
window.addEventListener('orientationchange', () => this.draw()); | |||
update() { | |||
// difference from draw(): yes you do rerender everything here as well, | |||
// but not things like the chart itself, mosty only at component level | |||
this.reCalc(); | |||
this.reRender(); | |||
} | |||
refreshRenderer() {} | |||
calcWidth() { | |||
let outerAnnotationsWidth = 0; | |||
// let charWidth = 8; | |||
@@ -150,37 +197,11 @@ export default class BaseChart { | |||
this.width = this.baseWidth - this.translateX * 2; | |||
} | |||
setupConstants() {} | |||
calc() { | |||
this.calcWidth(); | |||
this.reCalc(); | |||
} | |||
setupRenderer() {} | |||
setupComponents() { | |||
// Components config | |||
this.components = []; | |||
} | |||
makeContainer() { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | |||
<div class="frappe-chart graphics"></div> | |||
<div class="graph-stats-container"></div>` | |||
}); | |||
// Chart needs a dedicated parent element | |||
this.parent.innerHTML = ''; | |||
this.parent.appendChild(this.container); | |||
this.chartWrapper = this.container.querySelector('.frappe-chart'); | |||
this.statsWrapper = this.container.querySelector('.graph-stats-container'); | |||
} | |||
makeChartArea() { | |||
this.svg = makeSVGContainer( | |||
this.chartWrapper, | |||
@@ -214,11 +235,6 @@ export default class BaseChart { | |||
}); | |||
} | |||
update() { | |||
this.reCalc(); | |||
this.reRender(); | |||
} | |||
reCalc() { | |||
// Will update values(state) | |||
// Will recalc specific parts depending on the update | |||
@@ -249,13 +265,6 @@ export default class BaseChart { | |||
calcInitStage() {} | |||
makeTooltip() { | |||
this.tip = new SvgTip({ | |||
parent: this.chartWrapper, | |||
colors: this.colors | |||
}); | |||
this.bindTooltip(); | |||
} | |||
renderLegend() {} | |||
@@ -79,7 +79,7 @@ export default class LineChart extends AxisChart { | |||
fill_region_for_dataset(d, color, points_str) { | |||
let gradient_id = makeGradient(this.svg_defs, color, true); | |||
let pathStr = "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`; | |||
let pathStr = "M" + `0,${this.zeroLine}L` + points_str + `L${this.width},${this.zeroLine}`; | |||
d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`); | |||
this.paths_groups[d.index].appendChild(d.regionPath); | |||
@@ -0,0 +1,2 @@ | |||
import { getBarHeightAndYAttr } from '../utils/draw-utils'; | |||
@@ -1,67 +1,7 @@ | |||
import { fillArray } from '../utils/helpers'; | |||
const AXIS_TICK_LENGTH = 6; | |||
const LABEL_MARGIN = 4; | |||
const MIN_BAR_PERCENT_HEIGHT = 0.01; | |||
export function verticalLineProps(start, height, label='down') { | |||
// | |||
} | |||
export function getXLineProps(totalHeight, mode) { | |||
let startAt = totalHeight + 6, height, textStartAt, axisLineClass = ''; | |||
if(mode === 'span') { // long spanning lines | |||
startAt = -7; | |||
height = totalHeight + 15; | |||
textStartAt = totalHeight + 25; | |||
} else if(mode === 'tick'){ // short label lines | |||
startAt = totalHeight; | |||
height = 6; | |||
textStartAt = 9; | |||
axisLineClass = 'x-axis-label'; | |||
} | |||
return [startAt, height, textStartAt, axisLineClass]; | |||
} | |||
// export function getYLineProps(totalWidth, mode, specific=false) { | |||
export function getYLineProps(totalWidth, mode) { | |||
// if(specific) { | |||
// return[totalWidth, totalWidth + 5, 'specific-value', 0]; | |||
// } | |||
let width, text_end_at = -9, axisLineClass = '', startAt = 0; | |||
if(mode === 'span') { // long spanning lines | |||
width = totalWidth + 6; | |||
startAt = -6; | |||
} else if(mode === 'tick'){ // short label lines | |||
width = -6; | |||
axisLineClass = 'y-axis-label'; | |||
} | |||
return [width, text_end_at, axisLineClass, startAt]; | |||
} | |||
// let char_width = 8; | |||
// let allowed_space = avg_unit_width * 1.5; | |||
// let allowed_letters = allowed_space / 8; | |||
// return values.map((value, i) => { | |||
// let space_taken = getStringWidth(value, char_width) + 2; | |||
// if(space_taken > allowed_space) { | |||
// if(is_series) { | |||
// // Skip some axis lines if X axis is a series | |||
// let skips = 1; | |||
// while((space_taken/skips)*2 > allowed_space) { | |||
// skips++; | |||
// } | |||
// if(i % skips !== 0) { | |||
// return; | |||
// } | |||
// } else { | |||
// value = value.slice(0, allowed_letters-3) + " ..."; | |||
// } | |||
// } | |||
export function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) { | |||
let height, y; | |||
if (yTop <= zeroLine) { | |||
@@ -96,3 +36,24 @@ export function equilizeNoOfElements(array1, array2, | |||
} | |||
return [array1, array2]; | |||
} | |||
// let char_width = 8; | |||
// let allowed_space = avgUnitWidth * 1.5; | |||
// let allowed_letters = allowed_space / 8; | |||
// return values.map((value, i) => { | |||
// let space_taken = getStringWidth(value, char_width) + 2; | |||
// if(space_taken > allowed_space) { | |||
// if(is_series) { | |||
// // Skip some axis lines if X axis is a series | |||
// let skips = 1; | |||
// while((space_taken/skips)*2 > allowed_space) { | |||
// skips++; | |||
// } | |||
// if(i % skips !== 0) { | |||
// return; | |||
// } | |||
// } else { | |||
// value = value.slice(0, allowed_letters-3) + " ..."; | |||
// } | |||
// } |
@@ -1,10 +1,8 @@ | |||
import { getBarHeightAndYAttr, getXLineProps, getYLineProps } from './draw-utils'; | |||
import { getBarHeightAndYAttr } from './draw-utils'; | |||
const X_LABEL_CLASS = 'x-value-text'; | |||
const Y_LABEL_CLASS = 'y-value-text'; | |||
// const X_AXIS_LINE_CLASS = 'x-value-text'; | |||
// const Y_AXIS_LINE_CLASS = 'y-value-text'; | |||
const AXIS_TICK_LENGTH = 6; | |||
const LABEL_MARGIN = 4; | |||
const FONT_SIZE = 10; | |||
function $(expr, con) { | |||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | |||
@@ -134,122 +132,133 @@ export function makeText(className, x, y, content) { | |||
className: className, | |||
x: x, | |||
y: y, | |||
dy: '.32em', | |||
dy: (FONT_SIZE / 2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
innerHTML: content | |||
}); | |||
} | |||
export var AxisChartRenderer = (function() { | |||
var AxisChartRenderer = function(totalHeight, totalWidth, zeroLine, avgUnitWidth, xAxisMode, yAxisMode) { | |||
this.totalHeight = totalHeight; | |||
this.totalWidth = totalWidth; | |||
this.zeroLine = zeroLine; | |||
this.avgUnitWidth = avgUnitWidth; | |||
this.xAxisMode = xAxisMode; | |||
this.yAxisMode = yAxisMode; | |||
}; | |||
export function makeVertXLine(x, label, totalHeight, mode) { | |||
let height = mode === 'span' ? -1 * AXIS_TICK_LENGTH : totalHeight; | |||
AxisChartRenderer.prototype = { | |||
bar: function (x, yTop, args, color, index, datasetIndex, noOfDatasets) { | |||
let totalWidth = this.avgUnitWidth - args.spaceWidth; | |||
let startX = x - totalWidth/2; | |||
let width = totalWidth / noOfDatasets; | |||
let currentX = startX + width * datasetIndex; | |||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight); | |||
return createSVG('rect', { | |||
className: `bar mini`, | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
x: currentX, | |||
y: y, | |||
width: width, | |||
height: height | |||
}); | |||
}, | |||
dot: function(x, y, args, color, index) { | |||
return createSVG('circle', { | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
cx: x, | |||
cy: y, | |||
r: args.radius | |||
}); | |||
}, | |||
xLine: function(x, label, mode=this.xAxisMode) { | |||
// Draw X axis line in span/tick mode with optional label | |||
let [startAt, height, textStartAt, axisLineClass] = getXLineProps(this.totalHeight, mode); | |||
let l = createSVG('line', { | |||
x1: 0, | |||
x2: 0, | |||
y1: startAt, | |||
y2: height | |||
}); | |||
let text = createSVG('text', { | |||
className: X_LABEL_CLASS, | |||
x: 0, | |||
y: textStartAt, | |||
dy: '.71em', | |||
innerHTML: label | |||
}); | |||
let line = createSVG('g', { | |||
className: `tick ${axisLineClass}`, | |||
transform: `translate(${ x }, 0)` | |||
}); | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
return line; | |||
}, | |||
yLine: function(y, label, mode=this.yAxisMode) { | |||
// TODO: stroke type | |||
let lineType = ''; | |||
let [width, textEndAt, axisLineClass, startAt] = getYLineProps(this.totalWidth, mode); | |||
let l = createSVG('line', { | |||
className: lineType === "dashed" ? "dashed": "", | |||
x1: startAt, | |||
x2: width, | |||
y1: 0, | |||
y2: 0 | |||
}); | |||
let text = createSVG('text', { | |||
className: Y_LABEL_CLASS, | |||
x: textEndAt, | |||
y: 0, | |||
dy: '.32em', | |||
innerHTML: label+"" | |||
}); | |||
let line = createSVG('g', { | |||
className: `tick ${axisLineClass}`, | |||
transform: `translate(0, ${y})`, | |||
'stroke-opacity': 1 | |||
}); | |||
// if(darker) { | |||
// line.style.stroke = "rgba(27, 31, 35, 0.6)"; | |||
// } | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
return line; | |||
}, | |||
xRegion: function(x1, x2, label) { }, | |||
yRegion: function(y1, y2, label) { } | |||
}; | |||
let l = createSVG('line', { | |||
x1: 0, | |||
x2: 0, | |||
y1: totalHeight + AXIS_TICK_LENGTH, | |||
y2: height | |||
}); | |||
let text = createSVG('text', { | |||
x: 0, | |||
y: totalHeight + AXIS_TICK_LENGTH + LABEL_MARGIN, | |||
dy: FONT_SIZE + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': 'middle', | |||
innerHTML: label | |||
}); | |||
let line = createSVG('g', { | |||
transform: `translate(${ x }, 0)` | |||
}); | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
return AxisChartRenderer; | |||
})(); | |||
return line; | |||
} | |||
export function makeHoriYLine(y, label, totalWidth, mode) { | |||
let lineType = ''; | |||
let width = mode === 'span' ? totalWidth + AXIS_TICK_LENGTH : AXIS_TICK_LENGTH; | |||
let l = createSVG('line', { | |||
className: lineType === "dashed" ? "dashed": "", | |||
x1: -1 * AXIS_TICK_LENGTH, | |||
x2: width, | |||
y1: 0, | |||
y2: 0 | |||
}); | |||
let text = createSVG('text', { | |||
x: -1 * (LABEL_MARGIN + AXIS_TICK_LENGTH), | |||
y: 0, | |||
dy: (FONT_SIZE / 2 - 2) + 'px', | |||
'font-size': FONT_SIZE + 'px', | |||
'text-anchor': 'end', | |||
innerHTML: label+"" | |||
}); | |||
let line = createSVG('g', { | |||
transform: `translate(0, ${y})`, | |||
'stroke-opacity': 1 | |||
}); | |||
if(text === 0 || text === '0') { | |||
line.style.stroke = "rgba(27, 31, 35, 0.6)"; | |||
} | |||
line.appendChild(l); | |||
line.appendChild(text); | |||
return line; | |||
} | |||
export class AxisChartRenderer { | |||
constructor(state) { | |||
this.updateState(state); | |||
} | |||
updateState(state) { | |||
this.totalHeight = state.totalHeight; | |||
this.totalWidth = state.totalWidth; | |||
this.zeroLine = state.zeroLine; | |||
this.avgUnitWidth = state.avgUnitWidth; | |||
this.xAxisMode = state.xAxisMode; | |||
this.yAxisMode = state.yAxisMode; | |||
} | |||
bar(x, yTop, args, color, index, datasetIndex, noOfDatasets) { | |||
let totalWidth = this.avgUnitWidth - args.spaceWidth; | |||
let startX = x - totalWidth/2; | |||
let width = totalWidth / noOfDatasets; | |||
let currentX = startX + width * datasetIndex; | |||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight); | |||
return createSVG('rect', { | |||
className: `bar mini`, | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
x: currentX, | |||
y: y, | |||
width: width, | |||
height: height | |||
}); | |||
} | |||
dot(x, y, args, color, index) { | |||
return createSVG('circle', { | |||
style: `fill: ${color}`, | |||
'data-point-index': index, | |||
cx: x, | |||
cy: y, | |||
r: args.radius | |||
}); | |||
} | |||
xLine(x, label, mode=this.xAxisMode) { | |||
// Draw X axis line in span/tick mode with optional label | |||
return makeVertXLine(x, label, this.totalHeight, mode); | |||
} | |||
yLine(y, label, mode=this.yAxisMode) { | |||
return makeHoriYLine(y, label, this.totalWidth, mode); | |||
} | |||
xMarker() {} | |||
yMarker() {} | |||
xRegion() {} | |||
yRegion() {} | |||
} |
@@ -51,7 +51,6 @@ | |||
} | |||
} | |||
.axis, .chart-label { | |||
font-size: 11px; | |||
fill: #555b51; | |||
line { | |||
stroke: #dadada; | |||
@@ -78,17 +77,18 @@ | |||
line.dashed { | |||
stroke-dasharray: 5,3; | |||
} | |||
.tick { | |||
&.x-axis-label { | |||
display: block; | |||
} | |||
.axis-line { | |||
// &.x-axis-label { | |||
// display: block; | |||
// } | |||
// TODO: hack dy attr to be settable via styles | |||
.specific-value { | |||
text-anchor: start; | |||
} | |||
.y-value-text { | |||
.y-line { | |||
text-anchor: end; | |||
} | |||
.x-value-text { | |||
.x-line { | |||
text-anchor: middle; | |||
} | |||
} | |||