/* eslint-disable @typescript-eslint/no-explicit-any */
import { _t } from "../../../text";
import { h } from "@stencil/core";
import * as d3 from "d3";
import { randomIdGenerator } from "../../../global/utils/randomIdGenerator";
/**
 *
 * @enums myEnum - chart-line-myEnum.enum.ts
 * @noTokens - Komponentens färger sätts direkt som i graferna. Behöver skrivas om för att kunna hantera olika brands och teman.
 * @swedishName Linjediagram
 */
export class ChartLine {
    constructor() {
        this._lineTypes = [null, '18, 7', '8,7', '2,6'];
        this._strokeWidth = 2.75;
        this._lineCap = 'round';
        this._strokeGlyphs = [d3.symbolCircle, d3.symbolTriangle, d3.symbolSquare, d3.symbolWye, d3.symbolDiamond2];
        this._toggled = false;
        this._tickSize = 0;
        // Selection list from legend chips
        this._selectedLines = [];
        // Dragging
        this._dragStart = 0;
        this._dragDir = 0;
        this._loading = false;
        this.margin = {
            bottom: 0,
            top: 6,
            left: 5,
            right: 15
        };
        this.fontSize = '0.875rem';
        this.xPadding = 5;
        this.yPadding = 8;
        this.xTickSize = 5;
        // Formats
        this.formatStyle = ',d';
        this.axisNumberFormat = {
            decimal: ',',
            thousands: ' ',
            grouping: [3],
            currency: ['', 'kr']
        };
        // Observer for watching size changes when in line chart mode
        this.lineChartObserver = new ResizeObserver((entries) => {
            entries.forEach(() => {
                this.resizeSvg();
                this.resizeLines();
                this.repositionTooltip(); // Called on both div.resize, and window.resize
                d3.select(this._legendDiv).style('width', this.dims.width + 'px');
            });
        });
        // Observer for watching size changes when in table mode
        this.tableObserver = new ResizeObserver((entries) => {
            entries.forEach(() => {
                d3.select(this._legendDiv).style('width', this.dims.width + 'px');
                this.resizeTable();
            });
        });
        this.dragStart = (e) => {
            const container = d3.select(this._legendDiv).node();
            this._dragStart = e.x - container.offsetLeft;
            this._scrollLeft = container.scrollLeft;
        };
        this.drag = (e) => {
            const container = d3.select(this._legendDiv);
            this._dragDir = e.x;
            const delta = this._dragDir - container.node().offsetLeft;
            const walk = (delta - this._dragStart) * 1; //scroll-fast
            container.property('scrollLeft', this._scrollLeft - walk);
        };
        // Set yTicks
        this.setYTicks = (init = false) => {
            // Fixed number of y-axis lines
            const step = this._chartData.meta && this._chartData.meta.numberOfReferenceLines
                ? this._chartData.meta.numberOfReferenceLines
                : 5, min = this._yAxis.domain()[0], max = this._yAxis.domain()[1], stepValue = (max - min) / (step - 1), tickValues = d3.range(min, max + stepValue, stepValue);
            if (!init) {
                this._yAxisHandle
                    .style('font-size', this.fontSize)
                    .transition()
                    .duration(400)
                    .call(d3
                    .axisLeft(this._yAxis)
                    .ticks(step)
                    .tickPadding(this.yPadding)
                    .tickValues(tickValues)
                    .tickSize(-(this.dims.width - this.margin.left))
                    .tickFormat((d) => d3.formatLocale(this.axisNumberFormat).format(this.formatStyle)(d)))
                    .on('end', () => this.leftSetAxis());
            }
            else {
                this._yAxisHandle.style('font-size', this.fontSize).call(d3
                    .axisLeft(this._yAxis)
                    .ticks(step)
                    .tickPadding(this.yPadding)
                    .tickValues(tickValues)
                    .tickSize(-(this.dims.width - this.margin.left))
                    .tickFormat((d) => d3.formatLocale(this.axisNumberFormat).format(this.formatStyle)(d)));
            }
            this._yAxisHandle
                .selectAll('line')
                .attr('stroke', '#BDBDBD');
            this._yAxisHandle.select('.domain').attr('display', 'none');
            if (init)
                this.leftSetAxis(init);
        };
        // Adding event listeners to the chart
        this.addListenersToChart = () => {
            this._Tipbox
                .style('cursor', 'pointer')
                .on('mousemove', this.drawTooltipLine)
                .on('touchmove', 'ontouchstart' in window || window.navigator.maxTouchPoints
                ? this.drawTooltip
                : null)
                .on('mouseup', this.drawTooltip)
                .on('mouseleave', () => {
                this._secondaryLine.attr('display', 'none');
                if (!this._toggled) {
                    this._TooltipLine.attr('display', 'none');
                    this._Circles.selectAll('circle').attr('display', 'none');
                }
            });
        };
        // Initialize legend
        this.initLegend = () => {
            const container = d3.select(this._legendDiv);
            d3.select(this._legendDiv).style('width', this.dims.width + 'px');
            container.selectAll('.chip').remove();
            container.call(d3.drag().on('start', this.dragStart).on('drag', this.drag));
            container
                .selectAll('legendChips')
                .data(this._chartData.data.series)
                .join('button')
                .attr('class', 'chip')
                .attr('type', 'button')
                .attr('aria-pressed', false)
                .style('padding', '0.15rem 0.75rem')
                .style('border', '2px solid white')
                .style('border-radius', '6px')
                .style('display', 'flex')
                .style('flex-direction', 'row')
                .style('gap', '0.5rem')
                .style('align-items', 'center')
                .style('flex-wrap', 'nowrap')
                .style('min-width', 'fit-content')
                .style('height', 'calc(2rem - 2px)')
                .style('font-size', this.fontSize)
                .style('white-space', 'nowrap')
                .style('box-sizing', 'border-box')
                .style('background-color', 'white')
                .style('cursor', 'pointer')
                .html((d) => {
                return '<span style="pointer-events:none;">' + d.title + '</span>';
            })
                .on('click', this.togglePath)
                .on('mouseover', this.highlightPath)
                .on('focus', this.highlightPath)
                .on('mouseleave', this.colorLines)
                .on('blur', this.colorLines)
                .datum((d) => d)
                .insert('svg', 'span')
                .attr('class', 'legendSvg')
                .attr('pointer-events', 'none')
                .attr('width', 32)
                .attr('height', 28)
                .append('line')
                .attr('stroke', (d) => this.colorFunction(d))
                .attr('stroke-linecap', this._lineCap)
                .attr('stroke-width', 4)
                .attr('stroke-dasharray', (d, i) => this.dashFunction(d, i))
                .attr('x1', 3)
                .attr('x2', 29)
                .attr('y1', '50%')
                .attr('y2', '50%');
            // Only if data contains many lines
            if (this._chartData.data.series.length <= this._lineTypes.length)
                return;
            container
                .selectAll('.legendSvg')
                .filter((...[, i]) => {
                return i > (this._lineTypes.length - 1);
            })
                .append('path')
                .attr('d', (...[, i]) => this.symbolType(i, 70))
                .attr('fill', (d) => this.colorFunction(d))
                .attr('transform', 'translate(16,14)');
        };
        // Highlights a line
        this.highlightPath = (event, element) => {
            if (event.target.className != 'chip')
                return;
            const target = d3.select(event.target);
            target.style('border', (d) => '2px solid ' + this.colorFunction(d));
            this._lineChartContainer
                .selectAll('.linePath')
                .transition('highlight')
                .duration(150)
                .attr('stroke', (d, i, nodelist) => {
                if (d.title === element.title) {
                    d3.select(nodelist[i]).raise();
                    return this.colorFunction(d);
                }
                else
                    return '#E3E3E3';
            })
                .attr('stroke-width', (d) => {
                if (d.title === element.title)
                    return this._strokeWidth * 1.25;
                else
                    return this._strokeWidth;
            });
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .attr('fill', (d, i, nodelist) => {
                if (d.title === element.title) {
                    d3.select(nodelist[i]).raise();
                }
            });
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .selectAll('.symbol')
                .transition('highlight')
                .duration(150)
                .attr('fill', (d) => {
                if (!d)
                    return;
                if (d.title === element.title) {
                    return this.colorFunction(d);
                }
                else
                    return '#E3E3E3';
            });
        };
        // Selecting a path to view sollely from legend
        this.togglePath = (event) => {
            if (event.target.className != 'chip')
                return;
            const target = d3.select(event.target);
            // Using color of text to identify what state the legend chip is in, so resetting this the last thing we do
            const swap = 'white' === target.style('color');
            target
                .style('border', (d) => '2px solid ' + this.colorFunction(d))
                .style('background-color', (d) => swap ? 'white' : this.colorFunction(d))
                .style('color', () => (swap ? 'black' : 'white'))
                .attr('aria-pressed', target.attr('aria-pressed') == 'false' ? true : false)
                .select('line')
                .attr('stroke', (d) => (swap ? this.colorFunction(d) : 'white'));
            target
                .select('path')
                .attr('fill', (d) => (swap ? this.colorFunction(d) : 'white'));
            if (swap) {
                this._selectedLines = this._selectedLines.filter((elem) => {
                    return elem.title === target.datum()['title'] ? false : true;
                });
            }
            else {
                this._selectedLines.push(this._coreSeries.filter((elem) => elem.title === target.datum()['title'])[0]);
            }
            if (this._selectedLines.length < 1)
                this._chartData.data.series = JSON.parse(JSON.stringify(this._coreSeries));
            else {
                this._chartData.data.series.map((serie) => {
                    const index = this._selectedLines.findIndex((selected) => {
                        return serie.title === selected.title;
                    });
                    if (index != -1) {
                        serie.yValues = this._selectedLines[index].yValues;
                    }
                    else {
                        serie.yValues = [];
                    }
                });
            }
            this.updateSvg();
        };
        // Coloring all lines
        this.colorLines = (event) => {
            const target = d3.select(event.target);
            target.style('border', '2px solid white');
            this._lineChartContainer
                .selectAll('.linePath')
                .transition('highlight')
                .duration(150)
                .attr('stroke', (d) => this.colorFunction(d))
                .attr('stroke-width', this._strokeWidth);
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .selectAll('.symbol')
                .transition('highlight')
                .duration(150)
                .attr('fill', (d) => (d ? this.colorFunction(d) : null));
        };
        // Returns a line type given a integer value
        this.dashFunction = (...[, index]) => {
            return this._lineTypes[index % this._lineTypes.length];
        };
        // Initialize the drawing of the lines
        this.initLines = () => {
            this.defineLineAndScale();
            // TODO: do this data appending somewhere else
            this._lineChartContainer
                .selectAll('linePath')
                .data(this.reshapeData(this._chartData.data.series))
                .join('path')
                .attr('class', 'linePath')
                .attr('stroke', (d) => this.colorFunction(d))
                .attr('stroke-linecap', this._lineCap)
                .attr('stroke-width', (d) => (d.length > 1 ? this._strokeWidth : 4))
                .attr('stroke-dasharray', (d, i) => d.length > 1 ? this.dashFunction(d, i) : null)
                .attr('fill', 'none')
                .attr('d', (d) => this._LineFunc(d.line));
            this.addSymbols();
        };
        this.defineLineAndScale = () => {
            this._LineFunc = d3
                .line()
                .defined((d) => d[1] != null && d[1] != undefined && !isNaN(d[1]))
                .x((d) => this._lineScale(d[0]))
                .y((d) => this._yAxis(d[1]));
        };
        // Modifies data object adds a line member to object, line is an array of pairs of number: [number, number][]
        // Line is used to feed into _LineFunc
        this.reshapeData = (series) => {
            return series.map((obj) => {
                obj.line = this._chartData.data.xValues.map((elem, ind) => {
                    return [elem, obj.yValues[ind]];
                });
                obj.length = obj.yValues.filter((e) => e != null && e != undefined).length;
                return obj;
            });
        };
        // Adding symbols to lines where needed
        this.addSymbols = (transition = false) => {
            // Remove all old symbols
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .data(this.reshapeData(this._chartData.data.series.filter(
            // Symbols are only needed, when we are out of line types
            (...[, index]) => index > (this._lineTypes.length - 1))))
                .join((enter) => {
                if (!transition) {
                    enter
                        .append('g')
                        .attr('class', 'symbolGroup')
                        .selectAll('symbols')
                        .data((d, i) => {
                        return d.line.map((num) => {
                            if (num[1] != null)
                                return {
                                    val: num[1],
                                    x: num[0],
                                    title: d.title,
                                    colorToken: d.colorToken,
                                    index: i
                                };
                            else
                                return null;
                        });
                    })
                        .join('path')
                        .attr('class', 'symbol')
                        .attr('d', (d) => (d ? this.symbolType(d.index) : null))
                        .attr('fill', (d) => (d ? this.colorFunction(d) : null))
                        .attr('transform', (d) => d
                        ? `translate(${this._lineScale(d.x)},${this._yAxis(d.val)})`
                        : null);
                }
                else {
                    enter
                        .append('g')
                        .attr('class', 'symbolGroup')
                        .selectAll('symbol')
                        .data((d, i) => {
                        return d.line.map((num) => {
                            if (num[1] != null)
                                return {
                                    val: num[1],
                                    x: num[0],
                                    title: d.title,
                                    colorToken: d.colorToken,
                                    index: i
                                };
                            else
                                return null;
                        });
                    })
                        .join('path')
                        .attr('class', 'symbol')
                        .attr('d', (d) => (d ? this.symbolType(d.index) : null))
                        .attr('fill', (d) => (d ? this.colorFunction(d) : null))
                        .attr('transform', (d) => d
                        ? `translate(${this._lineScale(d.x)},${this._yAxis(d.val)})`
                        : null);
                }
            }, (update) => {
                if (!transition) {
                    update
                        .selectAll('.symbol')
                        .data((d, i) => {
                        return d.line.map((num) => {
                            console.log(d.title, i);
                            if (num[1] != null)
                                return {
                                    val: num[1],
                                    x: num[0],
                                    title: d.title,
                                    colorToken: d.colorToken,
                                    index: i
                                };
                            else
                                return null;
                        });
                    })
                        .join('path')
                        .attr('class', 'symbol')
                        .attr('d', (d) => (d ? this.symbolType(d.index) : null))
                        .attr('fill', (d) => (d ? this.colorFunction(d) : null))
                        .attr('transform', (d) => d
                        ? `translate(${this._lineScale(d.x)},${this._yAxis(d.val)})`
                        : null);
                }
                else {
                    update
                        .selectAll('path')
                        .data((d, i) => {
                        const toReturn = d.line.map((num) => {
                            if (num[1] != null)
                                return {
                                    val: num[1],
                                    x: num[0],
                                    title: d.title,
                                    colorToken: d.colorToken,
                                    index: i
                                };
                            else
                                return null;
                        });
                        return toReturn.filter((e) => e);
                    })
                        .join((enter) => {
                        enter
                            .append('path')
                            .attr('class', 'symbol')
                            .attr('d', (d) => d ? this.symbolType(d.index) : null)
                            .attr('fill', (d) => d ? this.colorFunction(d) : null)
                            .attr('transform', (d) => d
                            ? `translate(${this._lineScale(d.x)},${this._yAxis(d.val)})`
                            : null)
                            .attr('opacity', 0)
                            .transition()
                            .duration(400)
                            .ease(d3.easePolyInOut)
                            .attr('opacity', 1);
                    }, (update) => {
                        update
                            .transition()
                            .duration(400)
                            .ease(d3.easePolyInOut)
                            .attr('d', (d) => d ? this.symbolType(d.index) : null)
                            .attr('fill', (d) => d ? this.colorFunction(d) : null)
                            .attr('transform', (d) => d
                            ? `translate(${this._lineScale(d.x)},${this._yAxis(d.val)})`
                            : null);
                    }, (exit) => exit.remove());
                }
            }, (exit) => exit.remove());
        };
        // Returns a symbol type given a index
        this.symbolType = (index, size) => {
            return d3
                .symbol()
                .type(this._strokeGlyphs[Math.trunc(index / this._lineTypes.length)])
                .size(size ? size : 60)();
        };
        // Setting dimension variable
        this.setDims = (onlyHeight = false) => {
            // Get title dimensions
            const tempTitle = d3.select(this._titleDiv).node().getBoundingClientRect(), tempLegend = d3.select(this._legendDiv).node().getBoundingClientRect();
            const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
            if (!onlyHeight)
                this.dims = d3.select(this._chartDiv).node().getBoundingClientRect();
            this.dims.height =
                this._maxHeight - tempLegend.height - tempTitle.height - 1.75 * rem;
        };
        // Resizing svg element and its children
        this.resizeSvg = () => {
            if (this._loading)
                return this.loadingResize();
            this.setDims();
            this._svg.attr('width', this.dims.width).attr('height', this.dims.height);
            /****  Update yAxis ****/
            this._yAxis.range([this.dims.height - this.margin.bottom, this.margin.top]);
            // Fixed number of y-axis lines
            this.setYTicks(true);
            /****  Update xAxis ****/
            this._xAxis.range([
                0,
                this.dims.width - this.margin.left - this.margin.right
            ]);
            const ticks = Math.min(Math.max(Math.floor((this.dims.width - this.margin.left - this.margin.right) /
                this._tickSize) - 2, 1), this._chartData.data.xValues.length);
            this._xAxisHandle
                .attr('transform', 'translate(0,' + (this.dims.height - this.margin.bottom) + ')')
                .call(d3
                .axisBottom(this._xAxis)
                .ticks(ticks)
                .tickPadding(this.xPadding)
                .tickFormat((d) => {
                if (this._chartData.data.xValueNames)
                    return this._chartData.data.xValueNames[this._chartData.data.xValues.indexOf(d)];
                else
                    return d;
            }));
            this._Tipbox
                .attr('width', this.dims.width - this.margin.left / 2)
                .attr('height', this.dims.height - this.margin.top - this.margin.bottom)
                .attr('transform', `translate(${this.margin.left / 2},${this.margin.top})`);
        };
        // resizing chart while loading
        this.loadingResize = () => {
            this.setDims();
            this._svg.attr('width', this.dims.width);
            this.setYTicks();
            this._xAxis.range([
                0,
                this.dims.width - this.margin.left - this.margin.right
            ]);
            const ticks = Math.min(Math.floor((this.dims.width - this.margin.left - this.margin.right) /
                this._tickSize) - 2, this._xAxis.domain()[1]);
            this._xAxisHandle.call(d3
                .axisBottom(this._xAxis)
                .ticks(ticks)
                .tickPadding(this.xPadding)
                .tickFormat((d) => {
                if (this._chartData.data && this._chartData.data.xValueNames)
                    return this._chartData.data.xValueNames[this._chartData.data.xValues.indexOf(d)];
                else
                    return d;
            }));
            this._lineScale.range([
                0,
                this.dims.width - this.margin.left - this.margin.right
            ]);
            const LineFunc = d3
                .line()
                .defined((d) => d[1] != null)
                .x((d) => this._lineScale(d[0]))
                .y((d) => this._yAxis(d[1]))
                .curve(d3.curveBasis);
            this._lineChartContainer
                .selectAll('.linePath')
                .attr('d', (d) => LineFunc(d));
        };
        this.resizeLines = () => {
            if (this._loading)
                return;
            this._lineScale.range([
                0,
                this.dims.width - this.margin.left - this.margin.right
            ]);
            this.defineLineAndScale();
            this._lineChartContainer
                .selectAll('.linePath')
                .attr('stroke', (d) => this.colorFunction(d))
                .attr('d', (d) => this._LineFunc(d.line));
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .selectAll('.symbol')
                .attr('transform', (d, i) => d
                ? `translate(${this._lineScale(this._chartData.data.xValues[i])},${this._yAxis(d.val)})`
                : null);
        };
        this.colorFunction = (properties) => {
            return properties.colorToken
                ? getComputedStyle(document.documentElement)
                    .getPropertyValue(properties.colorToken)
                    .trim()
                    .toLocaleLowerCase()
                : this._Color(properties.title);
        };
        this.drawTooltipLine = (event) => {
            if (this._toggled)
                return this.drawDottedLine(event);
            this.drawMainLineAndCircles(event);
        };
        this.closeTooltip = () => {
            this._toggled = false;
            this._tooltip.style('display', 'none');
            this._secondaryLine.attr('display', 'none');
            this._TooltipLine.attr('display', 'none');
            this._Circles.selectAll('circle').attr('display', 'none');
            this._Circles
                .selectAll('circle')
                .attr('fill', 'white')
                .attr('stroke', (d) => this.colorFunction(d));
            this._svg.select('.tooltipPointer').remove();
        };
        this.drawDottedLine = (event) => {
            this._hoverPoint = this.closest(Math.round(this._lineScale.invert(d3.pointer(event, this._Tipbox.node())[0] - this.margin.left / 2)));
            if (this._hoverPoint === this._tooltipPoint)
                return this._secondaryLine.attr('display', 'none');
            this._secondaryLine
                .attr('display', null)
                .attr('stroke', 'gray')
                .attr('stroke-dasharray', 4)
                .attr('x1', this._xAxis(this._hoverPoint.toString()) + this.margin.left)
                .attr('x2', this._xAxis(this._hoverPoint.toString()) + this.margin.left)
                .attr('y1', this._yAxis(this._maxVal))
                .attr('y2', this.dims.height - this.margin.bottom)
                .style('pointer-events', 'none');
        };
        this.drawMainLineAndCircles = (event) => {
            this._hoverPoint = this.touchPosition(event);
            const stringPoint = this._hoverPoint.toString();
            this._TooltipLine
                .attr('display', null)
                .attr('stroke', 'gray')
                .attr('x1', this._xAxis(stringPoint) + this.margin.left)
                .attr('x2', this._xAxis(stringPoint) + this.margin.left)
                .attr('y1', this._yAxis(this._maxVal))
                .attr('y2', this.dims.height - this.margin.bottom)
                .style('pointer-events', 'none');
            this._Circles
                .selectAll('circle')
                .attr('display', null)
                .attr('pointer-events', 'none');
            this.positionCircles(stringPoint);
        };
        // Returns the position/index to draw tooltip/tooltip line
        this.touchPosition = (event) => {
            if (event.type === 'touchmove') {
                event.preventDefault();
                return this.closest(Math.round(this._lineScale.invert(d3.pointer(event.touches[0], this._Tipbox.node())[0] -
                    this.margin.left / 2)));
            }
            else {
                return this.closest(Math.round(this._lineScale.invert(d3.pointer(event, this._Tipbox.node())[0] - this.margin.left / 2)));
            }
        };
        // Function for drawing tooltip
        this.drawTooltip = (event) => {
            this.drawMainLineAndCircles(event);
            this._toggled = true;
            this._tooltipPoint = this.touchPosition(event);
            this._tooltipDataIndex = this._chartData.data.xValues.indexOf(this._tooltipPoint);
            // Heading
            this._tooltip.style('display', 'flex');
            const tooltipBody = this._tooltip.select('.tooltipBody');
            const tooltipHead = this._tooltip.select('.tooltipHead');
            tooltipHead
                .select(this._tooltipHeading)
                .style('margin', '0 0.75rem 0 0 ')
                .html(() => {
                if (this._chartData.data.xValueNames)
                    return this._chartData.data.xValueNames[this._chartData.data.xValues.indexOf(this._tooltipPoint)];
                return this._tooltipPoint;
            });
            // Body
            tooltipBody
                .selectAll('div')
                .data(this._chartData.data.series)
                .join('div')
                .style('display', 'flex')
                .style('flex-direction', 'row')
                .style('flex-wrap', 'nowrap')
                .style('white-space', 'nowrap')
                .style('justify-content', 'space-between')
                .html((d) => {
                return d.yValues[this._tooltipDataIndex] != null
                    ? '<span>' +
                        d.title +
                        '</span><span style="margin-left:0.75rem;">' +
                        d3.formatLocale(this.axisNumberFormat).format(this.formatStyle)(d.yValues[this._tooltipDataIndex]) +
                        '</span>'
                    : null;
            });
            this.positionTooltipWithinBounds(this._xAxis(this._tooltipPoint.toString()) + this.margin.left);
            this._Circles
                .selectAll('circle')
                .attr('fill', (d) => this.colorFunction(d))
                .attr('stroke', 'white');
            this._svg.select('.tooltipPointer').remove();
            const sym = d3.symbol().type(d3.symbolTriangle).size(60);
            this._svg
                .append('path')
                .attr('d', sym)
                .attr('class', 'tooltipPointer')
                .attr('fill', 'rgba(10, 10, 10, 0.90)')
                .style('filter', 'drop-shadow(gray 0px -1px 1px)')
                .attr('transform', `translate(${this._xAxis(this._tooltipPoint.toString()) + this.margin.left},${this._yAxis(this._maxVal) - 7}) rotate(180) scale(1.5,1.0)`);
        };
        this.initYAxis = (empty) => {
            // Y Axis
            this._yAxis = d3
                .scaleLinear()
                .domain([0, empty ? 10 : this._maxVal])
                .nice(3)
                .range([this.dims.height - this.margin.bottom, this.margin.top]);
            // The magic number is just needed in initialization, because getting the exact size is impossible during the initialization
            this.margin.left =
                this.getTextSize(d3.formatLocale(this.axisNumberFormat).format(this.formatStyle)(this._yAxis.domain()[1])) +
                    this.yPadding +
                    3;
            this._lineChartContainer = this._svg
                .append('g')
                .attr('transform', `translate(${this.margin.left},0)`);
            this._yAxisHandle = this._lineChartContainer
                .append('g')
                .attr('class', 'yAxis');
            this.setYTicks(true);
        };
        this.initXAxis = (empty) => {
            this._lineChartContainer.select('.xAxis').remove();
            // X axis
            if (!empty) {
                if (this._chartData.data.xValueNames)
                    this.margin.right =
                        this.getTextSize(this._chartData.data.xValueNames[this._chartData.data.xValueNames.length - 1]) /
                            2 +
                            1;
                else
                    this.margin.right =
                        this.getTextSize(d3.max(this._chartData.data.xValues).toString()) /
                            2 +
                            1;
            }
            this._xAxis = d3
                .scaleLinear()
                .domain([
                empty ? 1 : d3.min(this._chartData.data.xValues),
                empty ? 10 : d3.max(this._chartData.data.xValues)
            ]) // This is the min and the max of the data
                .range([0, this.dims.width - this.margin.left - this.margin.right]);
            this._lineScale = d3
                .scaleLinear()
                .domain([
                empty ? 1 : d3.min(this._chartData.data.xValues),
                empty ? 10 : d3.max(this._chartData.data.xValues)
            ]) // This is the min and the max of the data
                .range([0, this.dims.width - this.margin.left - this.margin.right]);
            const ticks = Math.min(Math.max(Math.floor((this.dims.width - this.margin.left - this.margin.right) /
                this._tickSize) - 2, 2), empty ? 10 : this._chartData.data.xValues.length);
            this._xAxisHandle = this._lineChartContainer
                .append('g')
                .attr('transform', 'translate(0,' + (this.dims.height - this.margin.bottom) + ')')
                .attr('class', 'xAxis')
                .style('font-size', this.fontSize)
                .call(d3
                .axisBottom(this._xAxis)
                .ticks(ticks)
                .tickSize(this.xTickSize)
                .tickPadding(this.xPadding)
                .tickFormat((d) => {
                if (!empty && this._chartData.data.xValueNames)
                    return this._chartData.data.xValueNames[this._chartData.data.xValues.indexOf(d)];
                else
                    return d;
            }));
            // Removing the domain
            this._xAxisHandle.select('.domain').attr('display', 'none');
            this._xAxisHandle
                .selectAll('line')
                .attr('stroke-linecap', this._lineCap)
                .attr('stroke', '#757575');
        };
        this.positionCircles = (point, transition) => {
            if (transition) {
                this._Circles
                    .selectAll('circle')
                    .transition('positioncircles')
                    .duration(transition)
                    .attr('cx', () => {
                    return this._xAxis(point) + this.margin.left;
                })
                    .attr('cy', (d, i, circleList) => {
                    const selData = d.yValues[this._chartData.data.xValues.indexOf(parseInt(point))];
                    if (this._yAxis(selData))
                        return this._yAxis(selData);
                    else {
                        d3.select(circleList[i]).attr('display', 'none');
                        return null;
                    }
                });
            }
            else {
                this._Circles
                    .selectAll('circle')
                    .attr('cx', () => {
                    return this._xAxis(point) + this.margin.left;
                })
                    .attr('cy', (d, i, circleList) => {
                    const selData = d.yValues[this._chartData.data.xValues.indexOf(parseInt(point))];
                    if (this._yAxis(selData))
                        return this._yAxis(selData);
                    else {
                        d3.select(circleList[i]).attr('display', 'none');
                        return null;
                    }
                });
            }
        };
        this.repositionTooltip = () => {
            if (!this._toggled)
                return;
            const stringPoint = this._chartData.data.xValues[this._tooltipDataIndex].toString();
            this._TooltipLine
                .attr('stroke', 'gray')
                .attr('x1', this._xAxis(stringPoint) + this.margin.left)
                .attr('x2', this._xAxis(stringPoint) + this.margin.left)
                .attr('y1', this._yAxis(this._maxVal))
                .attr('y2', this.dims.height - this.margin.bottom);
            this.positionTooltipWithinBounds(this._xAxis(stringPoint) + this.margin.left);
            this._svg
                .select('.tooltipPointer')
                .attr('transform', `translate(${this._xAxis(this._tooltipPoint.toString()) + this.margin.left},${this._yAxis(this._maxVal) - 7}) rotate(180) scale(1.5,1.0)`);
            this.positionCircles(stringPoint);
        };
        // Checks if tooltip bounding box is outside svg width, if so, moves tooltip within svg bounds
        this.positionTooltipWithinBounds = (pos, transitionTimer) => {
            this._tooltip.style('transform', () => {
                // We reset the position of the tooltip, and adjust for the maximum value, -1px for it to align perfectly
                return `translateY(${this._yAxis(this._maxVal) - this._tooltip.node().getBoundingClientRect().height - 9}px)`;
            });
            const rect = this._chartDiv.getBoundingClientRect();
            const comparativePosition = pos + rect.left;
            const tooltipWidth = this._tooltip.node().getBoundingClientRect().width / 2;
            if (rect.left < comparativePosition - tooltipWidth &&
                rect.right > comparativePosition + tooltipWidth) {
                this._tooltip.style('left', pos - tooltipWidth + 'px');
            }
            else if (rect.right < comparativePosition + tooltipWidth) {
                this._tooltip.style('left', pos -
                    this._tooltip.node().getBoundingClientRect().width +
                    Math.max(this.margin.right, 10) +
                    'px');
            }
            else if (rect.left > comparativePosition - tooltipWidth) {
                if (transitionTimer)
                    this._tooltip
                        .transition('moveTooltip')
                        .duration(transitionTimer)
                        .ease(d3.easePolyInOut)
                        .style('left', pos - this.margin.left + 'px');
                else
                    this._tooltip.style('left', pos - this.margin.left + 'px');
            }
            else {
                this._tooltip.style('left', pos -
                    document.getElementById(this.afId).getBoundingClientRect().width / 2 +
                    'px');
            }
        };
        this.mapColorToLine = () => {
            // list of group names
            this._Color = d3
                .scaleOrdinal()
                .domain(this._chartData.data.series.map(function (d) {
                return d.title;
            }))
                .range([
                '#00005a',
                '#D43372',
                '#058470',
                '#0058A3',
                '#556D22',
                '#1616B2',
                '#AA295B',
                '#035548'
            ]);
        };
        this.computeMaxVal = () => {
            // Compute max value from datasets
            this._maxVal = parseFloat(d3.max(this._chartData.data.series, (d) => d3.max(d['yValues'])));
        };
        // Updates number format if specified to be in percentage
        this.checkPercentage = () => {
            if (this._chartData.meta && this._chartData.meta.percentage)
                this.formatStyle = '.1%';
            else
                this.formatStyle = ',d';
        };
        this.closest = (target) => {
            return this._chartData.data.xValues.reduce(function (prev, curr) {
                return Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev;
            });
        };
        // Removes resize observer for chart and inits table
        this.activateTable = (event) => {
            this.closeTooltip();
            this.lineChartObserver.disconnect();
            this.tableObserver.observe(this._chartDiv);
            this._status.innerHTML = '<p>Visas som tabell</p>';
            d3.select(this._host).select('.tableButton').style('display', 'none');
            d3.select(this._host).select('.chartButton').style('display', null);
            event.target.nextSibling.lastChild.focus();
            const duration = 150;
            this._svg
                .attr('opacity', 1)
                .transition('fadeout')
                .duration(duration)
                .attr('opacity', 0)
                .on('end', () => {
                this._svg.remove();
                this._legendDiv.style['display'] = 'none';
                this._tableDiv.style['display'] = 'block';
                this.setTableContent();
                this.setTableDimensions();
                d3.select(this._tableDiv)
                    .style('opacity', 0)
                    .transition('fadein')
                    .duration(duration)
                    .style('opacity', 1);
            });
            d3.select(this._legendDiv)
                .style('opacity', 1)
                .transition('fadeout')
                .duration(duration)
                .style('opacity', 0);
        };
        // Resize table
        this.resizeTable = () => {
            this.setDims();
            this.setTableDimensions();
        };
        // Removes observer and calls init functions for line chart
        this.reInitChart = (event) => {
            this.tableObserver.disconnect();
            this._status.innerHTML = '<p>Visas som diagram</p>';
            d3.select(this._host).select('.tableButton').style('display', null);
            d3.select(this._host).select('.chartButton').style('display', 'none');
            event.target.previousSibling.lastChild.focus();
            const table = d3.select(this._tableDiv);
            table
                .style('opacity', 1)
                .transition()
                .duration(150)
                .style('opacity', 0)
                .on('end', () => {
                table.style('display', 'none');
                this.initSvg();
                this.initLines();
                this._svg
                    .attr('opacity', 0)
                    .transition()
                    .duration(150)
                    .attr('opacity', 1);
                d3.select(this._legendDiv)
                    .style('display', null)
                    .style('opacity', 0)
                    .transition()
                    .duration(150)
                    .style('opacity', 1);
            });
        };
        this.afHeadingLevel = undefined;
        this.afId = randomIdGenerator('tooltip');
        this.afChartData = undefined;
    }
    afChartDataUpdate() {
        const data = this.afChartData;
        if (typeof data === 'object') {
            this._chartData = Object.assign({}, data);
            return;
        }
        this._chartData = JSON.parse(data);
        // Making deep copy of this
        if (this._chartData.data)
            this._coreSeries = JSON.parse(JSON.stringify(this._chartData.data.series));
    }
    initSvg() {
        this.margin.bottom =
            Number(window
                .getComputedStyle(document.body)
                .getPropertyValue('font-size')
                .match(/\d+/)[0]) *
                Number(this.fontSize.replace(/[^\d.]|\.(?=.*\.)/g, '')) +
                this.xPadding +
                this.xTickSize;
        this.lineChartObserver.observe(this._chartDiv);
        d3.select(this._chartDiv).select('svg').remove();
        // Initialize an empty div, that we can place info in for tooltip
        // Styling for tooltip is in .scss file
        this._tooltip = d3.select('#' + this.afId);
        this._tooltip.select('.close').on('click', this.closeTooltip);
        this._tooltipHeading =
            'h' + (parseInt(this.afHeadingLevel.replace(/\D/g, '')) + 1);
        // Add heading placeholder for heading
        this._tooltip
            .selectAll('.tooltipHead')
            .insert(this._tooltipHeading, ':first-child') // increment heading level by one
            .style('margin', '0 0.75rem')
            .style('font-weight', '500');
        this._maxHeight = d3
            .select(this._host)
            .node()
            .parentNode.getBoundingClientRect().height;
        this.setDims();
        this.setTickSize();
        this.checkPercentage();
        d3.select(this._chartDiv).attr('height', this.dims.height + 'px');
        // Appending svg
        this._svg = d3
            .select(this._chartDiv)
            .append('svg')
            .attr('aria-hidden', true)
            .attr('role', 'img')
            .attr('aria-label', this._chartData && this._chartData.title
            ? 'Linjediagram om ' + this._chartData.title.toLowerCase()
            : 'Linjediagram');
        this._svg.attr('width', this.dims.width).attr('height', this.dims.height);
        this.mapColorToLine();
        this._TooltipLine = this._svg
            .append('line')
            .attr('id', 'toolTipLine' + this.afId);
        this._secondaryLine = this._svg
            .append('line')
            .attr('id', 'secondaryLine' + this.afId);
        this.computeMaxVal();
        this.initYAxis();
        this.initXAxis();
        this._Tipbox = this._svg
            .append('rect')
            .attr('class', 'svgRect')
            .attr('width', this.dims.width - this.margin.left / 2)
            .attr('height', this.dims.height - this.margin.top - this.margin.bottom)
            .attr('opacity', 0)
            .attr('transform', `translate(${this.margin.left / 2},0)`);
        this.addListenersToChart();
        this._Circles = this._svg
            .append('g')
            .attr('class', 'circleGroup')
            .on('mousemove', this.drawTooltipLine);
        this._Circles
            .selectAll('circle')
            .data(this._chartData.data.series)
            .join('circle')
            .attr('stroke', (d) => this.colorFunction(d))
            .attr('fill', 'white')
            .attr('stroke-width', '1px')
            .attr('r', 7)
            .attr('display', 'none');
        d3.select(window).on('resize.' + this.afId, this.repositionTooltip);
    }
    // Initializing chart with empty dataset
    initEmpty() {
        this.margin.bottom =
            Number(window
                .getComputedStyle(document.body)
                .getPropertyValue('font-size')
                .match(/\d+/)[0]) *
                Number(this.fontSize.replace(/[^\d.]|\.(?=.*\.)/g, '')) +
                this.xPadding +
                this.xTickSize;
        d3.select(this._chartDiv).select('svg').remove();
        // Initialize an empty div, that we can place info in for tooltip
        // Styling for tooltip is in .scss file
        this._tooltip = d3.select('#' + this.afId);
        d3.select('digi-icon-x').on('click', this.closeTooltip);
        this._tooltipHeading =
            'h' + (parseInt(this.afHeadingLevel.replace(/\D/g, '')) + 1);
        // Add heading placeholder for heading
        this._tooltip
            .selectAll('.tooltipHead')
            .insert(this._tooltipHeading, ':first-child') // increment heading level by one
            .style('margin', '0 0.75rem')
            .style('font-weight', '500');
        this._maxHeight = d3
            .select(this._host)
            .node()
            .parentNode.getBoundingClientRect().height;
        this.setDims();
        d3.select(this._chartDiv).attr('height', this.dims.height + 'px');
        this._svg = d3
            .select(this._chartDiv)
            .append('svg')
            .attr('aria-hidden', true);
        this._svg.attr('width', this.dims.width).attr('height', this.dims.height);
        this._TooltipLine = this._svg
            .append('line')
            .attr('id', 'toolTipLine' + this.afId);
        this._secondaryLine = this._svg
            .append('line')
            .attr('id', 'secondaryLine' + this.afId);
        this.initYAxis(true);
        this.initXAxis(true);
        this._Tipbox = this._svg
            .append('rect')
            .attr('class', 'svgRect')
            .attr('width', this.dims.width - this.margin.left / 2)
            .attr('height', this.dims.height - this.margin.top - this.margin.bottom)
            .attr('opacity', 0)
            .attr('transform', `translate(${this.margin.left / 2},0)`);
        this._Circles = this._svg
            .append('g')
            .attr('class', 'circleGroup')
            .on('mousemove', this.drawTooltipLine);
        this.defineLineAndScale();
        this.lineChartObserver.observe(this._chartDiv);
        this.loadingState(true);
    }
    loadingState(empty) {
        this._loading = true;
        d3.select('.tableButton').style('display', 'none');
        const duration = 400;
        const LineFunc = d3
            .line()
            .defined((d) => d[1] != null)
            .x((d) => this._lineScale(d[0]))
            .y((d) => this._yAxis(d[1]))
            .curve(d3.curveBasis);
        // close potentially open tooltip
        this.closeTooltip();
        // Remove hover listeners from chart
        this._Tipbox
            .style('cursor', 'wait')
            .on('mousemove', null)
            .on('touchmove', null)
            .on('mouseup', null)
            .on('mouseleave', null);
        d3.select(this._titleDiv)
            .select(this.afHeadingLevel)
            .html(`${_t.loading}...`);
        const height = d3.select(this._titleDiv).style('height');
        d3.select(this._titleDiv)
            .style('height', this._prevHeight)
            .transition('adjust')
            .duration(400)
            .style('height', height)
            .on('end', () => d3.select(this._titleDiv).style('height', null));
        d3.select(this._legendDiv).style('display', 'none');
        const animateRandomLine = () => {
            let firstVal = this._xAxis.domain()[0];
            const randomData = [...Array(this._xAxis.domain()[1])].map(() => {
                const dataPoint = [
                    firstVal,
                    Math.round(Math.random() * this._yAxis.domain()[1] * 100) / 100
                ]; // This assumes domain is from 0 to maxVal
                firstVal++;
                return dataPoint;
            });
            this._lineChartContainer.selectAll('.linePath').remove();
            this._lineChartContainer
                .selectAll('linePath')
                .data([randomData])
                .join('path')
                .attr('class', 'linePath')
                .attr('stroke', 'lightgray')
                .attr('stroke-linecap', this._lineCap)
                .attr('stroke-width', this._strokeWidth)
                .attr('fill', 'none')
                .attr('d', (d) => LineFunc(d))
                .transition()
                .duration(duration * 3)
                .ease(d3.easeQuadInOut)
                .attrTween('stroke-dasharray', function () {
                const length = this.getTotalLength();
                return d3.interpolate(`0,${length}`, `${length},${length}`);
            })
                .on('end', () => {
                this._lineChartContainer
                    .selectAll('.linePath')
                    .attr('stroke-dasharray', function () {
                    const length = this.getTotalLength();
                    return length + ' ' + length;
                })
                    .attr('stroke-dashoffset', 0)
                    .transition()
                    .duration(duration * 3)
                    .ease(d3.easeQuadInOut)
                    .attr('stroke-dashoffset', function () {
                    return -this.getTotalLength();
                })
                    .on('end', () => animateRandomLine());
            });
        };
        const flattenLines = () => {
            const data = this._lineChartContainer
                .selectAll('.linePath')
                .data()
                .map((elem) => {
                elem.line = elem.line.map((point) => {
                    return point[1]
                        ? [point[0], this._yAxis.domain()[0]]
                        : [point[0], undefined];
                });
                return elem;
            });
            this._lineChartContainer
                .selectAll('.linePath')
                .data(data)
                .transition()
                .duration(duration)
                .ease(d3.easePolyInOut)
                .attr('d', (d) => this._LineFunc(d.line))
                .on('end', (...[, i]) => (i === 0 ? animateRandomLine() : null));
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .selectAll('.symbol')
                .transition()
                .duration(duration)
                .ease(d3.easeLinear)
                .attr('transform', (d) => d
                ? `translate(${this._lineScale(d.x)},${this._yAxis(this._yAxis.domain()[0])})`
                : null)
                .attr('opacity', 0);
        };
        // Start loadingstate, either from empty set or last set
        if (empty)
            animateRandomLine();
        else
            flattenLines();
    }
    updateSvg() {
        // displaying legend again
        if (this._loading) {
            d3.select(this._host).select('.tableButton').style('display', null);
            d3.select(this._legendDiv).style('display', null);
            d3.select(this._titleDiv).style('height', null);
        }
        if (this._chartData.data.xValueNames)
            this.margin.right =
                this.getTextSize(this._chartData.data.xValueNames[this._chartData.data.xValueNames.length - 1]) /
                    2 +
                    1;
        else
            this.margin.right =
                this.getTextSize(d3.max(this._chartData.data.xValues).toString()) / 2 +
                    1;
        this.checkPercentage();
        this.setDims(true);
        this.mapColorToLine();
        this.computeMaxVal();
        this.setTickSize();
        d3.select(this._chartDiv).attr('height', this.dims.height + 'px');
        // Updating Svg attributes
        this._svg
            .attr('width', this.dims.width + 'px')
            .attr('height', this.dims.height + 'px')
            .attr('aria-label', this._chartData && this._chartData.title
            ? 'Linjediagram om ' + this._chartData.title.toLowerCase()
            : 'Linjediagram');
        // Y Axis
        this._yAxis = d3
            .scaleLinear()
            .domain([0, this._maxVal])
            .nice(3)
            .range([this.dims.height - this.margin.bottom, this.margin.top]);
        // Updating margins
        this.margin.left =
            this.getTextSize(d3.formatLocale(this.axisNumberFormat).format(this.formatStyle)(this._yAxis.domain()[1])) +
                this.yPadding +
                3;
        /****  Update xAxis ****/
        const oldDomain = this._xAxis.domain();
        this.initXAxis();
        this._lineChartContainer
            .transition('position')
            .duration(400)
            .ease(d3.easePolyInOut)
            .attr('width', this.dims.width)
            .attr('height', this.dims.height)
            .attr('transform', `translate(${this.margin.left},0)`);
        this.setYTicks();
        this.defineLineAndScale();
        //this.leftSetAxis();
        this.addSymbols(true);
        if (this._loading) {
            d3.select(this._host).select('.tableButton').style('display', null);
            this.addListenersToChart();
            const height = d3.select(this._titleDiv).style('height');
            d3.select(this._titleDiv)
                .style('height', this._prevHeight)
                .transition('adjust')
                .duration(400)
                .style('height', height)
                .on('end', () => d3.select(this._titleDiv).style('height', null));
            d3.select(this._legendDiv)
                .style('opacity', 0)
                .transition()
                .duration(400)
                .ease(d3.easePolyInOut)
                .style('opacity', 1);
            this._lineChartContainer.selectAll('.linePath').on('end', null);
            // Setting starting state for symbols
            this._lineChartContainer
                .selectAll('.symbolGroup')
                .selectAll('.symbol')
                .attr('opacity', 0)
                .attr('transform', (d) => d
                ? `translate(${this._lineScale(d.x)},${this._yAxis(this._yAxis.domain()[0])})`
                : null);
            this._lineChartContainer
                .selectAll('.linePath')
                .transition()
                .duration(400)
                .ease(d3.easeQuadInOut)
                .attr('stroke-dashoffset', function () {
                return -this.getTotalLength();
            })
                .on('end', () => {
                this._lineChartContainer
                    .selectAll('.linePath')
                    .data(this.reshapeData(this._chartData.data.series))
                    .join('path')
                    .attr('class', 'linePath')
                    .attr('stroke', (d) => this.colorFunction(d))
                    .attr('stroke-linecap', this._lineCap)
                    .attr('stroke-width', this._strokeWidth)
                    .attr('fill', 'none')
                    .attr('stroke-dasharray', this.dashFunction)
                    .attr('stroke-dashoffset', null)
                    .attr('d', (d) => this._LineFunc(d.line.map((pair) => [
                    pair[0],
                    this._yAxis.domain()[0]
                ])));
                // Transitioning in lines
                this._lineChartContainer
                    .selectAll('.linePath')
                    .transition()
                    .duration(400)
                    .ease(d3.easePolyInOut)
                    .attr('d', (d) => this._LineFunc(d.line));
                // Transitioning in symbols
                this._lineChartContainer
                    .selectAll('.symbolGroup')
                    .selectAll('.symbol')
                    .transition()
                    .duration(400)
                    .ease(d3.easePolyInOut)
                    .attr('transform', (d) => d
                    ? `translate(${this._lineScale(d.x)},${this._yAxis(d.val)})`
                    : null)
                    .attr('opacity', 1);
                this._loading = false;
            });
        }
        else {
            this._lineChartContainer
                .selectAll('.linePath')
                .data(this.reshapeData(this._chartData.data.series))
                .join('path')
                .transition()
                .duration(400)
                .ease(d3.easePolyInOut)
                .attr('class', 'linePath')
                .attr('stroke', (d) => this.colorFunction(d))
                .attr('stroke-linecap', this._lineCap)
                .attr('stroke-width', (d) => (d.length > 1 ? this._strokeWidth : 4))
                .attr('fill', 'none')
                .attr('stroke-dasharray', (d, i) => d.length > 1 ? this.dashFunction(d, i) : null)
                .attr('d', (d) => this._LineFunc(d.line));
        }
        // If we have the exact same domain, the tooltip may continue to be active
        if (this._toggled && oldDomain === this._xAxis.domain()) {
            const stringPoint = this._chartData.data.xValues[this._tooltipDataIndex].toString();
            this._Circles
                .selectAll('circle')
                .data(this._chartData.data.series)
                .join('circle')
                .transition('New circles')
                .duration(400)
                .ease(d3.easePolyInOut)
                .attr('stroke', 'white')
                .attr('fill', (d) => this.colorFunction(d))
                .attr('stroke-width', '1px')
                .attr('r', 7);
            this.positionCircles(stringPoint, 400);
            const oldPos = this._TooltipLine.attr('y1');
            this._TooltipLine
                .transition()
                .duration(400)
                .ease(d3.easePolyInOut)
                .attr('x1', this._xAxis(stringPoint) + this.margin.left)
                .attr('x2', this._xAxis(stringPoint) + this.margin.left)
                .attr('y1', this._yAxis(this._maxVal))
                .attr('y2', this.dims.height - this.margin.bottom);
            const tooltipBody = this._tooltip.select('.tooltipBody');
            const tooltipHead = this._tooltip.select('.tooltipHead');
            tooltipHead
                .select(this._tooltipHeading)
                .style('margin', '0 0.75rem')
                .html(() => {
                if (this._chartData.data.xValueNames)
                    return this._chartData.data.xValueNames[this._tooltipPoint - 1];
                return this._tooltipPoint;
            });
            // Body
            tooltipBody
                .selectAll('div')
                .data(this._chartData.data.series)
                .join('div')
                .style('display', 'flex')
                .style('flex-direction', 'row')
                .style('flex-wrap', 'nowrap')
                .style('white-space', 'nowrap')
                .style('justify-content', 'space-between')
                .html((d) => {
                return ('<span>' +
                    d.title +
                    ':&nbsp</span><span class="tooltipVal">' +
                    d.yValues[this._tooltipDataIndex] +
                    '</span>');
            });
            this.positionTooltipWithinBounds(this._xAxis(stringPoint) + this.margin.left, 400);
            this._tooltip
                .style('transform', () => {
                // We reset the position of the tooltip, and adjust for the maximum value, -1px for it to align perfectly
                return `translateY(${oldPos - this._tooltip.node().getBoundingClientRect().height - 9}px)`;
            })
                .transition()
                .duration(400)
                .ease(d3.easePolyInOut)
                .style('transform', () => {
                // We reset the position of the tooltip, and adjust for the maximum value, -1px for it to align perfectly
                return `translateY(${this._yAxis(this._maxVal) - this._tooltip.node().getBoundingClientRect().height - 9}px)`;
            });
            this._svg
                .select('.tooltipPointer')
                .transition()
                .duration(400)
                .ease(d3.easePolyInOut)
                .attr('transform', `translate(${this._xAxis(stringPoint) + this.margin.left},${this._yAxis(this._maxVal) - 7}) rotate(180) scale(1.5,1.0)`);
        }
        else {
            this._Circles
                .selectAll('circle')
                .data(this._chartData.data.series)
                .join('circle')
                .attr('stroke', (d) => this.colorFunction(d))
                .attr('fill', 'white')
                .attr('stroke-width', '1px')
                .attr('r', 7)
                .attr('display', 'none');
            this.closeTooltip();
        }
    }
    componentWillLoad() {
        this.afChartDataUpdate();
    }
    componentDidLoad() {
        if (Object.keys(this._chartData).length === 0)
            return this.initEmpty();
        // clearing selection
        this._selectedLines = [];
        this.initSvg();
        this.initLegend();
        this.initLines();
        document.fonts.onloadingdone = () => {
            this.setTickSize();
            this.resizeSvg();
        };
    }
    componentWillUpdate() {
        this._prevHeight = d3.select(this._titleDiv).style('height');
    }
    componentDidUpdate() {
        if (Object.keys(this._chartData).length === 0)
            return this.loadingState();
        // clearing selection
        this._selectedLines = [];
        this.updateSvg();
        this.initLegend();
        this.updateTableContent();
    }
    disconnectedCallback() {
        this.lineChartObserver.disconnect();
    }
    // Returns pixelwidth of text given the current fontSize (costly function using .each)
    getTextSize(text) {
        const textWidth = [];
        const svg = d3.select(this._chartDiv).append('svg');
        svg
            .selectAll('dummyText')
            .data([text])
            .join('text')
            .attr('font-size', this.fontSize)
            .attr('font-family', 'sans-serif')
            .text((d) => d)
            .each((...[, i, nodelist]) => {
            const thisWidth = nodelist[i].getComputedTextLength() + 5; // A little extra padding, text sizes seems to be a little small compared to real life
            textWidth.push(thisWidth);
            nodelist[i].remove();
        });
        svg.remove().exit();
        return textWidth[0];
    }
    // Goes through all tick values and checks the biggest in size, this one will decide the default tick-size
    setTickSize() {
        // Padding on both sides comined
        const textPadding = 25; // 12.5 pixel padding on each side
        if (this._chartData.data.xValueNames) {
            for (const elem of this._chartData.data.xValueNames) {
                const size = this.getTextSize(elem) + textPadding;
                if (size > this._tickSize)
                    this._tickSize = size;
            }
        }
        else {
            for (const elem of this._chartData.data.xValues) {
                const size = this.getTextSize(elem.toString()) + textPadding;
                if (size > this._tickSize)
                    this._tickSize = size;
            }
        }
    }
    // Left-orients Y-axis text
    leftSetAxis(init = false) {
        const shiftArray = [];
        if (!init) {
            this._yAxisHandle
                .selectAll('text')
                .transition('shuffle text')
                .duration(400)
                .attr('transform', (...[, i, nodelist]) => {
                const size = nodelist[i].getComputedTextLength();
                shiftArray.push(size);
                return `translate(${-1 * (this.margin.left - (size + this.yPadding))},0)`;
            });
            this._yAxisHandle
                .selectAll('line')
                .transition('shuffle line')
                .duration(400)
                .attr('x1', (...[, i]) => {
                return -1 * (this.margin.left - (shiftArray[i] + this.yPadding));
            });
        }
        else {
            this._yAxisHandle
                .selectAll('text')
                .attr('transform', (...[, i, nodelist]) => {
                const size = nodelist[i].getComputedTextLength();
                shiftArray.push(size);
                return `translate(${-1 * (this.margin.left - (size + this.yPadding))},0)`;
            });
            this._yAxisHandle.selectAll('line').attr('x1', (...[, i]) => {
                return -1 * (this.margin.left - (shiftArray[i] + this.yPadding));
            });
        }
    }
    // Updating data if content changes
    updateTableContent() {
        if (this._tableDiv.style['display'] === '' ||
            this._tableDiv.style['display'] === 'none')
            return;
        this.setTableContent();
        this.animateTableDimensions();
    }
    // Setting table content from this._chartData
    setTableContent() {
        this._tableDiv.innerHTML = `<digi-table af-size="small" af-variation="primary"> <table><caption>${this._chartData.title}</caption><thead><tr>${this.headings()}</tr></thead><tbody>${this.tableBody()}</tbody></table></digi-table>`;
    }
    setTableDimensions() {
        const table = d3.select(this._tableDiv);
        table
            // The last value is for compensating the padding set on the table element, changing padding in css, will need change in these two lines
            .style('height', this._maxHeight -
            d3.select(this._titleDiv).node().getBoundingClientRect().height -
            parseFloat(getComputedStyle(document.documentElement).fontSize) *
                0.65 +
            'px');
        table.style('width', this.dims.width -
            parseFloat(getComputedStyle(document.documentElement).fontSize) * 0.65 +
            'px');
    }
    animateTableDimensions() {
        const height = d3.select(this._titleDiv).style('height');
        d3.select(this._titleDiv)
            .style('height', this._prevHeight)
            .transition('adjust')
            .duration(400)
            .style('height', height)
            .on('end', () => d3.select(this._titleDiv).style('height', null));
        const table = d3.select(this._tableDiv);
        table
            .transition('adjustHeight')
            .duration(400)
            // The last value is for compensating the padding set on the table element, changing padding in css, will need change in these two lines
            .style('height', this._maxHeight -
            (parseFloat(height) +
                parseFloat(getComputedStyle(document.documentElement).fontSize) *
                    1.25) -
            parseFloat(getComputedStyle(document.documentElement).fontSize) *
                0.65 +
            'px');
        table.style('width', this.dims.width -
            parseFloat(getComputedStyle(document.documentElement).fontSize) * 0.65 +
            'px');
    }
    // Extracting all headings from data and returning a string with <th>-elements with headings
    headings() {
        let headingsString = `<th scope="col">${this._chartData.x}</th>`;
        this._coreSeries.forEach((elem) => {
            headingsString = headingsString.concat(`<th scope="col">${elem.title}</th>`);
        });
        return headingsString;
    }
    // Building the table body
    tableBody() {
        let body = '';
        const loopable = this._chartData.data.xValueNames
            ? this._chartData.data.xValueNames
            : this._chartData.data.xValues;
        loopable.forEach((elem, index) => {
            let cells = '';
            this._coreSeries.forEach((serie) => {
                cells = cells.concat(`<td>${serie.yValues[index] != null ? d3.formatLocale(this.axisNumberFormat).format(this.formatStyle)(serie.yValues[index]) : '-'}</td>`);
            });
            body = body.concat(`<tr><th scope='row'>${elem}${cells}</th></tr>`);
        });
        return body;
    }
    render() {
        let heading = '';
        if (!this._chartData.subTitle)
            heading = `<${this.afHeadingLevel}>${this._chartData.title}</${this.afHeadingLevel}>`;
        else
            heading = `<${this.afHeadingLevel}>${this._chartData.title}</${this.afHeadingLevel}><p>${this._chartData.subTitle}</p>`;
        return (h("host", { key: 'eedf8d79973aa2d15f474e93134acbdfb9ef3d0c', ref: (el) => (this._host = el) }, h("div", { key: 'd8500c2d3ca271fac9ebf5ec35c4c4da9aef0abd', class: "topWrapper", ref: (el) => (this._titleDiv = el) }, h("digi-typography", { key: '5c9b87f00f5bba14becd10154e3b13cac48124b7', class: "chartTitle", innerHTML: heading }), h("digi-button", { key: '859a31da8c6afc1c07474ea4914af732588cef5b', class: "buttonWrapper tableButton", "af-size": "medium", "af-variation": "function", "af-full-width": "false", onAfOnClick: this.activateTable, afAriaLabel: `Visa tabell ${this._chartData.title ? this._chartData.title.toLowerCase() : ''}` }, h("digi-icon-table", { key: '3852def8d78f96314ee1a304a8b4b5d787e6f456', slot: "icon" }), "Visa tabell"), h("digi-button", { key: 'b4c53c45553bce7c77d20653d52a39c431597491', style: { display: 'none' }, class: "buttonWrapper chartButton", "af-size": "medium", "af-variation": "function", "af-full-width": "false", onAfOnClick: this.reInitChart, afAriaLabel: `Visa diagram ${this._chartData.title ? this._chartData.title.toLowerCase() : ''}` }, h("digi-icon-chart", { key: '8f5fabf6d507e5e664dcf9c89a73a2bfb5572645', slot: "icon" }), "Visa diagram"), h("div", { key: '7678960d000f436d5b0a9dce2f4489f9b48a0fee', class: "scChartStatus", role: "status", ref: (el) => (this._status = el) })), h("div", { key: '64edcbc78f51256719674dc4f9dc01af0ee7998a', class: "chartTooltip", id: this.afId }, h("div", { key: '0e9d9ffed55146e7aef132df07eea7ff2f5996c0', class: "tooltipHead" }, h("digi-icon-x", { key: '0463a7d4892199fb684f536a07a45132ad458c62', class: "close" }), ' '), h("div", { key: 'a703ec0152cee394b60a9110dfba8090c8e2e0bb', class: "tooltipBody" })), h("div", { key: '36fe4d1b5ede71ff5fd0e04be1ce33a6a5dfa7bd', class: "chart", ref: (el) => (this._chartDiv = el) }), h("div", { key: '558f1c0c8d1c6921d76a2e017197914af3b4e83c', class: "legend", ref: (el) => (this._legendDiv = el) }), h("div", { key: 'f003d37dd680bf77a0fda37ba976edce57a7fcfe', class: "table", ref: (el) => (this._tableDiv = el) }, ' ')));
    }
    static get is() { return "digi-chart-line"; }
    static get encapsulation() { return "scoped"; }
    static get originalStyleUrls() {
        return {
            "$": ["chart-line.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["chart-line.css"]
        };
    }
    static get properties() {
        return {
            "afHeadingLevel": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": ""
                },
                "getter": false,
                "setter": false,
                "attribute": "af-heading-level",
                "reflect": false
            },
            "afId": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": ""
                },
                "getter": false,
                "setter": false,
                "attribute": "af-id",
                "reflect": false,
                "defaultValue": "randomIdGenerator('tooltip')"
            },
            "afChartData": {
                "type": "string",
                "mutable": true,
                "complexType": {
                    "original": "string | ChartLineData",
                    "resolved": "ChartLineData | string",
                    "references": {
                        "ChartLineData": {
                            "location": "import",
                            "path": "../interfaces/line-chart-data.interface",
                            "id": "src/components/_chart/interfaces/line-chart-data.interface.ts::ChartLineData"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": ""
                },
                "getter": false,
                "setter": false,
                "attribute": "af-chart-data",
                "reflect": false
            }
        };
    }
    static get elementRef() { return "element"; }
    static get watchers() {
        return [{
                "propName": "afChartData",
                "methodName": "afChartDataUpdate"
            }];
    }
}
