#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#   Copyright (C) 2018 Oslandia <infos@oslandia.com>
#
#   This file is a piece of free software; you can redistribute it and/or
#   modify it under the terms of the GNU Library General Public
#   License as published by the Free Software Foundation; either
#   version 2 of the License, or (at your option) any later version.
#
#   This library is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   Library General Public License for more details.
#   You should have received a copy of the GNU Library General Public
#   License along with this library; if not, see <http://www.gnu.org/licenses/>.
#

from qgis.utils import iface
from qgis.PyQt.QtCore import QSizeF, QRectF, pyqtSignal
from qgis.PyQt.QtGui import QColor
from PyQt5.QtGui import QPolygonF
from qgis.PyQt.QtWidgets import QComboBox, QDialog, QVBoxLayout, QDialogButtonBox
from qgis.PyQt.QtWidgets import QStackedWidget

from qgis.core import (
    QgsGeometry,
    QgsFields,
    QgsFeature,
    QgsRectangle,
    QgsFeatureRenderer,
    QgsReadWriteContext,
    QgsProject,
    QgsPointXY,
    QgsFeatureRequest,
)
from qgis.gui import QgsMapToolIdentifyFeature

from .common import POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER, qgis_render_context
from .common import ORIENTATION_UPWARD, ORIENTATION_DOWNWARD, ORIENTATION_LEFT_TO_RIGHT, LogItem
from .data_interface import FeatureData, LayerData, IntervalData

from .units import unitConversionFactor

from random import randint

from .time_scale import UTC

import numpy as np
import bisect
from math import isnan, exp, log
from datetime import datetime


class PlotItem(LogItem):

    # emitted when the style is updated
    style_updated = pyqtSignal()
    scale_updated = pyqtSignal()

    def __init__(
        self,
        size=QSizeF(400, 200),
        render_type=POINT_RENDERER,
        x_orientation=ORIENTATION_LEFT_TO_RIGHT,
        y_orientation=ORIENTATION_UPWARD,
        uncertainty_column=None,
        unit_factor=1.0,
        scale_type="linear",
        allow_mouse_translation=False,
        allow_wheel_zoom=False,
        symbology=None,
        parent_item=None,
    ):

        """
        Parameters
        ----------
        size: QSize
          Size of the item
        render_type: Literal[POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER]
          Type of renderer
        x_orientation: Literal[ORIENTATION_LEFT_TO_RIGHT, ORIENTATION_RIGHT_TO_LEFT]
        y_orientation: Literal[ORIENTATION_UPWARD, ORIENTATION_DOWNWARD]
        allow_mouse_translation: bool
          Allow the user to translate the item with the mouse (??)
        allow_wheel_zoom: bool
          Allow the user to zoom with the mouse wheel
        symbology: QDomDocument
          QGIS symbology to use for the renderer
        parent_item: QGraphicsItem
        """
        LogItem.__init__(self, parent_item)

        self.__item_size = size
        self.__data_rect = None
        self.__stacked_data = {}
        self.__delta = None
        self.__x_orientation = x_orientation
        self.__y_orientation = y_orientation
        self.__uncertainty_column = uncertainty_column
        self.__scale_type = scale_type
        self._log_factor = None
        self.__unit_factor = unit_factor
        self.__uom = None

        # origin point of the graph translation, if any
        self.__translation_orig = None

        self.__render_type = (
            render_type
        )  # type: Literal[POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER]

        self.__allow_mouse_translation = allow_mouse_translation
        self.__allow_wheel_zoom = allow_wheel_zoom

        self.__layer = None

        self.__default_renderers = [
            QgsFeatureRenderer.defaultRenderer(POINT_RENDERER),
            QgsFeatureRenderer.defaultRenderer(LINE_RENDERER),
            QgsFeatureRenderer.defaultRenderer(POLYGON_RENDERER),
        ]
        symbol = self.__default_renderers[1].symbol()
        symbol.setWidth(1.0)
        symbol = self.__default_renderers[0].symbol()
        symbol.setSize(5.0)
        symbol = self.__default_renderers[2].symbol()
        symbol.symbolLayers()[0].setStrokeWidth(1.0)

        if not symbology:
            self.__renderer = self.__default_renderers[self.__render_type]
        else:
            self.__renderer = QgsFeatureRenderer.load(
                symbology.documentElement(), QgsReadWriteContext()
            )

        # index of the current point to label
        self.__old_point_to_label = None
        self.__point_to_label = None

    def boundingRect(self):
        """ Return bounding rectangle

        :return: bounding rectangle
        :rtype: QRectF
        """
        return QRectF(0, 0, self.__item_size.width(), self.__item_size.height())

    def height(self):
        """ Return plot height

        :return: height
        :rtype: float
        """
        return self.__item_size.height()

    def set_height(self, height):
        """ Set plot height

        :param height: height
        :type height: float
        """
        self.__item_size.setHeight(height)

    def width(self):
        """ Return plot width

        :return: width
        :rtype: float
        """
        return self.__item_size.width()

    def set_width(self, width):
        """ Set plot width

        :param width: width
        :type width: float
        """
        self.__item_size.setWidth(width)

    def set_data_window(self, window):
        """ Set data_window

        :param window: QRectF
        :type windows: QRectF
        """
        self.__data_rect = window

    def min_depth(self):
        """ Return min depth value

        :return: min depth value
        :rtype: float
        """
        if self.__data_rect is None:
            return None
        return self.__data_rect.x()

    def max_depth(self):
        """ Return max depth value

        :return: max depth value
        :rtype: float
        """
        if self.__data_rect is None:
            return None
        return self.__data_rect.x() + self.__data_rect.width()

    def set_min_depth(self, min_depth):
        """ Set min depth value

        :param min_depth: min depth value
        :type min_depth: float
        """
        if self.__data_rect is not None:
            self.__data_rect.setX(min_depth)

    def set_max_depth(self, max_depth):
        """ Set max depth value

        :param max_depth: max depth value
        :type max_depth: float
        """
        if self.__data_rect is not None:
            w = max_depth - self.__data_rect.x()
            self.__data_rect.setWidth(w)

    def layer(self):
        """ Return layer

        :return: layer
        :rtype: QgsVectorLayer
        """
        return self.__layer

    def scale_type(self):
        """ Return scale type 

        :return: uncertainty column
        :rtype: string
        """
        return self.__scale_type

    def uncertainty_column(self):
        """ Return uncertainty column

        :return: uncertainty column
        :rtype: string
        """
        return self.__uncertainty_column

    def set_layer(self, layer):
        """ Set layer

        :param layer: layer
        :type layer: QgsVectorLayer
        """
        self.__layer = layer

    def data_window(self):
        """ Return data_window

        :return: data_window
        :rtype: QRectF
        """
        return self.__data_rect

    def set_data(self, x_values, y_values, ids, uncertainty_values, uom):
        """ Set data to display

        :param x_values: x values
        :type x_values: list
        :param y_values: y values
        :type y_values: list
        """
        self.__uom = uom
        self.__cumulative = isinstance(x_values, tuple)

        if self.__cumulative is False:
            self.__x_values = x_values
        else:
            self.__x_values = x_values[0]
            self.__lim_x_values = x_values[1]
        self.__y_values = y_values
        self.actualize_log_values()

        if len(ids) == 1 and len(ids) != len(self.__x_values):
            self.__ids = [ids[0] for x in self.__x_values]
        else:
            self.__ids = ids

        if len(self.__x_values) != len(self.__y_values):
            raise ValueError(
                self.tr("X and Y array has different length : ")
                + "{} != {}".format(len(self.__x_values), len(self.__y_values))
            )
        if self.__uncertainty_column:
            if len(self.__y_values) == len(uncertainty_values):
                self.__uncertainty_values = [
                    0 if u is None else u for u in uncertainty_values
                ]
            else:
                raise ValueError(
                    self.tr("Y and uncertainty values array has different length : ")
                    + "{} != {}".format(len(self.__y_values), len(uncertainty_values))
                )

        # Remove None values
        for i in reversed(range(len(self.__y_values))):
            if (
                self.__y_values[i] is None
                or self.__x_values[i] is None
                or isnan(self.__y_values[i])
                or isnan(self.__x_values[i])
            ):
                self.__y_values.pop(i)
                self.__log_y_values.pop(i)
                self.__x_values.pop(i)
                if self.__cumulative:
                    self.__cumulative.pop[i]
                self.__ids.pop(i)
                if self.__uncertainty_column:
                    self.__uncertainty_values.pop(i)
        if not self.__x_values:
            return

        # Initialize data rect to display all data
        # with a 20% buffer around Y values
        min_x = min(self.__x_values)
        max_x = max(self.__x_values)
        min_y = min(self.__y_values if self.__scale_type == "linear" else self.__log_y_values)
        max_y = max(self.__y_values if self.__scale_type == "linear" else self.__log_y_values)
        h = max_y - min_y
        if h == 0.0:
            h = 1.0
        min_y -= h * 0.1
        max_y += h * 0.1
        self.__data_rect = QRectF(min_x, min_y, max_x - min_x, max_y - min_y)

    def set_stacked_data(self, stacked_data):
        """ Import stacked data into the plot item"""
        self.__stacked_data = stacked_data
        for k, d in self.__stacked_data.items():
            if d.get_uom() != self.__uom:
                factor = unitConversionFactor(d.get_uom(), self.__uom)
                y_values = [y * factor for y in d.get_y_values()]
                d.set_y_values(y_values)
                d.set_uom(self.__uom)
                self.__stacked_data[k] = d

        # Prepare renderer for each plot
        self.__stacked_renderer = {}
        for k, d in self.__stacked_data.items():
            if isinstance(d, IntervalData):
                renderer = QgsFeatureRenderer.defaultRenderer(POLYGON_RENDERER)
            else:
                renderer = self.__renderer.clone()
            renderer.symbol().setColor(QColor(randint(0, 255), randint(0, 255), randint(0, 255)))
            self.__stacked_renderer[k] = renderer
        self.actualize_log_values()

    def actualize_log_values(self):
        """ Change y log values according to the entire set of data displayed """
        y_values = self.__y_values
        y_values = [y if y > 0 else 1e-32 for y in y_values]
        if self.__stacked_data:
            for k, v in self.__stacked_data.items():
                y_values += [y if y > 0 else 1e-32 for y in v.get_y_values()]

        self.__log_y_values = [
            exp(y / max(y_values) * (log(max(y_values)) - log(min(y_values))) + log(min(y_values)))
            for y in self.__y_values
        ]
        self.__stacked_log_y_values = {}
        logs_y_values = []
        if self.__stacked_data:
            for k, v in self.__stacked_data.items():
                self.__stacked_log_y_values[k] = [
                    exp(
                        y / max(y_values) * (log(max(y_values)) - log(min(y_values)))
                        + log(min(y_values))
                    )
                    for y in v.get_y_values()
                ]
                logs_y_values += self.__stacked_log_y_values[k]

    def get_renderer_info(self):
        """ Get all info of used renderers, needed to build symbol legend when a cell contains several plots

        :return: list of QgsRenderer
        :rtype: list
        :return list of QgsRenderer type (point, line, polygon)
        :rtype: list
        """
        renderers = [self.__renderer]
        renderers_type = [self.__render_type]
        if len(self.__stacked_data) > 0:
            for k, d in self.__stacked_data.items():
                symbology = d.get_symbology()
                render_type = d.get_symbology_type()
                if symbology is None:
                    renderer = self.__stacked_renderer[k]
                    if isinstance(d, IntervalData):
                        render_type = POLYGON_RENDERER
                    else:
                        render_type = POINT_RENDERER
                else:
                    renderer = QgsFeatureRenderer.load(
                        symbology.documentElement(), QgsReadWriteContext()
                    )
                renderers.append(renderer)
                renderers_type.append(render_type)
        return renderers, renderers_type

    def renderer(self):
        """ Return renderer

        :return: renderer
        :rtype: QgsFeatureRenderer
        """
        return self.__renderer

    def paint_data(
        self,
        painter,
        option,
        stacked_key=None,
        ids=None,
        x_values=None,
        y_values=None,
        uncertainty_values=None,
        symbology=None,
        render_type=None,
    ):
        """ Paint function for instantaneous and continuous data

        :return: rw, rh
        :return: int, int
        """
        if x_values is None and y_values is None:
            ids = self.__ids
            stacked_key = self.__layer.id()
            x_values = self.__x_values
            renderer = self.__renderer
            render_type = self.__render_type
            if self.__scale_type == "log":
                y_values_label = self.__y_values
                y_values = self.__log_y_values
            else:
                y_values_label = self.__y_values
                y_values = self.__y_values
            uncertainty_column = self.__uncertainty_column
            if uncertainty_column:
                uncertainty_values = self.__uncertainty_values
        else:
            if self.__scale_type == "log":
                y_values_label = y_values
                y_values = self.__stacked_log_y_values[stacked_key]
            else:
                y_values_label = y_values
            if symbology is None:
                renderer = self.__stacked_renderer[stacked_key]
                render_type = POINT_RENDERER
            else:
                renderer = QgsFeatureRenderer.load(
                    symbology.documentElement(), QgsReadWriteContext()
                )
            if len(ids) == 1 and len(ids) != len(x_values):
                ids = [ids[0] for x in x_values]
            else:
                ids = ids

        # Look for the first and last X values that fit our rendering rect
        if (
            self.__x_orientation == ORIENTATION_DOWNWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            imax_x = len(x_values) - bisect.bisect_left(
                sorted([-x for x in x_values]), self.__data_rect.x(), hi=len(x_values)
            )

            imin_x = len(x_values) - bisect.bisect_right(
                sorted([-x for x in x_values]), self.__data_rect.right(), hi=len(x_values)
            )
        else:
            imax_x = len(x_values) - bisect.bisect_left(
                sorted(x_values), self.__data_rect.x(), hi=len(x_values)
            )
            imin_x = len(x_values) - bisect.bisect_right(
                sorted(x_values), self.__data_rect.right(), hi=len(x_values)
            )

        # For lines and polygons, retain also one value before the min and one after the max
        # so that lines do not appear truncated
        # Do this only if we have at least one point to render within out rect
        if imin_x > 0 and imin_x < len(x_values) and x_values[imin_x] >= self.__data_rect.x():
            # FIXME add a test to avoid adding a point too "far away" ?
            imin_x -= 1
        if imax_x < len(x_values) - 1 and x_values[imax_x] <= self.__data_rect.right():
            # FIXME add a test to avoid adding a point too "far away" ?
            imax_x += 1

        x_values_slice = np.array(x_values[imin_x:imax_x])
        y_values_slice = np.array(y_values[imin_x:imax_x])
        if self.__uncertainty_column and len(uncertainty_values) > 0:
            uncertainty_slice = np.array(uncertainty_values[imin_x:imax_x])

        if len(x_values_slice) == 0:
            return

        # filter points that are not None (nan in numpy arrays)
        n_points = len(x_values_slice)
        if (
            self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
            and self.__y_orientation == ORIENTATION_UPWARD
        ):
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.width()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.width())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.height()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.height())
            xx = (x_values_slice - self.__data_rect.x()) * rw
            yy = (y_values_slice - self.__data_rect.y()) * rh
            if self.__uncertainty_column and len(uncertainty_slice) > 0:
                px = (x_values_slice - self.__data_rect.x()) * rw
                inf_values = np.where(
                    y_values_slice - uncertainty_slice >= self.__data_rect.y(),
                    y_values_slice - uncertainty_slice,
                    self.__data_rect.y(),
                )
                sup_values = np.where(
                    y_values_slice + uncertainty_slice
                    <= self.__data_rect.height() + self.__data_rect.y(),
                    y_values_slice + uncertainty_slice,
                    self.__data_rect.height() + self.__data_rect.y(),
                )
                py_inf = (
                    self.__item_size.height() - (inf_values - self.__data_rect.y()) * rh
                )
                py_sup = (
                    self.__item_size.height() - (sup_values - self.__data_rect.y()) * rh
                )
                for i, (x, inf, sup) in enumerate(zip(px, py_inf, py_sup)):
                    if (
                        not (
                            sup_values[i] <= self.__data_rect.y()
                            or inf_values[i] >= self.__data_rect.height() + self.__data_rect.y()
                        )
                        and sup_values[i] != inf_values[i]
                    ):
                        painter.drawLine(x, inf, x, sup)
                        painter.drawLine(x - 2, inf, x + 2, inf)
                        painter.drawLine(x - 2, sup, x + 2, sup)
        elif (
            self.__x_orientation == ORIENTATION_DOWNWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.height()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.height())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.width()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.width())
            xx = (y_values_slice - self.__data_rect.y()) * rh
            yy = (-x_values_slice - self.__data_rect.x()) * rw
            if self.__uncertainty_column:
                py = self.height() - (-x_values_slice - self.__data_rect.x()) * rw
                inf_values = np.where(
                    y_values_slice - uncertainty_slice >= self.__data_rect.y(),
                    y_values_slice - uncertainty_slice,
                    self.__data_rect.y(),
                )
                sup_values = np.where(
                    y_values_slice + uncertainty_slice
                    <= self.__data_rect.height() + self.__data_rect.y(),
                    y_values_slice + uncertainty_slice,
                    self.__data_rect.height() + self.__data_rect.y(),
                )
                px_inf = (inf_values - self.__data_rect.y()) * rh
                px_sup = (sup_values - self.__data_rect.y()) * rh
                for i, (y, inf, sup) in enumerate(zip(py, px_inf, px_sup)):
                    if not (
                        sup_values[i] <= self.__data_rect.y()
                        or inf_values[i]
                        >= self.__data_rect.height() + self.__data_rect.y()
                    ):
                        painter.drawLine(inf, y, sup, y)
                        painter.drawLine(inf, y - 2, inf, y + 2)
                        painter.drawLine(sup, y - 2, sup, y + 2)

        elif (
            self.__x_orientation == ORIENTATION_UPWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.height()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.height())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.width()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.width())
            xx = (y_values_slice - self.__data_rect.y()) * rh
            yy = (x_values_slice - self.__data_rect.x()) * rw
            if self.__uncertainty_column and len(uncertainty_slice) > 0:
                py = self.height() - (x_values_slice - self.__data_rect.x()) * rw
                inf_values = np.where(
                    y_values_slice - uncertainty_slice >= self.__data_rect.y(),
                    y_values_slice - uncertainty_slice,
                    self.__data_rect.y(),
                )
                sup_values = np.where(
                    y_values_slice + uncertainty_slice
                    <= self.__data_rect.height() + self.__data_rect.y(),
                    y_values_slice + uncertainty_slice,
                    self.__data_rect.height() + self.__data_rect.y(),
                )
                px_inf = (inf_values - self.__data_rect.y()) * rh
                px_sup = (sup_values - self.__data_rect.y()) * rh
                for i, (y, inf, sup) in enumerate(zip(py, px_inf, px_sup)):
                    if not (
                        sup_values[i] <= self.__data_rect.y()
                        or inf_values[i]
                        >= self.__data_rect.height() + self.__data_rect.y()
                    ):
                        painter.drawLine(inf, y, sup, y)
                        painter.drawLine(inf, y - 2, inf, y + 2)
                        painter.drawLine(sup, y - 2, sup, y + 2)

        self.__rw = rw
        self.__rh = rh

        if render_type == LINE_RENDERER:
            # WKB structure of a linestring
            #
            #   01 : endianness
            #   02 00 00 00 : WKB type (linestring)
            #   nn nn nn nn : number of points (int32)
            # Then, for each point:
            #   xx xx xx xx xx xx xx xx : X coordinate (float64)
            #   yy yy yy yy yy yy yy yy : Y coordinate (float64)

            wkb = np.zeros(8 * 2 * n_points + 9, dtype="uint8")
            wkb[0] = 1  # wkb endianness
            wkb[1] = 2  # linestring
            size_view = np.ndarray(buffer=wkb, dtype="int32", offset=5, shape=(1,))
            size_view[0] = n_points
            coords_view = np.ndarray(
                buffer=wkb, dtype="float64", offset=9, shape=(n_points, 2)
            )
            coords_view[:, 0] = xx[:]
            coords_view[:, 1] = yy[:]
        elif render_type == POINT_RENDERER:
            # WKB structure of a multipoint
            #
            #   01 : endianness
            #   04 00 00 00 : WKB type (multipoint)
            #   nn nn nn nn : number of points (int32)
            # Then, for each point:
            #   01 : endianness
            #   01 00 00 00 : WKB type (point)
            #   xx xx xx xx xx xx xx xx : X coordinate (float64)
            #   yy yy yy yy yy yy yy yy : Y coordinate (float64)

            wkb = np.zeros((8 * 2 + 5) * n_points + 9, dtype="uint8")
            wkb[0] = 1  # wkb endianness
            wkb[1] = 4  # multipoint
            size_view = np.ndarray(buffer=wkb, dtype="int32", offset=5, shape=(1,))
            size_view[0] = n_points
            coords_view = np.ndarray(
                buffer=wkb,
                dtype="float64",
                offset=9 + 5,
                shape=(n_points, 2),
                strides=(16 + 5, 8),
            )
            coords_view[:, 0] = xx[:]
            coords_view[:, 1] = yy[:]
            # header of each point
            h_view = np.ndarray(
                buffer=wkb,
                dtype="uint8",
                offset=9,
                shape=(n_points, 2),
                strides=(16 + 5, 1),
            )
            h_view[:, 0] = 1  # endianness
            h_view[:, 1] = 1  # point
        elif render_type == POLYGON_RENDERER:
            # WKB structure of a polygon
            #
            #   01 : endianness
            #   03 00 00 00 : WKB type (polygon)
            #   01 00 00 00 : Number of rings (always 1 here)
            #   nn nn nn nn : number of points (int32)
            # Then, for each point:
            #   xx xx xx xx xx xx xx xx : X coordinate (float64)
            #   yy yy yy yy yy yy yy yy : Y coordinate (float64)
            #
            # We add two additional points to close the polygon

            wkb = np.zeros(8 * 2 * (n_points + 2) + 9 + 4, dtype="uint8")
            wkb[0] = 1  # wkb endianness
            wkb[1] = 3  # polygon
            wkb[5] = 1  # number of rings
            size_view = np.ndarray(buffer=wkb, dtype="int32", offset=9, shape=(1,))
            size_view[0] = n_points + 2
            coords_view = np.ndarray(
                buffer=wkb, dtype="float64", offset=9 + 4, shape=(n_points, 2)
            )
            coords_view[:, 0] = xx[:]
            coords_view[:, 1] = yy[:]
            # two extra points
            extra_coords = np.ndarray(
                buffer=wkb,
                dtype="float64",
                offset=8 * 2 * n_points + 9 + 4,
                shape=(2, 2),
            )
            if (
                self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                and self.__y_orientation == ORIENTATION_UPWARD
            ):
                extra_coords[0, 0] = coords_view[-1, 0]
                extra_coords[0, 1] = 0.0
                extra_coords[1, 0] = coords_view[0, 0]
                extra_coords[1, 1] = 0.0
            elif (
                self.__x_orientation == ORIENTATION_DOWNWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                extra_coords[0, 0] = 0.0
                extra_coords[0, 1] = coords_view[-1, 1]
                extra_coords[1, 0] = 0.0
                extra_coords[1, 1] = coords_view[0, 1]

        # build a geometry from the WKB
        # since numpy arrays have buffer protocol, sip is able to read it
        geom = QgsGeometry()
        geom.fromWkb(wkb.tobytes())

        painter.setClipRect(0, 0, self.__item_size.width(), self.__item_size.height())

        fields = QgsFields()
        # fields.append(QgsField("", QVariant.String))
        feature = QgsFeature(fields, 1)
        feature.setGeometry(geom)

        context = qgis_render_context(
            painter, self.__item_size.width(), self.__item_size.height()
        )
        context.setExtent(
            QgsRectangle(0, 1, self.__item_size.width(), self.__item_size.height())
        )

        renderer.startRender(context, fields)
        renderer.renderFeature(feature, context)
        renderer.stopRender(context)

        # ix_values = np.array(self.__displayed_x_values + x_values)
        # iy_values = np.array(self.__displayed_y_values + y_values)
        # iy_values_label = np.array(self.__displayed_true_y_values + y_values_label)
        # iids = np.array(self.__displayed_ids + ids)
        # ilayers = np.array(self.__displayed_from_layer + [stacked_key for i in y_values])

        # x_sort = ix_values.argsort()
        # self.__displayed_x_values = ix_values[x_sort]
        # self.__displayed_y_values = iy_values[x_sort]
        # self.__displayed_true_y_values = iy_values_label[x_sort]
        # self.__displayed_ids = iids[x_sort]
        # self.__displayed_from_layer = ilayers[x_sort]

        # zip(*[
        #             (x,y,t,fid,l) for x,y,t,fid,l in sorted(zip(self.__displayed_x_values + x_values,
        #                                                          self.__displayed_y_values + y_values,
        #                                                          self.__displayed_true_y_values + y_values_label,
        #                                                          self.__displayed_ids + ids,
        #                                                          self.__displayed_from_layer + [stacked_key for i in y_values],
        #                                                          ), key=lambda pair: pair[0])])

        # first test
        self.__displayed_y_values = [
            y
            for x, y in sorted(
                zip(self.__displayed_x_values + x_values, self.__displayed_y_values + y_values),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_true_y_values = [
            u
            for x, u in sorted(
                zip(
                    self.__displayed_x_values + x_values,
                    self.__displayed_true_y_values + y_values_label,
                ),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_ids = [
            u
            for x, u in sorted(
                zip(self.__displayed_x_values + x_values, self.__displayed_ids + ids),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_from_layer = [
            l
            for x, l in sorted(
                zip(
                    self.__displayed_x_values + x_values,
                    self.__displayed_from_layer + [stacked_key for i in y_values],
                ),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_x_values = [x for x in sorted(self.__displayed_x_values + x_values)]

        return rw, rh

    def paint_interval_data(
        self,
        painter,
        option,
        stacked_key=None,
        ids=None,
        min_x_values=None,
        max_x_values=None,
        y_values=None,
        uncertainty_values=None,
        symbology=None,
        render_type=None,
    ):
        """ Paint function for cumulative data

        :return: rw, rh
        :return: int, int
        """
        if self.__data_rect is None:
            return

        if min_x_values is None and max_x_values is None and y_values is None:
            ids = self.__ids
            stacked_key = self.__layer.id()
            min_x_values = self.__x_values
            max_x_values = self.__lim_x_values
            renderer = self.__renderer
            render_type = self.__render_type
            if self.__scale_type == "log":
                y_values_label = self.__y_values
                y_values = self.__log_y_values
            else:
                y_values_label = self.__y_values
                y_values = self.__y_values
            uncertainty_column = self.__uncertainty_column
            if uncertainty_column:
                uncertainty_values = self.__uncertainty_values
        else:
            if self.__scale_type == "log":
                y_values_label = y_values
                y_values = self.__stacked_log_y_values[stacked_key]
            else:
                y_values_label = y_values
            if symbology is None:
                renderer = self.__stacked_renderer[stacked_key]
                render_type = POLYGON_RENDERER
            else:
                renderer = QgsFeatureRenderer.load(
                    symbology.documentElement(), QgsReadWriteContext()
                )
                # To Do : add other renderer type

        context = qgis_render_context(painter, self.width(), self.height())
        context.setExtent(QgsRectangle(0, 0, self.width(), self.height()))
        fields = self.__layer.fields()

        context.expressionContext().setFields(fields)

        if (
            self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
            and self.__y_orientation == ORIENTATION_UPWARD
        ):
            rw = self.width() / self.__data_rect.width()
            rh = self.height() / self.__data_rect.height()
        elif (
            self.__x_orientation in [ORIENTATION_DOWNWARD, ORIENTATION_UPWARD]
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            rw = self.height() / self.__data_rect.width()
            rh = self.width() / self.__data_rect.height()

        renderer.startRender(context, fields)

        for i in range(len(y_values)):
            f = QgsFeature(self.__layer.fields())
            min_x, max_x = min_x_values[i], max_x_values[i]
            # avoid to paint bars outside the column
            value = (
                y_values[i]
                if y_values[i] <= self.__data_rect.height() + self.__data_rect.y()
                else self.__data_rect.height() + self.__data_rect.y()
            )
            value = value if value >= self.__data_rect.y() else self.__data_rect.y()
            yy = (value - self.__data_rect.top()) * rh
            if (
                self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                and self.__y_orientation == ORIENTATION_UPWARD
            ):
                min_xx = (min_x - self.__data_rect.left()) * rw
                max_xx = (max_x - self.__data_rect.left()) * rw
            elif (
                self.__x_orientation == ORIENTATION_DOWNWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                min_xx = self.height() - (min_x - self.__data_rect.left()) * rw
                max_xx = self.height() - (max_x - self.__data_rect.left()) * rw
            elif (
                self.__x_orientation == ORIENTATION_UPWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                min_xx = (min_x - self.__data_rect.left()) * rw
                max_xx = (max_x - self.__data_rect.left()) * rw

            if render_type == POLYGON_RENDERER:
                if (
                    self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                    and self.__y_orientation == ORIENTATION_UPWARD
                ):
                    geom = QgsGeometry.fromQPolygonF(
                        QPolygonF(QRectF(min_xx, 0, max_xx - min_xx, yy))
                    )
                elif (
                    self.__x_orientation == ORIENTATION_DOWNWARD
                    and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
                ):
                    geom = QgsGeometry.fromQPolygonF(
                        QPolygonF(QRectF(0, self.height() - max_xx, yy, max_xx - min_xx))
                    )
                elif (
                    self.__x_orientation == ORIENTATION_UPWARD
                    and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
                ):
                    geom = QgsGeometry.fromQPolygonF(
                        QPolygonF(QRectF(0, min_xx, yy, max_xx - min_xx))
                    )
            elif render_type == LINE_RENDERER:
                if (
                    self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                    and self.__y_orientation == ORIENTATION_UPWARD
                ):
                    geom = QgsGeometry.fromPolylineXY(
                        [QgsPointXY(min_xx, yy), QgsPointXY(max_xx, yy)]
                    )
                elif (
                    self.__x_orientation == ORIENTATION_DOWNWARD
                    and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
                ):
                    geom = QgsGeometry.fromPolylineXY(
                        [
                            QgsPointXY(yy, self.height() - max_xx),
                            QgsPointXY(yy, self.height() - min_xx),
                        ]
                    )
                elif (
                    self.__x_orientation == ORIENTATION_UPWARD
                    and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
                ):
                    geom = QgsGeometry.fromPolylineXY(
                        [QgsPointXY(yy, min_xx), QgsPointXY(yy, max_xx)]
                    )

            f.setGeometry(geom)
            renderer.renderFeature(f, context)

        renderer.stopRender(context)

        if self.__uncertainty_column:
            for i in range(len(y_values)):
                min_x, max_x = min_x_values[i], max_x_values[i]
                value = y_values[i]
                if (
                    self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                    and self.__y_orientation == ORIENTATION_UPWARD
                ):
                    if not (
                        value + self.__uncertainty_values[i] <= self.__data_rect.y()
                        or value - self.__uncertainty_values[i]
                        >= self.__data_rect.height() + self.__data_rect.y()
                    ):
                        px = (
                            ((min_x - self.__data_rect.x()) * rw)
                            + ((max_x - self.__data_rect.x())) * rw
                        ) / 2
                        inf_value = (
                            value - self.__uncertainty_values[i]
                            if value - self.__uncertainty_values[i] >= self.__data_rect.y()
                            else self.__data_rect.y()
                        )
                        sup_value = (
                            value + self.__uncertainty_values[i]
                            if value + self.__uncertainty_values[i]
                            <= self.__data_rect.height() + self.__data_rect.y()
                            else self.__data_rect.height() + self.__data_rect.y()
                        )
                        inf = self.height() - (inf_value - self.__data_rect.y()) * rh
                        sup = self.height() - (sup_value - self.__data_rect.y()) * rh
                        painter.drawLine(px, inf, px, sup)
                        if inf_value >= self.__data_rect.y():
                            painter.drawLine(px - 2, inf, px + 2, inf)
                        if sup_value <= self.__data_rect.height() + self.__data_rect.y():
                            painter.drawLine(px - 2, sup, px + 2, sup)
                elif (
                    self.__x_orientation == ORIENTATION_DOWNWARD
                    and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
                ):
                    if not (
                        value + self.__uncertainty_values[i] <= self.__data_rect.y()
                        or value - self.__uncertainty_values[i]
                        >= self.__data_rect.height() + self.__data_rect.y()
                    ):
                        py = (
                            (self.height() - (min_x - self.__data_rect.x()) * rw)
                            + (self.height() - (max_x - self.__data_rect.x()) * rw)
                        ) / 2
                        inf_value = (
                            value - self.__uncertainty_values[i]
                            if value - self.__uncertainty_values[i] >= self.__data_rect.y()
                            else self.__data_rect.y()
                        )
                        sup_value = (
                            value + self.__uncertainty_values[i]
                            if value + self.__uncertainty_values[i]
                            <= self.__data_rect.height() + self.__data_rect.y()
                            else self.__data_rect.height() + self.__data_rect.y()
                        )
                        inf = (inf_value - self.__data_rect.top()) * rh
                        sup = (sup_value - self.__data_rect.top()) * rh
                        painter.drawLine(inf, py, sup, py)
                        if inf_value >= self.__data_rect.y():
                            painter.drawLine(inf, py - 2, inf, py + 2)
                        if sup_value <= self.__data_rect.height() + self.__data_rect.y():
                            painter.drawLine(sup, py - 2, sup, py + 2)
                elif (
                    self.__x_orientation == ORIENTATION_UPWARD
                    and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
                ):
                    if not (
                        value + self.__uncertainty_values[i] <= self.__data_rect.y()
                        or value - self.__uncertainty_values[i]
                        >= self.__data_rect.height() + self.__data_rect.y()
                    ):
                        py = (
                            (self.height() - (min_x - self.__data_rect.x()) * rw)
                            + (self.height() - (max_x - self.__data_rect.x()) * rw)
                        ) / 2
                        inf_value = (
                            value - self.__uncertainty_values[i]
                            if value - self.__uncertainty_values[i] >= self.__data_rect.y()
                            else self.__data_rect.y()
                        )
                        sup_value = (
                            value + self.__uncertainty_values[i]
                            if value + self.__uncertainty_values[i]
                            <= self.__data_rect.height() + self.__data_rect.y()
                            else self.__data_rect.height() + self.__data_rect.y()
                        )
                        inf = (inf_value - self.__data_rect.top()) * rh
                        sup = (sup_value - self.__data_rect.top()) * rh
                        painter.drawLine(inf, py, sup, py)
                        if inf_value >= self.__data_rect.y():
                            painter.drawLine(inf, py - 2, inf, py + 2)
                        if sup_value <= self.__data_rect.height() + self.__data_rect.y():
                            painter.drawLine(sup, py - 2, sup, py + 2)

        x_values = [(x_min + x_max) / 2 for x_min, x_max in zip(min_x_values, max_x_values)]
        self.__displayed_y_values = [
            y
            for x, y in sorted(
                zip(self.__displayed_x_values + x_values, self.__displayed_y_values + y_values),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_true_y_values = [
            u
            for x, u in sorted(
                zip(
                    self.__displayed_x_values + x_values,
                    self.__displayed_true_y_values + y_values_label,
                ),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_ids = [
            u
            for x, u in sorted(
                zip(self.__displayed_x_values + x_values, self.__displayed_ids + ids),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_from_layer = [
            l
            for x, l in sorted(
                zip(
                    self.__displayed_x_values + x_values,
                    self.__displayed_from_layer + [stacked_key for i in y_values],
                ),
                key=lambda pair: pair[0],
            )
        ]
        self.__displayed_x_values = [x for x in sorted(self.__displayed_x_values + x_values)]

        return rw, rh

    def paint(self, painter, option, widget):
        """ Paint plot item, heritated from QGraphicsItem

        :param painter: QPainter
        :type painter: QPainter
        :param option: QStyleOptionGraphicsItem
        :type option: QStyleOptionGraphicsItem
        :param widget: QWidget
        :type widget: QWidget
        """
        self.__displayed_x_values = []
        self.__displayed_y_values = []
        self.__displayed_true_y_values = []
        self.__displayed_ids = []
        self.__displayed_from_layer = []

        self.draw_background(painter)
        if self.__data_rect is None:
            return

        if self.__cumulative is False:
            rw, rh = self.paint_data(painter, option)
        else:
            rw, rh = self.paint_interval_data(painter, option)
        if len(self.__stacked_data) != 0:
            for k, d in self.__stacked_data.items():
                if isinstance(d, FeatureData) or isinstance(d, LayerData):
                    self.paint_data(
                        painter,
                        option,
                        stacked_key=k,
                        ids=d.get_ids_values(),
                        x_values=d.get_x_values(),
                        y_values=d.get_y_values(),
                        uncertainty_values=d.get_uncertainty_values(),
                        symbology=d.get_symbology(),
                        render_type=d.get_symbology_type(),
                    )
                elif isinstance(d, IntervalData):
                    self.paint_interval_data(
                        painter,
                        option,
                        stacked_key=k,
                        ids=d.get_ids_values(),
                        min_x_values=d.get_min_x_values(),
                        max_x_values=d.get_max_x_values(),
                        y_values=d.get_y_values(),
                        uncertainty_values=d.get_uncertainty_values(),
                        symbology=d.get_symbology(),
                        render_type=d.get_symbology_type(),
                    )

        painter.setPen(QColor(0, 0, 0))

        if self.__point_to_label is not None:
            i = self.__point_to_label
            x, y = self.__displayed_x_values[i], self.__displayed_y_values[i]
            if (
                self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                and self.__y_orientation == ORIENTATION_UPWARD
            ):
                px = (x - self.__data_rect.x()) * rw
                py = self.__item_size.height() - (y - self.__data_rect.y()) * rh
            elif (
                self.__x_orientation == ORIENTATION_DOWNWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                px = (y - self.__data_rect.y()) * rh
                py = self.height() - (-x - self.__data_rect.x()) * rw
            elif (
                self.__x_orientation == ORIENTATION_UPWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                px = (y - self.__data_rect.y()) * rh
                py = self.height() - (x - self.__data_rect.x()) * rw
            painter.drawLine(px - 5, py, px + 5, py)
            painter.drawLine(px, py - 5, px, py + 5)

    def mouseMoveEvent(self, event):
        """ On mouse move event

        :param event: event
        :type event: QEvent
        """
        if not self.__displayed_x_values:
            return

        pos = event.scenePos()
        if (
            self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
            and self.__y_orientation == ORIENTATION_UPWARD
        ):
            xx = (
                pos.x() - self.pos().x()
            ) / self.width() * self.__data_rect.width() + self.__data_rect.x()
            yy = (
                self.height() - (pos.y() - self.pos().y())
            ) / self.height() * self.__data_rect.height() + self.__data_rect.y()

            n_sup = bisect.bisect_right(
                self.__displayed_x_values, xx, hi=len(self.__displayed_x_values) - 1
            )
            n_inf = bisect.bisect_left(
                self.__displayed_x_values,
                self.__displayed_x_values[n_sup - 1],
                hi=len(self.__displayed_x_values) - 1,
            )

        elif (
            self.__x_orientation == ORIENTATION_DOWNWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            xx = -(
                self.__data_rect.width()
                - (pos.y() - self.pos().y()) / self.height() * self.__data_rect.width()
                + self.__data_rect.x()
            )
            yy = (
                (pos.x() - self.pos().x())
            ) / self.width() * self.__data_rect.height() + self.__data_rect.y()
            n_sup = bisect.bisect_right(
                self.__displayed_x_values, xx, hi=len(self.__displayed_x_values) - 1
            )
            n_inf = bisect.bisect_left(
                self.__displayed_x_values,
                self.__displayed_x_values[n_sup - 1],
                hi=len(self.__displayed_x_values) - 1,
            )
        elif (
            self.__x_orientation == ORIENTATION_UPWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            xx = (
                self.__data_rect.width()
                - (pos.y() - self.pos().y()) / self.height() * self.__data_rect.width()
                + self.__data_rect.x()
            )
            yy = (
                (pos.x() - self.pos().x())
            ) / self.width() * self.__data_rect.height() + self.__data_rect.y()
            n_sup = bisect.bisect_right(
                self.__displayed_x_values, xx, hi=len(self.__displayed_x_values) - 1
            )
            n_inf = bisect.bisect_left(
                self.__displayed_x_values,
                self.__displayed_x_values[n_sup - 1],
                hi=len(self.__displayed_x_values) - 1,
            )
        if n_sup - n_inf <= 1:
            ind = n_inf
        else:
            y_values = sorted(self.__displayed_y_values[n_inf:n_sup])
            i = bisect.bisect_right(y_values, yy, hi=len(y_values) - 1) - 1
            i = i + 1 if (abs(y_values[i + 1] - yy) > abs(y_values[i] - yy)) else i
            if i == -1:
                i = len(y_values) - 1
            ind = n_inf + i

        if ind >= 0 and ind <= len(self.__displayed_x_values) - 2:
            # switch the attached point when we are between two points
            if (
                self.__x_orientation != ORIENTATION_LEFT_TO_RIGHT
                and ind > 0
                and n_sup - n_inf <= 1
                and abs(self.__displayed_x_values[ind + 1] - xx)
                < abs(self.__displayed_x_values[ind] - xx)
            ):
                ind += 1
            elif (
                self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                and ind > 0
                and n_sup - n_inf <= 1
                and abs(self.__displayed_x_values[ind + 1] - xx)
                < abs(self.__displayed_x_values[ind] - xx)
            ):
                ind += 1
            self.__point_to_label = ind
        else:
            self.__point_to_label = None
        if self.__point_to_label != self.__old_point_to_label:
            self.update()
        if self.__point_to_label is not None:
            x, y = (
                self.__displayed_x_values[self.__point_to_label],
                self.__displayed_true_y_values[self.__point_to_label],
            )
            if (
                self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                and self.__y_orientation == ORIENTATION_UPWARD
            ):
                dt = datetime.fromtimestamp(x, UTC())
                txt = "Time: {} Value: {}".format(
                    dt.strftime("%x %X"), y * self.__unit_factor
                )
            elif (
                self.__x_orientation == ORIENTATION_DOWNWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                txt = "Depth: {} Value: {}".format(x, y * self.__unit_factor)
            elif (
                self.__x_orientation == ORIENTATION_UPWARD
                and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
            ):
                txt = "Elevation: {} Value: {}".format(x, y * self.__unit_factor)
            self.tooltipRequested.emit(txt)

        self.__old_point_to_label = self.__point_to_label

    def form(self, pos):
        """ Open feature form

        :param pos: cursor position
        :type pos: QPoint
        """
        # Get x and y value from cursor position
        if (
            self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
            and self.__y_orientation == ORIENTATION_UPWARD
        ):
            xx = (
                pos.x() - self.pos().x()
            ) / self.width() * self.__data_rect.width() + self.__data_rect.x()
            yy = (
                self.height() - (pos.y() - self.pos().y())
            ) / self.height() * self.__data_rect.height() + self.__data_rect.y()
            n_sup = bisect.bisect_right(
                self.__displayed_x_values, xx, hi=len(self.__displayed_x_values) - 1
            )
            n_inf = bisect.bisect_left(
                self.__displayed_x_values,
                self.__displayed_x_values[n_sup - 1],
                hi=len(self.__displayed_x_values) - 1,
            )
        elif (
            self.__x_orientation == ORIENTATION_DOWNWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            xx = -(
                self.__data_rect.width()
                - (pos.y() - self.pos().y()) / self.height() * self.__data_rect.width()
                + self.__data_rect.x()
            )
            yy = (
                pos.x() - self.pos().x()
            ) / self.width() * self.__data_rect.height() + self.__data_rect.y()
            n_sup = bisect.bisect_right(
                self.__displayed_x_values, xx, hi=len(self.__displayed_x_values) - 1
            )
            n_inf = bisect.bisect_left(
                self.__displayed_x_values,
                self.__displayed_x_values[n_sup - 1],
                hi=len(self.__displayed_x_values) - 1,
            )
        elif (
            self.__x_orientation == ORIENTATION_UPWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            xx = (
                self.__data_rect.width()
                - (pos.y() - self.pos().y()) / self.height() * self.__data_rect.width()
                + self.__data_rect.x()
            )
            yy = (
                pos.x() - self.pos().x()
            ) / self.width() * self.__data_rect.height() + self.__data_rect.y()
            n_sup = bisect.bisect_right(
                sorted(self.__displayed_x_values), xx, hi=len(self.__displayed_x_values) - 1
            )
            n_inf = bisect.bisect_left(
                self.__displayed_x_values,
                self.__displayed_x_values[n_sup - 1],
                hi=len(self.__displayed_x_values) - 1,
            )

        if (n_inf >= len(self.__displayed_x_values)) & (n_sup >= len(self.__displayed_x_values)):
            print(self.tr("Wrong bisect output, the click is probably outside the data area."))
            return False

        if n_sup - n_inf <= 1:
            if (
                self.__x_orientation != ORIENTATION_LEFT_TO_RIGHT
                and n_inf > 0
                and (xx - self.__displayed_x_values[n_inf - 1])
                < (self.__displayed_x_values[n_inf] - xx)
            ):
                n_inf -= 1
            if (
                self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
                and n_inf > 0
                and xx
                > (self.__displayed_x_values[n_inf - 1] + self.__displayed_x_values[n_inf]) / 2
            ):
                n_inf -= 1
            ind = n_inf
            if len(self.__ids) == 1:
                # continuous data
                feature_id = self.__displayed_ids[0]
            else:
                feature_id = self.__displayed_ids[ind]
        else:
            y_values = sorted(self.__displayed_y_values[n_inf:n_sup])
            i = bisect.bisect_right(y_values, yy, hi=len(y_values) - 1) - 1
            i = i + 1 if (abs(y_values[i + 1] - yy) > abs(y_values[i] - yy)) else i
            if i == -1:
                i = len(y_values) - 1
            ind = n_inf + i

        mx = self.__displayed_x_values[ind]
        my = self.__displayed_y_values[ind]

        if (
            self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
            and self.__y_orientation == ORIENTATION_UPWARD
        ):
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.width()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.width())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.height()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.height())
        elif (
            self.__x_orientation in [ORIENTATION_DOWNWARD, ORIENTATION_UPWARD]
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.height()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.height())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.width()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.width())

        if (
            self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT
            and self.__y_orientation == ORIENTATION_UPWARD
        ):
            px = (mx - self.__data_rect.x()) * rw
            py = self.__item_size.height() - (my - self.__data_rect.y()) * rh
        elif (
            self.__x_orientation == ORIENTATION_DOWNWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            px = (my - self.__data_rect.y()) * rh
            py = self.height() - (-mx - self.__data_rect.x()) * rw
        elif (
            self.__x_orientation == ORIENTATION_UPWARD
            and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT
        ):
            px = (my - self.__data_rect.y()) * rh
            py = self.height() - (mx - self.__data_rect.x()) * rw

        # If the click is close to a point, open a feature form
        # Kind of snapping help
        if (
            -5 < (px - (pos.x() - self.pos().x())) < 5
            and -5 < (py - (pos.y() - self.pos().y())) < 5
        ):
            layer = QgsProject.instance().mapLayer(self.__displayed_from_layer[ind])
            feature = next(
                layer.getFeatures(QgsFeatureRequest().setFilterFid(self.__displayed_ids[ind]))
            )
            iface.openFeatureForm(layer, feature, 0, 0)
            return True
        else:
            return False

    def edit_style(self):
        """ Open style dialog """
        from qgis.gui import QgsSingleSymbolRendererWidget
        from qgis.core import QgsStyle

        style = QgsStyle()
        sw = QStackedWidget()
        for i in range(3):
            if self.__renderer and i == self.__render_type:
                w = QgsSingleSymbolRendererWidget(self.__layer, style, self.__renderer)
            else:
                w = QgsSingleSymbolRendererWidget(
                    self.__layer, style, self.__default_renderers[i]
                )
            sw.addWidget(w)

        combo = QComboBox()
        combo.addItem("Points")
        combo.addItem("Line")
        combo.addItem("Polygon")

        combo.currentIndexChanged[int].connect(sw.setCurrentIndex)
        combo.setCurrentIndex(self.__render_type)

        dlg = QDialog()

        vbox = QVBoxLayout()

        btn = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        btn.accepted.connect(dlg.accept)
        btn.rejected.connect(dlg.reject)

        vbox.addWidget(combo)
        vbox.addWidget(sw)
        vbox.addWidget(btn)

        dlg.setLayout(vbox)
        dlg.resize(800, 600)

        r = dlg.exec_()

        if r == QDialog.Accepted:
            self.__render_type = combo.currentIndex()
            self.__renderer = sw.currentWidget().renderer().clone()
            self.update()
            self.style_updated.emit()

    def qgis_style(self):
        """Returns the current style, as a QDomDocument"""
        from PyQt5.QtXml import QDomDocument
        from qgis.core import QgsReadWriteContext

        doc = QDomDocument()
        elt = self.__renderer.save(doc, QgsReadWriteContext())
        doc.appendChild(elt)
        return (doc, self.__render_type)
