Using professional version, you will have unlimited number of projects and unlimited access to materials in Kodhus.com.
/*
* File: _1_chart-plugin.scss
* Location /src/scss/components
* Chart plugin
*/
:root {
--chart-height: 400px;
}
$colors: --primary-color,
--secondary-color,
--warning-color,
--success-color,
--error-color,
--color-highest,
--color-mid,
--color-higher;
.chart {
position: relative;
width: 100%;
margin: 20px auto;
height: var(--chart-height);
}
.chart svg {
display: block;
height: var(--chart-height);
}
.chart .tooltip {
position: absolute;
display: inline-flex;
flex-shrink: 0;
flex-direction: column;
box-shadow: 0 0 20px -3px rgba(0, 0, 0, 0.1), 0 3px 10px -1px rgba(0, 0, 0, 0.2);
padding: 20px;
background: var(--color-white);
border-radius: 5px;
pointer-events: none;
color: var(--color-higher);
margin: 5px;
opacity: 0;
visibility: hidden;
transition: opacity 0.1s, visibility 0.1s;
}
.chart .tooltip.show {
opacity: 1;
visibility: visible;
}
.chart .tooltip .title {
margin-bottom: 10px;
white-space: nowrap;
}
.chart .tooltip .values {
display: flex;
flex-direction: column-reverse;
}
.chart .tooltip .value {
display: flex;
align-items: center;
}
.chart .legend-container {
padding: 40px 0;
display: flex;
justify-content: center;
}
.chart .legend-container .legend {
display: inline-flex;
@media screen and (max-width: 768px) {
flex-wrap: wrap;
display: grid;
grid-template-columns: 1fr;
}
}
.data-points-chart {
transition: opacity 0.1s;
}
.data-points-chart.fade {
opacity: 0.2;
}
.anchors-container {
transition: opacity 0.1s;
}
.anchors-container.fade {
opacity: 0.2;
}
.chart .legend-container .legend .legend-item {
padding: 0 20px;
display: flex;
align-items: center;
cursor: pointer;
@media screen and (max-width: 768px) {
margin-top: 4px;
}
}
.chart .legend-container .legend .legend-item:hover .label {
color: var(--color-highest);
}
.chart .legend-container .legend .legend-item .marker,
.chart .tooltip .value .marker {
width: 12px;
height: 12px;
border-radius: 50px;
background: #ccc;
}
.chart .tooltip .value .marker {
width: 10px;
height: 10px;
}
@for $i from 1 through 8 {
.chart .legend-container .legend .legend-item:nth-child(#{$i}) .marker {
background-color: var(nth($colors, $i));
}
}
.chart .legend-container .legend .legend-item .label,
.chart .tooltip .value .label {
margin-left: 10px;
color: var(--color-higher);
}
.chart .grid {
stroke: var(--color-mid);
stroke-dasharray: 0;
stroke-width: 1;
}
.chart .grid line {
stroke: var(--color-lower);
stroke-dasharray: 0;
stroke-width: 1;
}
.chart .hover-line {
stroke: var(--color-lower);
stroke-dasharray: 6 3;
stroke-width: 1;
display: none;
}
.chart .hover-line.show {
display: block;
}
.chart line {
stroke: var(--color-higher);
stroke-dasharray: 0;
stroke-width: 1;
}
.chart .step-line {
stroke: var(--color-lowest) !important;
}
.chart .line-chart {
fill: none;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
}
@for $i from 0 through 7 {
.chart .column-chart--c#{$i} {
fill: var(nth($colors, ($i+1 % 7)));
}
.chart .anchor-points--c#{$i} {
fill: var(nth($colors, ($i+1 % 7)));
}
.chart .area-data-points--#{$i} {
fill: var(nth($colors, ($i+1 % 7)));
opacity: 0.24;
}
.chart .data-points--#{$i} {
stroke: var(nth($colors, ($i+1 % 7)));
}
}
.chart .column-chart.hover {
opacity: 0.8;
transition: opacity 0.2;
}
.chart .labels {
fill: var(--color-higher);
font-size: 14px;
}
.chart .label-title {
text-transform: uppercase;
font-size: 13px;
fill: var(--color-mid);
}
.chart .y-labels .label-title {
text-transform: uppercase;
font-size: 13px;
fill: var(--color-mid);
top: 50%;
}
.chart .labels.x-labels {
text-anchor: middle;
}
.chart circle {
stroke: #fff;
opacity: 0;
visibility: hidden;
stroke-width: 2;
transform-box: fill-box;
transform-origin: center;
transition: transform 0.2s;
}
.chart circle.show {
opacity: 1;
visibility: visible;
}
.chart circle.hover {
transform: scale(1.3);
opacity: 1;
visibility: visible;
}
.chart .labels.y-labels {
text-anchor: end;
}
/*
* File: 1-chart-plugin.js
* Location /src/js/components
* Chart plugin
*/
const svgPath = (points, command) => {
const d = points.reduce((acc, point, i, a) => i === 0 ?
`M ${point.x},${point.y}` :
`${acc} ${command(point, i, a)}`, '')
return d;
}
const svgClosedPathForAreaChart = (points, command, xAxisCoordinates) => {
const d = points.reduce((acc, point, i, a) => i === 0 ?
`M ${point.x},${point.y}` :
`${acc} ${command(point, i, a)}`, '')
return `${d} L${points[points.length-1].x},${xAxisCoordinates[1].y} L${xAxisCoordinates[0].x},${xAxisCoordinates[0].y} Z`;
}
const line = (pointA, pointB) => {
const lengthX = pointB.x - pointA.x;
const lengthY = pointB.y - pointA.y;
return {
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
angle: Math.atan2(lengthY, lengthX)
}
}
const controlPoint = (current, previous, next, reverse) => {
const p = previous || current
const n = next || current
const smoothing = 0.18
const o = line(p, n)
const angle = o.angle + (reverse ? Math.PI : 0)
const length = o.length * smoothing
const x = current.x + Math.cos(angle) * length
const y = current.y + Math.sin(angle) * length
return { x, y }
}
const lineCommand = point => `L ${point.x} ${point.y}`;
const bezierCommand = (point, i, a) => {
const { x: cpsX, y: cpsY } = controlPoint(a[i - 1], a[i - 2], point);
const { x: cpeX, y: cpeY } = controlPoint(point, a[i - 1], a[i + 1], true)
return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${point.x},${point.y}`
}
class Chart {
constructor(options) {
this.chartOptions = options;
this.element = null;
this.barWidth = (options && options.chartOptions && options.chartOptions.column && options.chartOptions.column.width) ? 100 / parseInt(options.chartOptions.column.width.match(/\d+/g)[0]) : 1 / 0.6;
this.multiBarDistance = (options && options.chartOptions && options.chartOptions.column && options.chartOptions.column.gap != undefined) ? options.chartOptions.column.gap : 2;
this.showLegend = (options && options.legend) ? options.legend : false;
this.colors = ['--primary-color', '--secondary-color', '--warning-color', '--success-color', '--error-color'];
this.smooth = (options.chartOptions && options.chartOptions.smooth) ? options.chartOptions.smooth : false;
this.withAnchorPoints = (options.chartOptions && options.chartOptions.line && options.chartOptions.line.showAnchorPoints) ? options.chartOptions.line.showAnchorPoints : false;
this.type = options.type;
this.combinedCharts = (options && options.type == undefined);
this.padding = 60;
this.lineIndicator = (options && options.lineIndicator) ? options.lineIndicator : true;
this.barRadius = (options && options.chartOptions && options.chartOptions.column && options.chartOptions.column.radius != undefined) ? options.chartOptions.column.radius : 4;
this.xLabelsContainer = null;
this.yLabelContainer = null;
this.data = options.dataPoints;
this.datapoints = options.datapoints;
this.tooltip = null;
this.steps = [];
this.step = null;
this.showTooltip = (options && options.tooltip && options.tooltip.enabled) ? options.tooltip.enabled : false;
this.valueLabelModifier = (options && options.yAxis && options.yAxis.valueLabelModifier) ? options.yAxis.valueLabelModifier : null;
this.labelModifier = (options && options.xAxis && options.xAxis.labelModifier) ? options.xAxis.labelModifier : null;
this.tooltipCustomHTML = (options && options.tooltip && options.tooltip.customeHTML) ? options.tooltip.customeHTML : null;
this.yLabel = (options && options.yAxis && options.yAxis.title) ? options.yAxis.title : '';
this.xLabel = (options && options.xAxis && options.xAxis.title) ? options.xAxis.title : '';
this.hasNegativeValue = false;
this.stack = (options.chartOptions && options.chartOptions.column && options.chartOptions.column.stack) ?
options.chartOptions.column.stack :
false;
this.svg = null;
this.xAxisLabels = (options && options.xAxis && options.xAxis.labels) ?
options.xAxis.labels :
Array.from(this.datapoints.reduce((prev, current) => current.data.length > prev.data.length ? current.data : prev.data).keys());
this.internalData = this.datapoints.map(dataPoint => []);
this.xPointsPosition = [];
this.initializeChart(options);
this.draw(options);
this.enableResizeHandler();
}
enableResizeHandler() {
window.addEventListener('resize', (e) => {
this.svg.setAttribute('width', this.element.parentElement.offsetWidth);
this.generateXAxis();
this.generateXAxisLabels();
this.drawChart();
});
}
initializeChart(options) {
var container = document.querySelector(options.element);
const chartContainer = document.createElement('div');
chartContainer.classList.add('chart');
container.appendChild(chartContainer);
this.svg = this._createSVG(container);
chartContainer.appendChild(this.svg);
this.element = chartContainer;
this.xLabelsContainer = document.createElementNS('http://www.w3.org/2000/svg', 'g');
this.xLabelsContainer.classList.add('labels', 'x-labels');
this.svg.appendChild(this.xLabelsContainer);
this.yLabelsContainer = document.createElementNS('http://www.w3.org/2000/svg', 'g');
this.yLabelsContainer.classList.add('labels', 'y-labels');
this.svg.appendChild(this.yLabelsContainer);
if (this.showLegend) {
this.generateLegend();
}
this.generateTooltip();
}
generateXAxis() {
var xAxisLine;
const x1 = this.padding;
const y1 = this.element.offsetHeight - this.padding;
const x2 = this.padding + this.element.offsetWidth - (2 * this.padding);
const y2 = this.element.offsetHeight - this.padding;
if (this.svg.querySelector('.x-grid')) {
xAxisLine = this.element.querySelector('.x-grid line');
this.setLineAttribute(xAxisLine, x1, x2, y1, y2);
} else {
var xAxisLineC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
xAxisLineC.classList.add('grid', 'x-grid');
xAxisLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
this.setLineAttribute(xAxisLine, x1, x2, y1, y2);
xAxisLineC.appendChild(xAxisLine);
this.svg.appendChild(xAxisLineC);
}
}
setLineAttribute(line, x1, x2, y1, y2) {
line.setAttribute('x1', x1);
line.setAttribute('x2', x2);
line.setAttribute('y1', y1);
line.setAttribute('y2', y2);
}
generateYAxis() {
var axisLineC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
axisLineC.classList.add('grid', 'y-grid');
var axisLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
axisLine.setAttribute('x1', 0 + this.padding);
axisLine.setAttribute('x2', 0 + this.padding);
axisLine.setAttribute('y1', this.padding);
axisLine.setAttribute('y2', this.element.offsetHeight - this.padding);
axisLineC.appendChild(axisLine);
this.svg.appendChild(axisLineC);
}
checkInChartArea(e) {
if (e.clientX > this.padding + this.element.getBoundingClientRect().left && e.clientX < this.element.offsetWidth - this.padding + this.element.getBoundingClientRect().left) return true;
return false;
}
generateLegend() {
const legendContainer = document.createElement('div');
legendContainer.classList.add('legend-container');
const legend = document.createElement('div');
legend.classList.add('legend');
this.datapoints.forEach((dataPoint, i) => {
const legendItem = document.createElement('span');
legendItem.classList.add('legend-item');
legendItem.addEventListener('mouseover', (e) => {
this.svg.querySelectorAll('.data-points-chart').forEach(dpChart => {
dpChart.classList.add('fade');
});
this.svg.querySelectorAll('.anchors-container').forEach(anchorC => {
anchorC.classList.add('fade');
});
if (dataPoint.type === 'column') {
this.svg.querySelector('.column-chart--c' + i).classList.remove('fade');
}
if (dataPoint.type === 'line') {
this.svg.querySelector('.line-chart--c' + i).classList.remove('fade');
this.svg.querySelector('.anchor-points--c' + i) && this.svg.querySelector('.anchor-points--c' + i).classList.remove('fade');
}
if (dataPoint.type === 'area') {
this.svg.querySelector('.area-chart--c' + i).classList.remove('fade');
this.svg.querySelector('.anchor-points--c' + i) && this.svg.querySelector('.anchor-points--c' + i).classList.remove('fade');
}
});
legendItem.addEventListener('mouseout', (e) => {
this.svg.querySelectorAll('.data-points-chart').forEach(dpChart => {
dpChart.classList.remove('fade');
});
this.svg.querySelectorAll('.anchors-container').forEach(anchorC => {
anchorC.classList.remove('fade');
});
});
const marker = document.createElement('span');
marker.classList.add('marker');
const label = document.createElement('span');
label.classList.add('label');
label.innerHTML = dataPoint.name;
legendItem.appendChild(marker);
legendItem.appendChild(label);
legend.appendChild(legendItem);
});
legendContainer.appendChild(legend);
this.element.appendChild(legendContainer);
}
generateXAxisLabels() {
this.xPointsPosition.length = 0;
var labels = this.xAxisLabels;
const numLabels = labels.length;
var ticksLength = labels.length;
let count = numLabels;
if (this.type === 'column' || this.combinedChartIncludingColumnChart()) {
ticksLength += 1;
count = numLabels + 1;
}
if (Array.from(this.svg.querySelector('.x-labels').children).length > 0) {
labels.forEach((_, i) => {
var text = this.xLabelsContainer.querySelectorAll('text')[i];
var eachAxisSection = (this.element.offsetWidth - 2 * this.padding) / (count - 1);
var x = (i) * eachAxisSection + this.padding;
if (this.combinedChartIncludingColumnChart() || this.type === 'column') {
x = (i) * (eachAxisSection) + this.padding + eachAxisSection / 2;
}
text.setAttribute('x', x);
if (window.innerWidth < 500) {
text.setAttribute('transform', `rotate(-45 ${x} ${this.element.offsetHeight - this.padding + 30})`)
} else {
text.setAttribute('transform', `rotate(0 ${x} ${this.element.offsetHeight - this.padding + 30})`)
}
// tickLines
var tickLine = this.xLabelsContainer.querySelectorAll('line')[i];
tickLine.setAttribute('x1', (i) * ((this.element.offsetWidth - 2 * this.padding) / (count - 1)) + this.padding);
tickLine.setAttribute('x2', (i) * ((this.element.offsetWidth - 2 * this.padding) / (count - 1)) + this.padding);
tickLine.setAttribute('y1', this.padding + (this.element.offsetHeight - 2 * this.padding));
tickLine.setAttribute('y2', this.padding + (this.element.offsetHeight - 2 * this.padding) + 5);
this.xPointsPosition.push((i) * ((this.element.offsetWidth - 2 * this.padding) / (count - 1)) + this.padding);
if (i < this.datapoints[0].data.length) {
this.datapoints.forEach((dataPoint, j) => {
this.internalData[j][i].x = (i) * ((this.element.offsetWidth - 2 * this.padding) / (count - 1)) + this.padding;
});
}
});
document.querySelector('.label-title').setAttribute('x', this.element.offsetWidth / 2);
} else {
var eachAxisSection = (this.element.offsetWidth - 2 * this.padding) / (count - 1);
for (var i = 0; i < ticksLength; i++) {
const label = labels[i];
var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
var x = (i) * eachAxisSection + this.padding;
if (this.combinedChartIncludingColumnChart() || this.type === 'column') {
x = (i) * (eachAxisSection) + this.padding + eachAxisSection / 2;
}
text.setAttribute('x', x);
text.setAttribute('y', this.element.offsetHeight - this.padding + 30);
text.textContent = label;
if (window.innerWidth < 500) {
text.setAttribute('transform', `rotate(-45 ${x} ${this.element.offsetHeight - this.padding + 30})`)
} else {
text.setAttribute('transform', `rotate(0 ${x} ${this.element.offsetHeight - this.padding + 30})`)
}
this.xLabelsContainer.appendChild(text);
// tickLines
var tickLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
tickLine.setAttribute('x1', (i) * (eachAxisSection) + this.padding);
tickLine.setAttribute('x2', (i) * (eachAxisSection) + this.padding);
tickLine.setAttribute('y1', this.padding + (this.element.offsetHeight - 2 * this.padding));
tickLine.setAttribute('y2', this.padding + (this.element.offsetHeight - 2 * this.padding) + 5);
this.xLabelsContainer.appendChild(tickLine);
this.xPointsPosition.push((i) * (eachAxisSection) + this.padding);
}
this.datapoints.forEach((dp, j) => {
dp.data.forEach((point, i) => {
this.internalData[j].push({
x: (i) * (eachAxisSection) + this.padding,
y: 0
});
});
})
this.generateXAxisTitle();
}
}
generateXAxisTitle() {
var labelTitle = document.createElementNS('http://www.w3.org/2000/svg', 'text');
labelTitle.textContent = this.xLabel;
labelTitle.classList.add('label-title');
labelTitle.setAttribute('y', this.element.offsetHeight);
this.xLabelsContainer.appendChild(labelTitle);
labelTitle.setAttribute('x', this.element.offsetWidth / 2);
}
generateTooltip() {
this.tooltip = document.createElement('div');
this.tooltip.classList.add('tooltip');
this.element.appendChild(this.tooltip);
const title = document.createElement('div');
title.classList.add('title');
this.tooltip.appendChild(title);
const values = document.createElement('div');
values.classList.add('values');
this.tooltip.appendChild(values);
}
combinedChartIncludingColumnChart() { return this.combinedCharts && this.datapoints.filter(dp => dp.type === 'column').length > 0; };
draw(options) {
var hoverLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
var eachAxisSection = (this.element.offsetWidth - 2 * this.padding) / (this.xAxisLabels.length);
hoverLine.classList.add('hover-line');
hoverLine.setAttribute('x1', 0 + this.padding);
hoverLine.setAttribute('x2', 0 + this.padding);
hoverLine.setAttribute('y1', this.padding);
hoverLine.setAttribute('y2', this.element.offsetHeight - this.padding);
this.svg.appendChild(hoverLine);
// xaxis
this.generateXAxis();
const xAxisLabels = this.xAxisLabels;
this.generateXAxisLabels();
this.svg.addEventListener('mousemove', (e) => {
if (this.type !== 'nice') {
if (this.checkInChartArea(e)) {
if (this.lineIndicator && this.type !== 'column') {
hoverLine.classList.add('show');
}
const xPosition = e.clientX - this.element.getBoundingClientRect().left;
this.xPointsPosition.forEach((xP, i) => {
var xAxisStepWidth = ((this.element.offsetWidth - 2 * this.padding) / (xAxisLabels.length - 1)) / 2;
var condition = Math.abs(xPosition - xP) < xAxisStepWidth;
if (this.type === 'column' || this.combinedChartIncludingColumnChart()) {
condition = xPosition >= xP && xPosition < this.xPointsPosition[i + 1];
}
if (condition) {
var x = xP;
if (this.type === 'column') {
x = xP + xAxisStepWidth - (this.multiBarDistance * this.datapoints.length);
}
if (this.combinedChartIncludingColumnChart()) {
x += eachAxisSection / 2;
}
hoverLine.setAttribute('x1', x);
hoverLine.setAttribute('x2', x);
// tooltip
document.querySelectorAll('.column-charts-container').forEach(c => {
c.querySelectorAll('.column-chart').forEach((column, k) => {
if (k === i) {
column.classList.add('hover');
} else {
column.classList.remove('hover');
}
});
});
document.querySelectorAll('.anchors-container').forEach(c => {
c.querySelectorAll('circle').forEach((circle, k) => {
if (k === i) {
circle.classList.add('hover');
} else {
circle.classList.remove('hover');
}
});
});
if (this.showTooltip) {
this.tooltip.classList.add('show');
this.tooltip.style.top = this.padding + 'px';
if (this.type !== 'column') {
if (xP < this.element.offsetWidth / 2) {
this.tooltip.style.left = x + 'px';
} else {
this.tooltip.style.left = x - this.tooltip.offsetWidth - 10 + 'px';
}
} else {
this.tooltip.style.left = x + 'px';
this.tooltip.style.transform = 'translate(-50%)';
}
var title, values;
if (!this.tooltipCustomHTML) {
title = this.tooltip.querySelector('.title');
title.innerHTML = (this.labelModifier) ? this.labelModifier(this.xAxisLabels[i]) : this.xAxisLabels[i];
values = this.tooltip.querySelector('.values');
values.innerHTML = '';
}
this.internalData.forEach((dataset, j) => {
if (this.tooltipCustomHTML) {
this.tooltip.innerHTML = this.tooltipCustomHTML(this.chartOptions, j, i);
} else {
if (this.internalData[j][i]) {
const value = document.createElement('div');
value.classList.add('value');
const marker = document.createElement('span');
marker.classList.add('marker');
marker.style.background = `var(${this.colors[j]})`;
const label = document.createElement('span');
label.classList.add('label');
label.innerHTML = (this.valueLabelModifier) ?
this.valueLabelModifier(this.datapoints[j].data[i]) :
this.datapoints[j].data[i];
value.appendChild(marker);
value.appendChild(label);
values.appendChild(value);
}
}
});
}
}
});
} else {
hoverLine.classList.remove('show');
this.tooltip.classList.remove('show');
this.svg.querySelectorAll('circle').forEach(circle => circle.classList.remove('hover'));
}
}
});
this.svg.addEventListener('mouseout', (e) => {
hoverLine.classList.remove('show');
this.tooltip.classList.remove('show');
this.svg.querySelectorAll('circle').forEach(circle => circle.classList.remove('hover'));
})
var allData;
if ((this.type === 'column' || this.combinedChartIncludingColumnChart()) && this.stack) {
if (this.combinedChartIncludingColumnChart()) {
var allColumns = this.datapoints.filter(dp => dp.type === 'column')
.map(dataPoint => dataPoint.data)
.reduce((prev, current) => {
current.forEach((p, i) =>
prev[i] ? prev[i] = prev[i] + current[i] : prev[i] = current[i]
);
return prev;
}, []);
allData = [...allColumns, ...this.datapoints.filter(dp => dp.type !== 'column').map(dataPoint => dataPoint.data)[0]];
} else {
allData = this.datapoints.map(dataPoint => dataPoint.data).reduce((prev, current) => {
current.forEach((p, i) => prev[i] ? prev[i] = prev[i] + current[i] : prev[i] = current[i]);
return prev;
}, []);
}
} else {
allData = options.datapoints.reduce((prev, current) => [...prev, ...current.data], []);
}
const max = Math.max(...allData);
var min = Math.min(...allData);
const diff = max - min;
var power = Math.round(Math.log10(diff));
var step = Math.pow(10, power) / 2;
this.step = step;
var stepStart;
var liftUpValue = 0;
if (min < 0) {
if (Math.abs(min % step)) {
var absValue = Math.abs(min);
var low = absValue - absValue % step;
stepStart = -1 * (low + step);
} else {
stepStart = min;
}
this.hasNegativeValue = true;
liftUpValue = step;
} else {
stepStart = 0;
}
const steps = [];
var i;
for (i = stepStart; i <= (diff - stepStart); i += step) {
steps.push(i);
}
if (steps[steps.length - 1] < max) {
steps.push(i);
}
this.steps = steps;
this.datapoints.forEach((dataPoint, j) => {
dataPoint.data.forEach((dt, i) => {
this.internalData[j][i].y = (this.element.offsetHeight - 2 * this.padding) + this.padding - (dt + liftUpValue) * (this.element.offsetHeight - 2 * this.padding) / (steps[steps.length - 1] - steps[0]);
})
});
steps.forEach((step, i) => {
var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', this.padding - 10);
text.setAttribute('y', (((this.element.offsetHeight - 2 * this.padding) / (steps.length - 1)) * i) + this.padding);
text.textContent = steps[steps.length - 1 - i];
this.yLabelsContainer.appendChild(text);
// step lines
if (i < steps.length - 1) {
var stepLineC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
stepLineC.classList.add('grid', 'y-grid');
var stepLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
stepLine.classList.add('step-line');
const x1 = this.padding;
const x2 = this.padding + (this.element.offsetWidth - 2 * this.padding);
const y1 = (((this.element.offsetHeight - 2 * this.padding) / (steps.length - 1)) * i) + this.padding;
const y2 = y1;
this.setLineAttribute(stepLine, x1, x2, y1, y2);
stepLineC.appendChild(stepLine);
this.svg.appendChild(stepLineC);
}
});
this.generateYAxisTitle();
this.drawChart();
}
generateYAxisTitle() {
var labelTitle = document.createElementNS('http://www.w3.org/2000/svg', 'text');
labelTitle.textContent = this.yLabel;
labelTitle.classList.add('label-title');
labelTitle.setAttribute('y', (this.element.offsetHeight - this.padding) / 2);
this.yLabelsContainer.appendChild(labelTitle);
labelTitle.setAttribute('x', 10);
labelTitle.setAttribute('transform', `rotate(-90, 10, ${(this.element.offsetHeight - this.padding) / 2})`);
}
drawColumnChart(dataPoints, index) {
var command = lineCommand;
const curve = this.barRadius / this.datapoints.length + 1;
const numDatasets = dataPoints.length;
const columnRatioToAxisSection = this.barWidth;
dataPoints.forEach((points, i) => {
var column;
if (this.svg.querySelector('.column-chart--c' + (index && index[i] || i))) {
points.forEach((point, j) => {
column = this.svg.querySelector('.column-chart--c' + (index && index[i] || i)).querySelectorAll('path')[j];
var xAxisPosition = this.element.offsetHeight - this.padding;
if (this.hasNegativeValue) {
xAxisPosition = xAxisPosition - (this.element.offsetHeight - 2 * this.padding) / (this.steps.length - 1);
}
var eachAxisSection = (this.element.offsetWidth - 2 * this.padding) / (this.xAxisLabels.length);
if (!this.stack) {
const columnWidth = eachAxisSection / (numDatasets * columnRatioToAxisSection);
var startingPoint = (eachAxisSection - numDatasets * columnWidth - (this.multiBarDistance * (numDatasets - 1)) - (numDatasets) * curve) / 2;
var patchPoints = [
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i), y: xAxisPosition },
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i), y: point.y },
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i) + (columnWidth), y: point.y },
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i) + (columnWidth), y: xAxisPosition },
{ x: point.x + startingPoint, y: xAxisPosition }
];
const yValue = this.datapoints[(index && index[i] || i)].data[j];
const sweepFlag = (yValue >= 0) ? 1 : 0;
const curveSign = (yValue >= 0) ? -1 * curve : curve;
var path = `M ${patchPoints[0].x},${patchPoints[0].y} V ${point.y - curve -0.5} a ${curve},${curve},0,0,${sweepFlag},${curve},${curveSign} H ${patchPoints[2].x} a ${curve},${curve},0,0,${sweepFlag},${curve},${-1 * curveSign} V ${xAxisPosition}`;
column.setAttribute('d', path);
} else {
if (this.datapoints[i].data[j] < 0) {
return;
}
const columnWidth = eachAxisSection / (columnRatioToAxisSection);
var startingPoint = (eachAxisSection - columnWidth - curve) / 2;
var patchPoints = [
{ x: point.x + startingPoint, y: xAxisPosition },
{ x: point.x + startingPoint, y: point.y },
{ x: point.x + startingPoint + (columnWidth), y: point.y },
{ x: point.x + startingPoint + (columnWidth), y: xAxisPosition },
{ x: point.x + startingPoint, y: xAxisPosition }
];
var tempCurve;
var path;
if (i !== dataPoints.length - 1) {
tempCurve = 0;
if (i === 0) {
path = `M ${patchPoints[0].x},${patchPoints[0].y} V ${point.y} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},-${tempCurve} H ${patchPoints[2].x + curve} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},${tempCurve} V ${xAxisPosition}`;
} else {
var baseY = 0;
for (var k = 1; k < i; k++) {
baseY += xAxisPosition - dataPoints[k - 1][j].y;
}
path = `M ${patchPoints[0].x},${patchPoints[0].y - baseY} V ${point.y - baseY} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},-${tempCurve} H ${patchPoints[2].x + curve} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},${tempCurve} V ${xAxisPosition - baseY}`;
}
} else {
tempCurve = curve;
var baseY = 0;
for (var k = 1; k < i + 1; k++) {
if (this.datapoints[k - 1].data[j] < 0) {
} else {
baseY += xAxisPosition - dataPoints[k - 1][j].y;
}
}
path = `M ${patchPoints[0].x},${patchPoints[0].y - baseY} V ${point.y - baseY} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},-${tempCurve} H ${patchPoints[2].x} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},${tempCurve} V ${xAxisPosition - baseY}`;
}
column.setAttribute('d', path);
}
});
} else {
const columnChartC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
columnChartC.classList.add('column-chart--c' + (index && index[i] || i), 'column-charts-container', 'data-points-chart');
points.forEach((point, j) => {
column = document.createElementNS('http://www.w3.org/2000/svg', 'path');
column.classList.add('column-chart');
var xAxisPosition = this.element.offsetHeight - this.padding;
if (this.hasNegativeValue) {
xAxisPosition = xAxisPosition - (this.element.offsetHeight - 2 * this.padding) / (this.steps.length - 1);
}
var eachAxisSection = (this.element.offsetWidth - 2 * this.padding) / (this.xAxisLabels.length);
if (!this.stack) {
const columnWidth = eachAxisSection / (numDatasets * columnRatioToAxisSection);
var startingPoint = (eachAxisSection - numDatasets * columnWidth - (this.multiBarDistance * (numDatasets - 1)) - (numDatasets) * curve) / 2;
var patchPoints = [
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i), y: xAxisPosition },
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i), y: point.y },
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i) + (columnWidth), y: point.y },
{ x: point.x + startingPoint + (i * (columnWidth + curve)) + (this.multiBarDistance * i) + (columnWidth), y: xAxisPosition },
{ x: point.x + startingPoint, y: xAxisPosition }
];
const yValue = this.datapoints[(index && index[i] || i)].data[j];
const sweepFlag = (yValue >= 0) ? 1 : 0;
const curveSign = (yValue >= 0) ? -1 * curve : curve;
var path = `M ${patchPoints[0].x},${patchPoints[0].y} V ${point.y - curve -0.5} a ${curve},${curve},0,0,${sweepFlag},${curve},${curveSign} H ${patchPoints[2].x} a ${curve},${curve},0,0,${sweepFlag},${curve},${-1 * curveSign} V ${xAxisPosition}`;
} else {
const columnWidth = eachAxisSection / (columnRatioToAxisSection);
var startingPoint = (eachAxisSection - columnWidth - curve) / 2;
var patchPoints = [
{ x: point.x + startingPoint, y: xAxisPosition },
{ x: point.x + startingPoint, y: point.y },
{ x: point.x + startingPoint + (columnWidth), y: point.y },
{ x: point.x + startingPoint + (columnWidth), y: xAxisPosition },
{ x: point.x + startingPoint, y: xAxisPosition }
];
var tempCurve;
var path;
if (i !== dataPoints.length - 1) {
tempCurve = 0;
if (i === 0) {
path = `M ${patchPoints[0].x},${patchPoints[0].y} V ${point.y} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},-${tempCurve} H ${patchPoints[2].x + curve} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},${tempCurve} V ${xAxisPosition}`;
} else {
var baseY = 0;
for (var k = 1; k < i + 1; k++) {
if (this.datapoints[k - 1].data[j] < 0) {
} else {
baseY += xAxisPosition - dataPoints[k - 1][j].y;
}
}
path = `M ${patchPoints[0].x},${patchPoints[0].y - baseY} V ${point.y - baseY} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},-${tempCurve} H ${patchPoints[2].x + curve} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},${tempCurve} V ${xAxisPosition - baseY}`;
}
} else {
tempCurve = curve;
var baseY = 0;
for (var k = 1; k < i + 1; k++) {
if (this.datapoints[k - 1].data[j] < 0) {
} else {
baseY += xAxisPosition - dataPoints[k - 1][j].y;
}
}
path = `M ${patchPoints[0].x},${patchPoints[0].y - baseY} V ${point.y - baseY} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},-${tempCurve} H ${patchPoints[2].x} a ${tempCurve},${tempCurve},0,0,1,${tempCurve},${tempCurve} V ${xAxisPosition - baseY}`;
}
if (this.datapoints[i].data[j] < 0) {
column.style.opacity = 0;
}
}
column.setAttribute('d', path);
columnChartC.appendChild(column);
});
this.svg.appendChild(columnChartC);
}
})
}
drawLineChart() {
var command = (this.smooth) ? bezierCommand : lineCommand;
const dataPoints = this.internalData;
dataPoints.forEach((points, i) => {
var line;
if (this.svg.querySelector('.line-chart--c' + i)) {
line = this.svg.querySelector('.line-chart--c' + i).querySelector('path');
var path = svgPath(points, command);
line.setAttribute('d', path);
} else {
const lineChartC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
lineChartC.classList.add('line-chart--c' + i, 'data-points--' + i, 'data-points-chart');
line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
line.classList.add('line-chart');
var path = svgPath(points, command);
line.setAttribute('d', path);
lineChartC.appendChild(line);
this.svg.appendChild(lineChartC);
}
})
}
drawSingleLineChart(points, i) {
var eachAxisSection = (this.element.offsetWidth - 2 * this.padding) / (this.xAxisLabels.length);
var command = (this.smooth) ? bezierCommand : lineCommand;
var line;
if (this.svg.querySelector('.line-chart--c' + i)) {
line = this.svg.querySelector('.line-chart--c' + i).querySelector('path');
var path = svgPath(points, command);
line.setAttribute('d', path);
} else {
const lineChartC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
lineChartC.classList.add('line-chart--c' + i, 'data-points--' + i, 'data-points-chart');
line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
line.classList.add('line-chart');
if (this.combinedChartIncludingColumnChart()) {
points.forEach(p => p.x = p.x + eachAxisSection / 2);
}
var path = svgPath(points, command);
line.setAttribute('d', path);
lineChartC.appendChild(line);
this.svg.appendChild(lineChartC);
}
}
drawLineChart(dataPoints) {
dataPoints.forEach((points, i) => {
this.drawSingleLineChart(points, i);
})
}
drawSingleAreaChart(points, i) {
var command = (this.smooth) ? bezierCommand : lineCommand;
var area;
this.drawSingleLineChart(points, i);
var xAxisY = this.element.offsetHeight - this.padding;
if (this.svg.querySelector('.area-chart--c' + i)) {
area = this.svg.querySelector('.area-chart--c' + i).querySelector('path');
var areaPath = svgClosedPathForAreaChart(points, command, [{ x: this.padding, y: xAxisY }, { x: this.padding, y: xAxisY }]);
area.setAttribute('d', areaPath);
} else {
const areaChartC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
areaChartC.classList.add('area-chart--c' + i, 'area-data-points--' + i);
area = document.createElementNS('http://www.w3.org/2000/svg', 'path');
area.classList.add('area-chart');
var areaPath = svgClosedPathForAreaChart(points, command, [{ x: this.padding, y: xAxisY }, { x: this.padding, y: xAxisY }]);
area.setAttribute('d', areaPath);
areaChartC.appendChild(area);
this.svg.appendChild(areaChartC);
}
}
drawAreaChart(dataPoints) {
dataPoints.forEach((points, i) => {
this.drawSingleAreaChart(points, i);
});
}
drawSingleAnchorPoints(points, i) {
if (this.svg.querySelector('.anchor-points--c' + i)) {
for (var j = 0; j < points.length; j++) {
var dt = points[j];
var circle = this.svg.querySelector('.anchor-points--c' + i).querySelectorAll('circle')[j];
circle.setAttribute('cx', dt.x);
}
} else {
var anchorPointcC = document.createElementNS('http://www.w3.org/2000/svg', 'g');
anchorPointcC.classList.add('anchor-points--c' + i, 'anchors-container');
for (var i = 0; i < points.length; i++) {
var dt = points[i];
var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('r', 6);
circle.setAttribute('cx', dt.x);
circle.setAttribute('cy', dt.y);
anchorPointcC.appendChild(circle);
}
this.svg.appendChild(anchorPointcC);
}
}
drawAnchorPoints(dataPoints) {
dataPoints.forEach((points, i) => {
this.drawSingleAnchorPoints(points, i);
});
}
drawChart() {
if (this.combinedCharts) {
var newDatapoints = [];
this.datapoints.forEach((datapoint, i) => {
if (datapoint.type === 'line' || datapoint.type === 'area') {
newDatapoints.push({ type: datapoint.type, index: i, datapoints: this.internalData[i] });
}
if (datapoint.type === 'column') {
const columnDatapoints = newDatapoints.filter(nDp => nDp.type === 'column')[0];
if (columnDatapoints != null) {
columnDatapoints.datapoints.push({ index: i, data: this.internalData[i] });
} else {
newDatapoints.push({ type: 'column', datapoints: [{ index: i, data: this.internalData[i] }] });
}
}
});
newDatapoints.forEach(dp => {
if (dp.type === 'line') {
this.drawSingleLineChart(dp.datapoints, dp.index);
}
if (dp.type === 'area') {
this.drawSingleAreaChart(dp.datapoints, dp.index);
}
if (dp.type === 'column') {
this.drawColumnChart(dp.datapoints.map(dp => dp.data), dp.datapoints.map(dp => dp.index));
}
if (dp.type !== 'column') {
this.drawSingleAnchorPoints(dp.datapoints, dp.index);
if (this.withAnchorPoints) {
this.svg.querySelectorAll('circle').forEach(circle => circle.classList.add('show'));
}
}
})
} else {
this.drawChartDataPoints();
}
}
drawChartDataPoints() {
if (this.type === 'line') {
this.drawLineChart(this.internalData);
}
if (this.type == 'area') {
this.drawAreaChart(this.internalData);
}
if (this.type == 'column') {
this.drawColumnChart(this.internalData);
}
if (this.type !== 'column') {
this.drawAnchorPoints(this.internalData);
if (this.withAnchorPoints) {
this.svg.querySelectorAll('circle').forEach(circle => circle.classList.add('show'));
}
}
}
_createSVG(container) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svg.setAttribute('width', container.offsetWidth);
svg.setAttribute('height', container.offsetHeight);
return svg;
}
}
Using professional version, you will have unlimited number of projects and unlimited access to materials in Kodhus.com.