arrow_back
View code
Copy HTML
/*
 * 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;
}
Copy SCSS
/*
 * 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;
    }
}
Copy JS
Upgrade to professional version

Using professional version, you will have unlimited number of projects and unlimited access to materials in Kodhus.com.