# -*- coding: iso-8859-1 -*-
from qgis.core import (QgsCoordinateTransform, QgsExpression, QgsFeature, QgsFeatureRequest, QgsField, QgsFields, QgsGeometry, QgsPageSizeRegistry, QgsProject, QgsVectorLayer)

from qgis.PyQt.QtCore import (QAbstractTableModel, QDateTime, QEventLoop, QMimeData, QObject, QPoint, QPointF, QPropertyAnimation, QRect, QRectF, QSize, Qt,
                              QTime, QTimer, QUrl, pyqtSignal, pyqtSlot, pyqtProperty)
from qgis.PyQt.QtGui import (QBrush, QColor, QFont, QIcon, QImage, QKeySequence, QPageSize, QPainter,  QPalette, QPen, QPixmap, QStandardItem, QStandardItemModel)
from qgis.PyQt.QtWidgets import (QAbstractItemView, QAction, QApplication, QAbstractSpinBox, QApplication, QButtonGroup, QCheckBox, QComboBox, QDateTimeEdit, QDialog, QDockWidget,
                                 QFileDialog, QFrame, QGridLayout, QGroupBox, QHeaderView, QHBoxLayout, QInputDialog, QLabel, QLayout, QLCDNumber, QLineEdit, QListView,
                                 QMdiSubWindow, QMenu, QMessageBox, QProgressBar, QPushButton, QRadioButton, QSizePolicy, QSlider, QSpinBox, QStyledItemDelegate, QTableView,
                                 QTableWidget, QTableWidgetItem, QTextBrowser, QTextEdit, QToolBar, QToolButton, QVBoxLayout, QWidget, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem)

from qgis.PyQt.QtPrintSupport import (QPrintPreviewDialog, QPrinter)
from qgis.gui import (QgsExpressionBuilderDialog, QgsMapCanvasItem, QgsMessageBar)
from qgis.core import (QgsExpression, QgsExpressionContext, QgsExpressionContextScope, Qgis, QgsPoint, QgsPointXY)

from qgis.PyQt.QtWebKitWidgets import (QWebInspector, QWebPage, QWebView)
from qgis.PyQt.QtWebKit import QWebSettings

#NEW 4.1.5
from rdigc.utils import (bufferPlus, changeButtonIcon, convertUnits, dicoHasKey, extrudeDiff, fixeLabelsFileDialog,
                         getAltiPoint, getExtension, getHarmonicMean, getLitteralDate, getMean, getNivVigilance,
                         getQuartiles, getCloudCover, getRainForce, getStDev, getThemeIcon, getTransformer, getWindForce,
                         getWindOrientation, isInfNumber, jsonDateToStrDate, jsonToMatplotlibDate, lissage, openFile, seriesUpSeuils,
                         setImgPath, setObjSizes, testSeuilVisibility, utc_to_local, windowIcon)

import os, glob, shutil
import os.path
from os.path import (dirname, exists, join)
from os import remove as removeFile

from matplotlib.figure import Figure as pltFigure
import matplotlib.pyplot as plt

from decimal import Decimal
import matplotlib.image as mpimg
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from mpl_toolkits.mplot3d import axes3d, Axes3D
from matplotlib.offsetbox import (TextArea, DrawingArea, OffsetImage,  AnnotationBbox)
from matplotlib.cbook import get_sample_data

import locale
import csv
import statistics
import json

from qgis import processing
from processing.core.Processing import Processing
from qgis.core import (QgsProcessingFeedback, QgsProcessingFeatureSourceDefinition)

from datetime import datetime as thedatetime
from datetime import timedelta

from qgis.PyQt.QtNetwork import (QNetworkAccessManager, QNetworkDiskCache, QNetworkReply, QNetworkRequest)
from scipy.stats import linregress

import ast
import pytz
import numpy as np
import random

from webbrowser import open as webbrowseropen         
from os import walk
from os.path import (basename, exists, splitext)
from math import (ceil, floor, sqrt)

#NEW 4.1.5 - JSCANVAS
from rdigc.graphmaker import *
from rdigc.params import (APIHUBEAU, APIMETEO, APIMODELMETEOITEMS, COMMENTOBSERVATIONS, COMMENTSSEUILRDI, HTMLGRAPHICSJS, HTMLSTATISTICS,
                          HTMLSTYLESBULLETIN, HTMLSTYLESMETEO, HTMLSTYLESCANVAS, HTMLSTYLESSTATISTICS,
                          IMAGESFORBULLETIN, INFORMATIONSSTYLES, JSCANVAS, NODEJSCANVAS, STYLECOMBOBOX, STYLELABEL, STYLELABELERROR, TIMEZONE)
#NEW 4.1.5
from rdigc.utils import (getContextMenuSelection, getLayerByName, invertSelItems, selAllItems, unSelAllItems)
from statistics import mean

from pathlib import Path as pathLibPath

#NEW 4.1.0
def cleanHtmlGraphics(lines):
    newHtml = ""
    if type(lines) != list : lines = lines.split("\n")
    for line in lines :
        if line.find("<div class=\"chartjs-size-monitor\">") > -1 :
           toKill = "<div class=\"chartjs-size-monitor\"><div class=\"chartjs-size-monitor-expand\"><div class=\"\"></div></div><div class=\"chartjs-size-monitor-shrink\"><div class=\"\"></div></div></div>"
           newHtml+= line.replace(toKill, "")
           
        elif line.find("name=\"echo\"") > -1 :
              posd = line.find("line-chart-")+len("line-chart-")
              posf = line.find("\"", posd)
              id =  line[posd:posf]
              newHtml+= "<canvas id=\"line-chart-%s\" name=\"echo\" style=\"width: 779px; height: 389px; display: block;\" width=\"1168\" height=\"583\" class=\"chartjs-render-monitor\"></canvas>" % id
        else : newHtml+= line

    if newHtml == "" : newHtml = html 
        
    return newHtml 

def bufferPlus(inputLayer, distance, output, before_processing_reproject_to_epsg_number=None):
    params = {'INPUT': inputLayer, 
              'DISTANCE': distance,
              'END_CAP_STYLE': 2 , # - 0: Round - 1: Flat - 2: Square
              'JOIN_STYLE' : 0, # - 0: Round - 1: Angle droit - 2: Oblique
              "SEGMENTS": 5, 
              'MITER_LIMIT' : 2, 
              'DISSOLVE': False,
              'SEPARATE_DISJOINT': False,
              'OUTPUT': output}
    feedback = QgsProcessingFeedback()
    alg_name = 'native:buffer'
    result = processing.run(alg_name, params, feedback=feedback)
    return result

def blocGraph():
    info = """
            <center>
            <details>
            <summary class='graphTheme'><table>
            <tr>
 	       <td><img src="file:///$TYPEGRAPH" width='32px' height='32px' title="Graphique $TITLEGRAPH"></td>
 	       <td class="titleTheme"><u>Graphique $TITLEGRAPH</u> :</td>
 	    </tr>
 	    </table></summary>   
	    <img class="post-content-wrapper" src="file:///$PLOTGRAPH" width='100%' height='100%'>
	    </details>
	    </center>
            <br><br>"""            
    return info

def clamp(x): return max(0, min(x, 255))

#COMMENTS FOR STATUT
def getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, unitInfos, typeInfos):
    bgColor, color, commentStatut, iAlarm, imgStatut, keySeuil = getNivVigilance(stationDicoSeuils, lastObs, typeInfos, unitInfos)
    return commentStatut, imgStatut, commentStatut.replace("<br>", "\n")

#COMMENTS FOR TREND
def getCommentsForTrend(coeffReg, isTextual):
    dictLegend = COMMENTOBSERVATIONS

    racComment = "3 dernières heures"
    comments = "Tendance des %s : " % racComment if isTextual else ""
    symbol, titleImg2, txtSymbol = "", "", ""

    if not coeffReg : return "Tendance non déterminée pour les %s." % racComment, "<b>?</b>", "%s (%s)" % (dictLegend["e3"], racComment)

    if coeffReg >= -0.005 and coeffReg <= 0.005 : symbol, titleImg2, txtSymbol = "&#8608;", "%s (%s)" % (dictLegend["e0"], racComment), dictLegend["e0"]
    elif coeffReg > -0.005  :                     symbol, titleImg2, txtSymbol = "&#8608;", "%s (%s)" % (dictLegend["e1"], racComment), dictLegend["e1"]
    else :                                        symbol, titleImg2, txtSymbol = "&#8599;", "%s (%s)" % (dictLegend["e2"], racComment), dictLegend["e2"]

    comments = "<b>%s</b> %s<br>\n" % (symbol, txtSymbol) if comments == "" else "%s<b>%s</b> %s<br>\n" % (comments, symbol, txtSymbol)

    return comments, symbol, titleImg2

def correctPath(path):
    path = "%s" % (path)
    a = len(path)
    letter = "/"
    b = path.rfind(letter, 0, a)    
    return ("%s/" % (path)) if a != b else path

def getListDocForMenu(obj, menu, idStation, pathDoc):
    iconUrlWeb = QIcon(getThemeIcon("urlweb.png"))
    listDocAss = []    
    
    for dirname, dirnames, filenames in walk(pathDoc):
        for fileName in filenames:
            if fileName.startswith(idStation) :

                name = "%s" % (basename(fileName))
                extension = getExtension(fileName)

                extension, icon = getExtension(fileName), None
                if extension.startswith(".") :
                   extension = extension[1:]
                   icon = QIcon(getThemeIcon("%s.png" % extension)) 
                
                if menu :
                    action = QAction(icon, name, obj) if icon else QAction(iconUrlWeb, name, obj)
                    action.setWhatsThis("%s/%s" % (dirname, fileName)) 
                    action.triggered.connect(obj.parent.openUrl)
                    menu.addAction(action)
                else :
                    listDocAss.append("%s/%s" % (dirname.replace("\\", "/").replace("//", "/"), fileName))

    if not menu : return listDocAss


class logConsole(QMdiSubWindow):
    def __init__(self, parent, button, width, height, namePlugin):

        super(logConsole, self).__init__()
        self.setObjectName("logConsole")
        self.setWindowTitle("Console journal")
        self.parent = parent
        self.button = button
        self.namePlugin = namePlugin
        self.setWindowOpacity(0.75)
        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
        self.w = width
        self.h = height
        self.setFixedSize(self.w, self.h)
        self.resize(self.w, self.h)

        self.setStyleSheet("QMdiSubWindow {color: black; background-color:white; border:1px; border-width: 0px;}")
        windowIcon(self, "logconsole_off.png")
    
    def setupUi(self):
        self.textEdit = QTextEdit(self)
        self.textEdit.setStyleSheet("QTextEdit {font: 14px 'Arial'; color: black; background: #CCC;}")
        self.textEdit.setHtml("<body {font-family: Arial, sans-serif; font-size: 18px; margin: 0; display: grid;}>")
        self.textEdit.setReadOnly(True) #False

        self.copyTextToClipBoardButton = funnyPushButton(self)
        self.copyTextToClipBoardButton.initPushButton(24, 24, self.width()-180,  1, "copyTextToClipBoardButton", "", "Copier au format brut vers le presse papier", True, getThemeIcon("copytoclipboard.png"), 24, 24, True, False)

        self.copyToClipBoardButton = funnyPushButton(self)
        self.copyToClipBoardButton.initPushButton(24, 24, self.width()-140,  1, "copyToClipBoardButton", "", "Copier au format HTML vers le presse papier pour des applicatifs tiers", True, getThemeIcon("copyhtml.png"), 24, 24, True, False)

        self.addButton = funnyPushButton(self)
        self.addButton.initPushButton(24, 24, self.width()-110,  1, "addButton", "", "Ajouter une information", True, getThemeIcon("add.png"), 24, 24, True, False)
        
        self.printButton = funnyPushButton(self)
        self.printButton.initPushButton(24, 24, self.width()-80, 1, "printButton", "", "Imprimer le journal", True, getThemeIcon("printer.png"), 24, 24, True, False)

        self.cleanButton = funnyPushButton(self)
        self.cleanButton.initPushButton(24, 24, self.width()-40, 1, "cleanButton", "", "Vider le journal", True, getThemeIcon("garbage.png"), 24, 24, True, False)

        self.copyTextToClipBoardButton.clicked.connect(self.copyText)
        self.copyToClipBoardButton.clicked.connect(self.copyHtml)
        self.addButton.clicked.connect(self.addAnnotation)
        self.printButton.clicked.connect(self.printConsole)
        self.cleanButton.clicked.connect(self.cleanConsole)
       
    def addAnnotation(self) :
        text, ok = QInputDialog().getText(self, "Annotation pour le journal", "Texte à ajouter ...", QLineEdit.Normal)
        if not ok: return
        self.setAnnontation(text, False)

    def setAnnontation(self, text, isHtml):     
        html = self.textEdit.toHtml()
        if isHtml == False : text = "<span style='font: 18px Arial;'>%s</span>" % text
        self.textEdit.setHtml(html+text+"<br>")

    def copyText(self):
        text = self.textEdit.toPlainText()
        QApplication.clipboard().setText(text)

   
    def copyHtml(self):
        html = self.textEdit.toHtml()
        reportRichTextMime = QMimeData()
        reportRichTextMime.setHtml(html)
        QApplication.clipboard().setMimeData(reportRichTextMime)


    def printConsole(self):
        printer = QPrinter(); printer.setPageSize(QPrinter.A4); printer.setOrientation(QPrinter.Portrait);
        printer.setPageMargins(5, 5, 5, 5, QPrinter.Millimeter); printer.setOutputFormat(QPrinter.NativeFormat);
        orientation = QPrinter.Portrait
        dlgPrint = printerPreview(self, self.textEdit, "%s : Imprimer le journal" % self.namePlugin, orientation, "printer.png")

    
    def cleanConsole(self): self.textEdit.setHtml("<body {font-family: Arial, sans-serif; font-size: 14px; margin: 0; display: grid;}>")      
        
    def resizeEvent(self, event) :
        self.textEdit.setGeometry(5, 25, self.w-10, self.h-30)

    def closeEvent(self, event):
        self.button.setIcon(QIcon(getThemeIcon("logconsole_off.png")))

	
class CustomStyleDelegate(QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super(CustomStyleDelegate, self).initStyleOption(option, index)
        brush = QColor("black")
        option.backgroundBrush = QColor(235, 235, 235)
        option.palette.setBrush(QPalette.Text, brush)

    def paint(self, painter, option, index):
        super(CustomStyleDelegate, self).paint(painter, option, index)

        margins = 0
        border_color = QColor(235, 235, 235)
        painter.save()
        pen = QPen()
        pen.setColor(border_color)
        pen.setWidth(margins)
        painter.setPen(pen)
        r = QRect(option.rect).adjusted(0, 0, -margins, -margins)
        painter.drawRect(r)
        painter.restore()

      
class toolBarSnifferWidget(QDockWidget):
      def  __init__(self, mainWindow, canvas, plugin):
         super(toolBarSnifferWidget, self).__init__()
         self.setWindowTitle("sniffer")
         self.minWidth, self.minHeight = 1, 1
         setObjSizes(self, self.minWidth, self.minHeight)
         self.setWindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint)
         self.setWindowOpacity(0.0)
         self.mainWindow = mainWindow
         self.canvas = canvas
         self.plugin = plugin
         self.isClosed = False
         self.setGeometry(0, 0, 1, 1)
         self.setVisible(False)
         self.hide()
         self.mainWindow.installEventFilter(self)

      def eventFilter(self, obj, event):
          if obj is self.mainWindow:
             if event.type() == event.Resize: self.setVisible(True)  ; self.resizeObjects()
             elif event.type() == event.Move : self.setVisible(True)  ; self.resizeObjects()
             elif event.type() == event.WindowStateChange :
                 if event.oldState() and (Qt.WindowMinimized or Qt.WindowMaximized) : self.setVisible(True)  ; self.resizeObjects();
             elif event.type() == event.Close :
                  msgDlgBox = myMsgDlgBox(self, QMessageBox.Question, "Confirmer la sortie ...", "Voulez-vous vraiment quitter\nQGIS et %s maintenant ?" % self.plugin.namePlugin, "rdistop.png", "inondation.png", 0, None, self)
                  ret = msgDlgBox.exec_()
                  if ret == 1 :
                     try : self.plugin.killMemoryLayerFromProject() 
                     except : pass
                     if not (self.mainWindow.menuBar().isVisible() and self.mainWindow.statusBar().isVisible()) :
                           self.plugin.actionToggleMapOnly.triggered.emit()
                           self.isClosed = True
                           event.accept()
                  else : event.ignore() ; return True;
                
          return QObject.eventFilter(self, obj, event)

      def resizeObjects(self): self.plugin.getUpdateRightWidgets()

      def closeEvent(self, event):
          try : 
                if self.isClosed : event.accept() 
                else : event.ignore(); return True ;
          except :  event.ignore(); return True ;
           
      def quit(self): self.plugin.quit()

    
class tableFieldsMapper(QTableWidget):    
    def __init__(self, *args):
            super(tableFieldsMapper, self).__init__(*args)
            #NEW 4.0.0
            self.params = {
                "administratif" : {"INSEE_COM" : None, "NOM" : None, "FILTERWITH" : None},
                "cours_eau"     : {"ID" : None, "TOPONYME" : None, "FILTERWITH" : None},
                "hydrometrie"   : {"CdStationH" : None, "LbStationH" : None, "CdAncieRef" : None, "SEUIL_RDI" : {}, "FILTERWITH" : None},                
                "infoclimat"    :  {"libelle" : None, "code" : None}                
                }

            self.setRowCount(0)
            self.setColumnCount(2)
            self.styleComboBox = STYLECOMBOBOX
            self.styleLabel = STYLELABEL
            self.styleLabelError = STYLELABELERROR

            self.layer = None
            
            self.setHorizontalHeaderLabels(["Mot clef", "Attribut correspondant"])
            self.setDragEnabled(False)
            self.setAcceptDrops(False)
            self.setDragDropOverwriteMode(False)
            self.setDropIndicatorShown(False)
            self.setSelectionMode(QAbstractItemView.SingleSelection)
            self.setSelectionBehavior(QAbstractItemView.SelectRows)

            self.context = QgsExpressionContext()
            self.scope = QgsExpressionContextScope()
            self.verticalHeader().hide()
            self.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
            self.initSizeCols()                       

    def initSizeCols(self):
        self.setColumnWidth(0, 120)
        self.setColumnWidth(1, 250)

    def setFields(self, layer):
        self.layer = layer
        
        provider = layer.dataProvider()
        nameFields = [field.name() for field in provider.fields()]
        
        for i in range(self.rowCount()):
            itemKey = self.item(i, 0).text()
            if itemKey != "FILTERWITH" :
               comboFields = self.cellWidget(i, 1)
               comboFields.clear()
               comboFields.addItems(nameFields)


    def toExpression(self, text): return text.replace("{", "\"").replace("}", "\"")
    def fromExpression(self, text):
        pos, deb = -1, False
        pos = text.find("\"")
        while pos > -1 :
            text = "%s%s%s" % (text[0:pos], "{" if not deb else "}", text[pos+1:])
            deb = not deb
            pos = text.find("\"", pos+1)
            
        return text

    def getItemPosByKey(self, key, nameField):
        for i in range(self.rowCount()):
            itemKey = self.item(i, 0).text()
            if itemKey == key :
               if itemKey != "FILTERWITH" : 
                  try : index = self.cellWidget(i, 1).findText(nameField, Qt.MatchFixedString)
                  except : index = -1
                  if index >= 0 : self.cellWidget(i, 1).setCurrentIndex(index)
                  self.item(i, 0).setBackground(QColor(200, 245, 80, 250)) if index >= 0 else self.item(i, 0).setBackground(QColor(250, 120, 120, 250))
                  break

               else :
                  self.cellWidget(i, 1).setText(nameField)
                  #VERIFY evaluate expression
                  expressionText = self.toExpression(self.filterWith.text())
                  value = self.evaluateExpression(expressionText)
                  if value != None : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
                  else :
                     if expressionText == "" : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
                     else : self.item(i, 0).setBackground(QColor(250, 120, 120, 250)) 
                  

    def evaluateExpression(self, expressionText) :
        expression = QgsExpression(expressionText)
        for feat in self.layer.getFeatures() :
            self.scope.setFeature(feat)
            self.context.appendScope(self.scope)
            self.scope.setFeature(feat); break ;
        value = expression.evaluate(self.context)
        return value

    def getExpression(self):
        if not self.layer : return
        
        changeButtonIcon(self, self.sender(),"expressionedit.png")

        expressionText = self.toExpression(self.filterWith.text())
        value = self.evaluateExpression(expressionText)

        builder = QgsExpressionBuilderDialog(self.layer, expressionText , self ) 
        windowIcon(builder, "geomaker/expressionedit.png")
              
        if (builder.exec_() == QDialog.Accepted):
            if not expressionText != builder.expressionText(): changeButtonIcon(self, self.sender(),"expression.png"); return;
            expressionText = builder.expressionText()
            value = self.evaluateExpression(expressionText)
            self.filterWith.setText(self.fromExpression(expressionText))
            i = self.rowCount()-1
            if value != None : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
            else :
               if expressionText == "" : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
               else : self.item(i, 0).setBackground(QColor(250, 120, 120, 250))               

        changeButtonIcon(self, self.sender(),"expression.png")

  
    def setData(self, paramtable, posY) :

        height = 0
        self.data = self.params[paramtable]
        
        for key in self.data.keys():
            self.insertRow(self.rowCount()); height+= 30 #40
            item = QTableWidgetItem(key)
            item.setFlags(item.flags() ^ Qt.ItemIsEditable)
            self.setItem(self.rowCount()-1, 0, item)

            if key != "FILTERWITH" :
                comboFields = QComboBox()
                comboFields.setObjectName("comboFields")
                comboFields.setStyleSheet(self.styleComboBox)
                self.setCellWidget(self.rowCount()-1, 1, comboFields)
            else :
                self.filterWith = QLabel()
                self.expressionButton = QPushButton(self.filterWith)
                self.expressionButton.setObjectName("expression")
                self.expressionButton.setToolTip("Constructeur Expression QGIS ...")
                self.expressionButton.setFixedSize(25, 25)
                self.expressionButton.setGeometry(215, 1, 25, 25)
                self.expressionButton.setIcon(QIcon(getThemeIcon("expression.png")))
                self.expressionButton.clicked.connect(self.getExpression)
                self.setCellWidget(self.rowCount()-1, 1, self.filterWith)
            
            self.setGeometry(100, posY, 380, height)
            self.setMinimumHeight(height)

        
        return posY+height-20

#NEW 4.1.5 
class paramToService(QDialog):
    def  __init__(self, parent, indexTypeInfo, deltaInMinutes):
            super(paramToService, self).__init__()
            self.setWindowTitle("Paramètres pour le service stations :")
            self.setWindowFlags(Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 480, 410 
            self.resize(self.minWidth, self.minHeight)
            setObjSizes(self, self.minWidth, self.minHeight)
            self.parent = parent
            self.tableFieldsStations = parent.tableFieldsStations
            self.selectedStations = {}

            self.layer = getLayerByName(self.parent.layerStations)
            self.stationIcon = QIcon(getThemeIcon("viewstation.png"))
            self.allstationIcon = QIcon(getThemeIcon("inondation.png"))

            windowIcon(self, "station.png")

            self.labelTypeInfos  = QLabel(self)
            self.labelTypeInfos.setText("Type d'information : <hr>")
            self.labelTypeInfos.setGeometry(10, 10, 460, 25)

            self.comboTypeInfos = QComboBox(self)
            self.comboTypeInfos.addItems(["Débit", "Hauteur"])
            self.comboTypeInfos.setIconSize(QSize(18,18))
            self.comboTypeInfos.setStyleSheet(self.parent.styleComboBox)
            model = self.comboTypeInfos.model()
            for index in range(0, model.rowCount()):
                item = model.item(index)
                item.setIcon(QIcon(getThemeIcon("debit.png")) if index == 0 else QIcon(getThemeIcon("hauteur.png")))
            self.comboTypeInfos.setGeometry(10, 40, 100, 25)
            self.comboTypeInfos.setCurrentIndex(indexTypeInfo)

            self.label = QLabel(self)
            self.label.setText("Intervalle de scrutation : <hr>")
            self.label.setGeometry(10, 70, 460, 25)

            self.spinBoxTimer = QSpinBox(self)
            self.spinBoxTimer.setObjectName("spinBoxTimer")
            self.spinBoxTimer.setGeometry(10, 100, 70, 25)
            self.spinBoxTimer.setValue(deltaInMinutes)
            self.spinBoxTimer.setMinimum(1)
            self.spinBoxTimer.setMaximum(60)
            self.spinBoxTimer.setSuffix(' min.')
            self.spinBoxTimer.setSingleStep(5)
            self.spinBoxTimer.setStyleSheet("QSpinBox {font: normal 12px 'Arial'; color: rgb(255, 255, 255);background: #5D5D5D;}")
   
            self.labelListStations = QLabel(self)
            self.labelListStations.setObjectName("labelListStations")
            self.labelListStations.setText("Choix des stations : <hr>")
            self.labelListStations.setGeometry(10, 130, 460, 25)

            self.model = QStandardItemModel()
            self.view = QTableView()
            self.view.setModel( self.model )
            self.view.horizontalHeader().setVisible(False)
            self.view.verticalHeader().setVisible(False)
            self.view.setColumnHidden(1, True)
            self.view.verticalHeader().setDefaultSectionSize(20)
            self.view.horizontalHeader().setDefaultSectionSize(500)
            self.view.setStyleSheet("QTableView {min-width: 400px; max-width: 500px; selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FFFFFF, stop: 1 #000000);}")
            self.view.setShowGrid(False)

            self.listStations = QComboBox(self) 
            self.listStations.setEditable(True)
            self.listStations.setEnabled(True)
            self.listStations.setObjectName("listStations")
            self.listStations.setModel(self.model)
            self.listStations.setView(self.view)
            self.listStations.setGeometry(10, 160, 440, 25)
            self.listStations.setVisible(False)
            
            self.listStationsView = QListView(self)
            self.listStationsView.setObjectName("listStations")
            self.listStationsView.setModel(self.model)
            self.listStationsView.setGeometry(10, 160, 440, 200)

            contextMenu = getContextMenuSelection(self, None, None, False)

            butContextMenu = funnyPushButton(self)
            butContextMenu.initPushButton(25, 19, 0, 0, "butContextMenu", "", "Actions sur les membres", True, QIcon(getThemeIcon("actions.png")), 25, 19, True, False)
            butContextMenu.setGeometry(450, 160, 25, 19)
            butContextMenu.setMenu(contextMenu)            

            self.acceptButton = QPushButton(self)
            self.acceptButton.setObjectName("acceptButton")
            self.acceptButton.setText("Appliquer") 
            self.acceptButton.setFixedSize(100, 25)
            self.acceptButton.setGeometry(140, 370, 100, 30)
            self.acceptButton.clicked.connect(self.accept)
            
            self.rejectButton = QPushButton(self)
            self.rejectButton.setObjectName("rejectButton")
            self.rejectButton.setText("Annuler")
            self.rejectButton.setFixedSize(100, 25)
            self.rejectButton.setGeometry(240, 370, 100, 30)
            self.rejectButton.clicked.connect(self.reject)

            self.updateListStations()

    def selAllItems(self) : selAllItems(self.listStations)
    def unSelAllItems(self) : unSelAllItems(self.listStations)
    def invertSelItems(self) : invertSelItems(self.listStations)
    
    def updateTextComboListStations(self):
            model = self.listStations.model()
            self.counter = 0
            for index in range(1, model.rowCount()):
                item = model.item(index)
                if  item.checkState() == Qt.Checked : self.counter+= 1          
            item = self.listStations.model().item(0, 0)
            item.setText("Stations actives sélectionnées [%s]" % self.counter)
            self.acceptButton.setEnabled(self.counter > 0)

    def updateListStations(self): 
          self.listStations.clear()
          self.listStationsView.model().removeRows( 0, self.listStations.model().rowCount())
          
          self.layer = getLayerByName(self.parent.layerStations)
          try :  
              if not self.layer : return
          except :
              self.close()    
          if self.tableFieldsStations == {} : return

          fieldsNames = self.layer.fields().names()
          mapfieldsNames = map(lambda x:x.upper(), fieldsNames)

          filterWith  = self.parent.tableFieldsStations['FILTERWITH']
          if filterWith == "" : features = self.layer.getFeatures()
          else :
              exp = QgsExpression(filterWith.replace("{", "\"").replace("}", "\""))
              request = QgsFeatureRequest(exp)
              features = self.layer.getFeatures(request)
          
          self.listStations.addItem(self.allstationIcon, "Stations actives sélectionnées")

          fieldCdStationH = self.parent.tableFieldsStations['CdStationH']
          fieldLbStationH = self.parent.tableFieldsStations['LbStationH']
          fieldCdAncieRef = self.parent.tableFieldsStations['CdAncieRef']
          fieldSeuilRDI   = self.parent.tableFieldsStations['SEUIL_RDI']
 
          try : indexRdi = self.layer.fields().names().index(fieldSeuilRDI) 
          except : indexRdi = -1
          stations = {}

          transformer = getTransformer(self.layer.crs().authid(), 4326) 
          for feat in features:
              idStation = feat[fieldCdStationH]
              seuilRdi = feat[fieldSeuilRDI] if indexRdi >-1 else "Pas d'infos"
              point = feat.geometry().centroid().asPoint()    
              pointLL = transformer.transform(point, QgsCoordinateTransform.ForwardTransform) 
              longitude = pointLL.x()
              latitude =  pointLL.y()
             
              stations[idStation] = {"pointStation" : feat.geometry().asPoint(), "lbStation" : feat[fieldLbStationH], "idHydro" : feat[fieldCdAncieRef], "SEUIL_RDI" : seuilRdi if seuilRdi else "Pas d'infos", "latitude" : latitude, "longitude"  : longitude} 

          self.stations = stations
          orderedStations = dict(sorted(stations.items()))

          i = 1

          for idStation in orderedStations :
              lbStation, idHydro = orderedStations[idStation]["lbStation"], orderedStations[idStation]["idHydro"]
              self.listStations.addItem(self.stationIcon, "%s || %s || %s" % (idStation, lbStation, idHydro))
              item = self.listStations.model().item(i, 0)
              item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
              item.setData(Qt.Checked, Qt.CheckStateRole) 
              i+= 1  

          self.updateTextComboListStations()
          self.listStationsView.setModel(self.model)
          self.listStations.model().itemChanged.connect(self.updateTextComboListStations)

    def getDeltaScrutation(self): return int(self.spinBoxTimer.value())*1000*60
    def getDeltaInMinutes(self) : return self.spinBoxTimer.value()

    def getStations(self):
        model = self.listStations.model()
        nbTotal = model.rowCount()

        self.selectedStations = {}

        for index in range(1, nbTotal):
            item = model.item(index)
            if  item.checkState() == Qt.Checked :
                infos = item.text().split(" || ")
                idStation = infos[0]
                if dicoHasKey(self.stations, idStation) : self.selectedStations[idStation] = dict(self.stations[idStation])        
        return self.selectedStations    

class paramToPrintMap(QDialog):
    def  __init__(self, parent):
            super(paramToPrintMap, self).__init__()
            self.setWindowTitle("Paramètres pour l'impression de la carte")
            self.setWindowFlags(Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 280, 290
            self.resize(self.minWidth, self.minHeight)
            setObjSizes(self, self.minWidth, self.minHeight)
            self.parent = parent

            windowIcon(self, "printer.png")

            self.label = QLabel(self)
            self.label.setText("Définissez l'orientation pour l'impression de la carte ...<hr>")
            self.label.setGeometry(10, 10, 260, 30)

            self.comboBoxOrientation = QComboBox(self)
            self.comboBoxOrientation.addItems(["Paysage", "Portrait"])
            self.comboBoxOrientation.setMinimumHeight(80)
            self.comboBoxOrientation.setMaximumHeight(80)

            self.comboBoxOrientation.setIconSize(QSize(64,64))
            self.comboBoxOrientation.setStyleSheet(self.parent.styleComboBox)

            self.comboBoxOrientation.setItemIcon(0, QIcon(getThemeIcon("paysage.png")))
            self.comboBoxOrientation.setItemIcon(1, QIcon(getThemeIcon("portrait.png")))

            self.comboBoxOrientation.setGeometry(10, 40, 265, 85)
            self.comboBoxOrientation.setCurrentIndex(self.parent.orientationIndex)

            self.pagesizes = {}

            pageSizeRegsitry = QgsPageSizeRegistry().entries()
            for elt in pageSizeRegsitry :
                if elt.displayName.find("×") == -1 :
                   self.pagesizes[elt.displayName] = [elt.size.width(), elt.size.height(), elt.size.units()]

            self.labelFormat = QLabel(self)
            self.labelFormat.setText("Définissez le format l'impression de la carte ...<hr>")
            self.labelFormat.setGeometry(10, 130, 265, 25)
            
            self.comboBoxPageSize = QComboBox(self)
            cPageSizes = sorted(self.pagesizes.keys())
            self.comboBoxPageSize.addItems(cPageSizes)
            self.comboBoxPageSize.setCurrentIndex(self.parent.pageFormatIndex) #cPageSizes.index("A4"))
            self.comboBoxPageSize.setGeometry(10, 160, 265, 30)
            
            self.composer = QCheckBox(self)
            self.composer.setText("Me permettre de mofidier la mise en page")
            self.composer.setGeometry(10, 190, 265, 25)
            self.composer.setChecked(self.parent.withComposer)

            self.legende = QCheckBox(self)
            self.legende.setText("Inclure une légende simple")
            self.legende.setGeometry(10, 210, 265, 25)            
            self.legende.setChecked(self.parent.withLegend)

            self.northArrow = QCheckBox(self)
            self.northArrow.setText("Inclure une flèche du Nord")
            self.northArrow.setGeometry(10, 230, 265, 25)            
            self.northArrow.setChecked(self.parent.withNorthArrow)            

            self.acceptButton = QPushButton(self)
            self.acceptButton.setObjectName("acceptButton")
            self.acceptButton.setText("Imprimer") 
            self.acceptButton.setFixedSize(100, 25)
            self.acceptButton.setGeometry(40, 260, 100, 30)
            self.acceptButton.clicked.connect(self.accept)
            
            self.rejectButton = QPushButton(self)
            self.rejectButton.setObjectName("rejectButton")
            self.rejectButton.setText("Annuler")
            self.rejectButton.setFixedSize(100, 25)
            self.rejectButton.setGeometry(140, 260, 100, 30)
            self.rejectButton.clicked.connect(self.reject)

    def getOrientationIndex(self): return self.comboBoxOrientation.currentIndex()
    def getNorthArrow(self): return self.northArrow.isChecked()
    def getLegende(self): return self.legende.isChecked()
    def getComposition(self): return self.composer.isChecked()
    def getPageFormatIndex(self): return self.comboBoxPageSize.currentIndex()
    def getPageFormat(self): return self.comboBoxPageSize.currentText()
    def getPageSize(self): return self.pagesizes[self.comboBoxPageSize.currentText()]
 
    
class Button(QPushButton):
    entered = pyqtSignal()
    leaved = pyqtSignal()

    def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted):
        setObjSizes(self, sizeWidth, sizeHeight)
        if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
        if icon != "" :
           self.setIcon(QIcon(icon))
           self.setIconSize(QSize(iconWidth, iconHeight))
        self.setToolTip(toolTip)
        if isStyleSheeted : self.setStyleSheet("QPushButton {border: none;}")
        self.setObjectName(name)
        if text != "" : self.setText(text)    

    def enterEvent(self, event):
        super().enterEvent(event)
        self.entered.emit()

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.leaved.emit()                    


class switchButtonRdi(QPushButton):
    def __init__(self, parent = None, img = None):
        super(switchButtonRdi, self).__init__(parent)
        
        self.setCheckable(True)
        self.setIconSize(QSize(24, 24))
        icon = getThemeIcon(img)
        self.setIcon(QIcon(icon))
        self.setWhatsThis(img)
    
    def updateImg(self):
        icon = QIcon(getThemeIcon("%s_on.png" % self.whatsThis())) if self.isChecked() else QIcon(getThemeIcon("%s_off.png" % self.whatsThis()))
        self.setIcon(icon)    


                    
class linkedToolBar(QToolBar):
     def __init__(self, parent, canvas, lbStation, idStation, typeInfos, idHydro, posX, posY, noReturn, pathDoc, *args): 
          super(linkedToolBar, self).__init__(*args)

          self = QToolBar(canvas)
          self.parent = parent

          if noReturn :  icon, text, url = "sandre.png", "Fiche SANDRE pour la station %s ...", "https://www.sandre.eaufrance.fr/geo/StationHydro/%s"
          else :         icon, text, url = "vigicrues.png", "Fiche Vigicrues pour la station %s ...", "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie="+typeInfos+"&ZoomInitial=3" 
             
          dicoCtrls = {"actionWeb" : (icon, text, lbStation, url, idStation),
                       "actionFiche" : ("hydro.png", "Fiche Synthèse DREAL pour %s ...", lbStation, "http://www.hydro.eaufrance.fr/stationhydro/%s/fiche", idStation),
                       "actionStats" : ("inrae.png", "Satistiques sur INRAE pour %s ...", lbStation, "https://hydrogr.github.io/BDD-HydroClim/images/fr/%s_INRAE_BDD-HydroClim_fact_sheet_FR.png", idStation),
                       "actionData" : ("birdz.png", "Données sur OPENDATASOFT pour %s ...", lbStation, "https://birdz.opendatasoft.com/explore/dataset/vigicrues-hauteurs-et-debits-des-cours-deau/table/?flg=en&q=%s", idHydro[0:4])
                       }

          for key in dicoCtrls :
              icon = getThemeIcon(dicoCtrls[key][0])  
              text = dicoCtrls[key][1]
              button = QToolButton()              
              action = QAction(QIcon(icon), text % dicoCtrls[key][2], self)
              action.setToolTip(action.text())
              action.setObjectName(key)
              action.setWhatsThis(dicoCtrls[key][3] % dicoCtrls[key][4])
              action.triggered.connect(self.parent.openUrl)
              button.setDefaultAction(action)
              button.setStyleSheet("""QToolButton { background: rgb(255, 255, 255); border: 1px  solid  rgb(0, 0, 0); border-style: outset; border-width: 1px; border-radius: 50px; color: white;}
                                      QToolButton:hover { background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #96b920, stop: 1 #b5e61d);}
                                      """)
              setattr(self, key, action)
              self.addWidget(button) 

          toolDocLink = QMenu("Documents pour la station")

          getListDocForMenu(self, toolDocLink, idStation, pathDoc)

          button = QPushButton()     
          button.setIcon(QIcon(getThemeIcon("classification.png")))
          button.setToolTip("Documents associés")
          button.setIconSize(QSize(24, 24))
          button.setMinimumWidth(48)
          button.setMaximumWidth(48)
          button.setMinimumHeight(48)
          button.setMaximumHeight(48)
          button.setMenu(toolDocLink)  
          button.setStyleSheet("""QPushButton:hover{background-color:  rgb(0, 255, 255); border: 2px  solid  (0, 255, 255);}""")

          self.addWidget(button)
          
          self.setIconSize(QSize(48, 48))      
          self.setOrientation(Qt.Vertical)
          self.setStyleSheet("QToolBar { border: 1px solid; border-radius: 5px; background-color: rgba(255, 255, 255, 50);}")         
          self.setGeometry(posX, posY, 55, 270)
     

class labeller(QLabel):
     def __init__(self, name, text, styleSheet, alignment, *args):
             super(labeller, self).__init__(*args)
             self.setObjectName(name)
             self.setText(text)
             if styleSheet == "special": self.setStyleSheet("QLabel {display: block;  border: 2px solid gray;  border-radius: 10px; text-align: center;  line-height: 10px; background-color: #C0C0C0; border: 1px solid black; color: black}")
             elif styleSheet == "infos": self.setStyleSheet("QLabel {font-size:10px; background-color:#C0C0C0; border: 1px solid #e1e1e1; padding: 4px ; color: black}")
             elif styleSheet == "coords": self.setStyleSheet("QLabel {  color : white; font-size: 11px; background-color: rgba(93, 93, 93, 125);}")
             self.setAlignment(alignment)


class windowPlotStatsAndGraphs(QDialog):
    def __init__(self, parent, titleWindow, typeStats, data, labels, title, img, minY, maxY, typeInfos, idHydro, lbStation, idStation, pathDoc, tz, service, average, ecartType, coeffReg, constanteReg, stationDicoSeuils):  
        super(windowPlotStatsAndGraphs, self).__init__()
        self.parent = parent
        self.timezone = tz
        self.service = service
        self.average = average
        self.ecartType = ecartType
        self.coeffReg = coeffReg
        self.constanteReg = constanteReg
        self.stationDicoSeuils = stationDicoSeuils
        self.lblOriginalData = 'Observations brutes'
        self.lblSmoothy = 'Courbe lissée'
        self.lblTendancy = 'Droite de tendance'
        self.lblDeltaAverage = '+/- delta écart type'
        self.average1ineup, self.average1inedown, self.averagefill = None, None, None        

        self.setWindowFlags(Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
        
        self.typeStats = typeStats
        self.data = data
        self.labels = labels
        infos = title.split("[")
        self.title = infos[0]
        
        self.timelaps = infos[1].replace("]", "")
        
        self.image = img
        self.minY = minY
        self.maxY = maxY
        self.typeInfos = typeInfos
        self.idHydro = idHydro
        self.lbStation = lbStation
        self.idStation = idStation
        self.minWidth, self.minHeight = 1200, 700
        self.resize(self.minWidth, self.minHeight)

        windowIcon(self, "grahique.png") 
        self.setWindowTitle("%s - %s :" % (self.parent.namePlugin, titleWindow)) if titleWindow else self.setWindowTitle("%s - %s :" % (self.parent.namePlugin, QApplication.translate("rdi","Graph statistics")))

        self.figure = pltFigure()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)

        self.addingTb = linkedToolBar(self, self.canvas, self.lbStation, self.idStation, self.typeInfos, self.idHydro, 10, 160, False, pathDoc) 
        
        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        self.setLayout(layout)

        self.chkSmoothy = QCheckBox("Afficher la %s" % self.lblSmoothy.lower(), self)
        self.chkSmoothy.setChecked(True)
        self.chkSmoothy.setObjectName("chkSmoothy")
        self.chkSmoothy.setGeometry(340, 0, 200, 25)
        self.chkSmoothy.stateChanged.connect(self.setVisibilityForSmoothy) 
        
        self.chkDeltaAverage = QCheckBox("Afficher la zone %s" % self.lblDeltaAverage.lower(), self)
        self.chkDeltaAverage.setChecked(False)
        self.chkDeltaAverage.setObjectName("chkDeltaAverage")
        self.chkDeltaAverage.setGeometry(340, 15, 200, 25)
        self.chkDeltaAverage.stateChanged.connect(self.setVisibilityForAverage) 

        self.chkTendency = QCheckBox("Afficher la %s" % self.lblTendancy.lower(), self)
        self.chkTendency.setChecked(False)
        self.chkTendency.setObjectName("chkTendency")
        self.chkTendency.setGeometry(340, 30, 200, 25)
        self.chkTendency.stateChanged.connect(self.setVisibilityForTendancy) 
        
        if self.typeInfos == "Q" :
            self.chkStem = QPushButton(self)
            self.chkStem.setCheckable(True)
            self.chkStem.setChecked(False)
            self.chkStem.setObjectName("chkStem")
            self.chkStem.setGeometry(550, 15, 30, 30)
            self.chkStem.setIcon(QIcon(getThemeIcon("stem.png")))
            self.chkStem.setIconSize(QSize(24 , 24))
            self.chkStem.setToolTip("Basculer en représentation par tiges")
            self.chkStem.clicked.connect(self.updateImgForButton)

        self.plot()

    def resizeEvent(self, event):
        return
        self.chkSmoothy.setGeometry(self.width()-850, 0, 200, 25)
        self.chkDeltaAverage.setGeometry(self.width()-850, 15, 200, 25)
        self.chkTendency.setGeometry(self.width()-850, 30, 200, 25)
        if self.typeInfos == "Q" : self.chkStem.setGeometry(self.width()-600, 15, 30, 30)
        

    def updateLegend(self):
        for origline in self.legend.get_lines():
            visible = origline.get_visible()
            text = "%s" % origline
            if text.find(self.lblSmoothy) > -1 : visible = self.chkSmoothy.isChecked()
            elif text.find(self.lblTendancy) > -1 : visible = self.chkTendency.isChecked()
            elif text.find(self.lblDeltaAverage) > -1 : visible = self.chkDeltaAverage.isChecked()
            origline.set_alpha(1.0 if visible else 0.0)

        i, counter = 1, len(self.legend.get_patches())
        for obj in self.legend.get_patches() :
            if i == counter :
                visible = self.chkDeltaAverage.isChecked()
                obj.set_alpha(1.0 if visible else 0.0)
            i+= 1
            
        self.legend.update
    
    def setVisibilityForSmoothy(self):
        self.linesmoothy.set_visible(self.chkSmoothy.isChecked())
        self.updateLegend()
        self.canvas.draw()
        
        
    def setVisibilityForAverage(self):
        cond = self.chkDeltaAverage.isChecked()
        self.average1ineup.set_visible(cond)
        self.averagefill.set_visible(cond)
        self.average1inedown.set_visible(cond)
        self.updateLegend()
        self.canvas.draw()
        
        
    def setVisibilityForTendancy(self):
        self.tendancy.set_visible(self.chkTendency.isChecked())
        self.updateLegend()
        self.canvas.draw()
        
    def updateImgForButton(self):
        icon = "stackplot.png" if self.chkStem.isChecked() else "stem.png"
        toolTip = "Basculer en représentation pleine" if self.chkStem.isChecked() else "Basculer en représentation par tiges"
        self.chkStem.setIcon(QIcon(getThemeIcon(icon)))
        self.chkStem.setToolTip(toolTip)
        self.plot()

    def format_coord(self, x, y):  return ""   

    def update_annot(self, ind, line, annot, ydata):
        x, y = line.get_data()
        annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
       
        x_values = " ".join(list(map(str, ind["ind"])))
        y_values = " ".join(str(ydata[n]) for n in ind["ind"])

        dateObs = "%s" % x[ind["ind"][0]]
        dateObs = "%s%s" % (dateObs.replace(" ", "T"), "Z")
        dateObs = jsonDateToStrDate(dateObs, "", " ", None)
                               
        valueObs = round(float(y[ind["ind"][0]]), 3)
        
        text = "date : {}\nvaleur relevée : {}".format(dateObs, valueObs)
         
        annot.set_text(text)
        annot.get_bbox_patch().set_alpha(0.4)


    def hover(self, event, line_info):
        line, annot, ydata = line_info
        vis = annot.get_visible()
        ax = self.figure.get_axes()[0]

        if event.inaxes == ax:
            cont, ind = line.contains(event)
            if cont:
                self.update_annot(ind, line, annot, ydata)
                annot.set_visible(True)
                self.figure.canvas.draw_idle()
            else:
                if vis:
                    annot.set_visible(False)
                    self.figure.canvas.draw_idle()


    def openUrl(self):
        url = self.sender().whatsThis()
        try : webbrowseropen(url)
        except : pass


    def setDecorations(self) : 
        self.figure.text(0.12, 0.92, self.service, size=10, color='black')
        self.figure.text(0.12, 0.9, 'Mesures de station hydrométrique', size=8, color='black')
        self.figure.text(0.8, 0.05, 'données : www.hydro.eaufrance.fr', size=8, color='black')      
        
        fileLogoLeft = self.parent.fileLogoLeft 
        if exists(fileLogoLeft) :
           newaxlogoLeft = self.figure.add_axes([0.0, 0.8, 0.1, 0.2], anchor='NE', zorder=-1)  
           imgLogoLeft = mpimg.imread(fileLogoLeft)
           newaxlogoLeft.imshow(imgLogoLeft)
           newaxlogoLeft.axis('off')

        
        fileLogoRight = self.parent.fileLogoRight 
        if exists(fileLogoRight) :
           newaxlogoRight = self.figure.add_axes([0.9, 0.8, 0.1, 0.2], anchor='NW', zorder=-1)  
           imgLogoRight = mpimg.imread(fileLogoRight)
           newaxlogoRight.imshow(imgLogoRight)
           newaxlogoRight.axis('off')        

    def plot(self):

        self.figure.clear()
        ax = self.figure.add_subplot(111)

        if self.typeStats == "linechart0" :
           if self.typeInfos == "H" :
              self.basicData, = ax.plot_date(self.labels, self.data, linestyle="dotted",  label=self.lblOriginalData, marker="o", markersize=1.0, color='dodgerblue')
           else :
              self.basicData, = ax.plot_date(self.labels, self.data, linestyle="dotted", marker="o", markersize=1.0, color='dodgerblue')
              
           if self.typeInfos == "Q" :
              if self.chkStem.isChecked() :
                 markerline, stemlines, baseline = ax.stem(self.labels, self.data, label=self.lblOriginalData, linefmt='dodgerblue',  bottom=0.0, use_line_collection=True) 
                 markerline.set_markerfacecolor('none')
              else : ax.stackplot(self.labels, self.data, labels=[self.lblOriginalData], alpha=0.8, color='dodgerblue')
           
           formatter = mdates.DateFormatter("%d/%m/%Y %Hh%Mm")
           formatter.set_tzinfo(pytz.timezone(self.timezone)) 
           ax.xaxis.set_major_formatter(formatter)

           self.annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",  bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))
           self.annot.set_visible(False)

           label = "date d'observation - fuseau horaire : %s\npériode d'observation : %s" % (self.timezone, self.timelaps) 

           ax.set(xlabel=label, ylabel='Hauteurs (m)', title=self.title) if self.typeInfos == "H" else ax.set(xlabel=label, ylabel='Débit m3/s', title=self.title)
           self.figure.autofmt_xdate()
           ax.xaxis.set_tick_params(rotation=30, labelsize=10)
           ax.set_ylim([self.minY*0.75, self.maxY*1.25])

           #SET STATUS BAR INFO 
           ax.format_coord = self.format_coord

           for label in (ax.get_xticklabels() + ax.get_yticklabels()):
               label.set_fontname('Arial')
               label.set_fontsize(7)  

           def smooth(y, box_pts):
               box = np.ones(box_pts)/box_pts
               y_smooth = np.convolve(y, box, mode='same') 
               return y_smooth

           data = list(self.data)
           data.insert(0, self.data[0])
           y_smooth = smooth(data, 2) 
           labels = list(self.labels)
           labels.insert(0, self.labels[0])

           if self.stationDicoSeuils != {}:
              firstKey = True
              lineSeuil = []
              for key in self.stationDicoSeuils.keys():
                  valueSeuil = self.stationDicoSeuils[key]['valueSeuil']
                  commentsSeuilRDI = self.stationDicoSeuils[key]['commentsSeuilRDI'].replace("<br>", " ")
                  if firstKey : linestyle, linewidth, firstKey = "dashed", 1.5, False
                  else : linestyle, linewidth = "solid", 2.5 
                  line1 = ax.axhline(valueSeuil, color='red', alpha=.4, linestyle=linestyle, linewidth=linewidth, label="%s (%s)" % (commentsSeuilRDI, valueSeuil))
                  lineSeuil.append(line1)
                  ax.annotate("%s (%s)" % (commentsSeuilRDI, valueSeuil), xy=(0.5, 0.5), xycoords=line1,  xytext=(1, 0.5), textcoords="offset points", size=8, color='red')

           if not self.average :
              self.average = np.nanmean(data)
              obs = np.arange(len(labels))
              self.coeffReg, self.constanteReg = np.polyfit(obs, data, deg=1)
              self.ecartType = np.nanstd(data)

           obs = np.arange(len(labels))
           fit = np.polyfit(obs, data, deg=1)
           fit_function = np.poly1d(fit)
           self.tendancy, = ax.plot(labels, fit_function(obs), alpha=0.8, linestyle="dotted", linewidth=2.0, label=self.lblTendancy, color='navy')           
           
           smoothy = list(y_smooth)
           del labels[0]
           del smoothy[0]
           self.linesmoothy, = ax.plot(labels, smoothy, 'b-', color='navy', label=self.lblSmoothy)

           if self.average :
              self.average1ine = ax.axhline(self.average, color='olive', label='Moyenne (%s)' % self.average, alpha=.8, linestyle="dashed", linewidth=2.0)
              if self.ecartType : 
                  condAvg = self.chkDeltaAverage.isChecked() 
                  self.average1ineup = ax.axhline(self.average + self.ecartType, color='yellowgreen', alpha=.4, linestyle="dotted", linewidth=1.0, label=self.lblDeltaAverage)
                  self.average1inedown =  ax.axhline(self.average - self.ecartType, color='yellowgreen', alpha=.4, linestyle="dotted", linewidth=1.0)
                  self.averagefill = ax.fill_between(self.labels, self.average - self.ecartType, self.average + self.ecartType, label='Zone %s' % self.lblDeltaAverage.lower(), alpha=.2, linewidth=0, color='yellowgreen', hatch='/', zorder=10)

              self.averagenote = ax.annotate('Moyenne sur la période (%s)' % self.average, xy=(0.75, 0.5), xycoords=self.average1ine,  xytext=(1, 0.5), textcoords="offset points", size=8, color='olive')

           self.setDecorations()
           ax.grid(True)

        
        if self.typeStats == "linechart1" :
                
           ax = self.figure.add_subplot(111)
           ax.plot_date(self.labels, self.data)
           ax.set(xlabel="date d'observation", ylabel='Hauteurs (m)', title=self.title)
           self.figure.autofmt_xdate()
           ax.xaxis.set_tick_params(rotation=30, labelsize=10)
           self.setDecorations()
           ax.grid(True)

        self.ax = ax

        self.legend = ax.legend(loc='upper left', title="Légende :", title_fontsize='x-small', fontsize='small', fancybox=True, shadow=True) 
        self.legend.set_zorder(20)

        self.updateLegend()
       
        self.tendancy.set_visible(self.chkTendency.isChecked())
        self.linesmoothy.set_visible(self.chkSmoothy.isChecked())
        if self.average1ineup : self.average1ineup.set_visible(condAvg)
        else : self.chkDeltaAverage.setEnabled(False)        
        if self.average1inedown : self.average1inedown.set_visible(condAvg)
        if self.averagefill : self.averagefill.set_visible(condAvg)        
                
        self.canvas.draw()
        self.canvas.setToolTip("http://www.hydro.eaufrance.fr/stations/%s&procedure=synthese" % self.idHydro)

        self.annot = ax.annotate("", xy=(0, 0), xytext=(-20, 20), textcoords="offset points",  bbox=dict(boxstyle="round", fc="w"),  arrowprops=dict(arrowstyle="->"))
        self.annot.set_visible(False)
        line_info = [self.basicData, self.annot, self.data] 
        self.canvas.mpl_connect("motion_notify_event", lambda event: self.hover(event, line_info))


class printerPreview(object):
      def __init__(self, parent, obj, title, orientation, icon):
                 
              printer = QPrinter() 
              printer.setPageSize(QPrinter.A4)
              printer.setOrientation(orientation)
              printer.setPageMargins(5, 10, 5, 10, QPrinter.Millimeter)
              printer.setOrientation(orientation)
              printer.setOutputFormat(QPrinter.NativeFormat)
              self = QPrintPreviewDialog(printer)
              windowIcon(self, icon)
              self.setWindowTitle(title)
              self.setWindowFlags(Qt.WindowMaximizeButtonHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
              self.paintRequested.connect(obj.print_)
              self.exec_()        


class myMsgDlgBox(QMessageBox):
        
        def __init__(self, parent, icon, title, msg, image, iconWindow, contexte, ctrl, *args):
            super(myMsgDlgBox, self).__init__()
            self.setWindowTitle(title)
            self.setIcon(icon)
            self.parent = parent
                        
            windowIcon(self, iconWindow)

            groupBoxNodeInfos = QGroupBox(self)
            groupBoxNodeInfos.setFixedSize(420, 320)
            groupBoxNodeInfos.setObjectName("groupBoxNodeInfos")


            if contexte == 0 :
                labelImage = QLabel(groupBoxNodeInfos)
                icon = getThemeIcon(image)
                carIcon = QImage(icon)
                labelImage.setPixmap(QPixmap.fromImage(carIcon).scaled(400, 300))
                labelImage.setFixedSize(400, 300)
                labelImage.setGeometry(5, 5, 400, 300)

                lblText = QLabel(labelImage)
                lblText.setText(msg)
                lblText.setAlignment(Qt.AlignCenter)
                lblText.setStyleSheet("""color: black; background-color: rgba(255, 255, 125, 150); border-left-width: 1px; border-left-color: black;
                                         border-left-style: solid; border-right-color: black; border-right-width: 1px; border-right-style: solid;
                                         border-right-radius: 3px;""")
                lblText.setFixedSize(390, 65)
                lblText.setGeometry(5, 110, 390, 65)
                
            else :
                labelImage = QTextBrowser(groupBoxNodeInfos)
                labelImage.setStyleSheet("QTextBrowser { border: 1px solid; border-radius: 5px; opacity: 0.5; background-color: white;}")
                labelImage.setFixedSize(400, 300)
                labelImage.setGeometry(5, 5, 400, 300)
                labelImage.append(msg)

            acceptButton = QPushButton(self)
            acceptButton.setObjectName("acceptButton")
            acceptButton.setText("Oui") if contexte == 0 else acceptButton.setText("Ok")
            acceptButton.setFixedSize(100, 25)
            acceptButton.clicked.connect(self.accept)

            if contexte == 0 :
                rejectButton = QPushButton(self)
                rejectButton.setObjectName("rejectButton")
                rejectButton.setText("Non")
                rejectButton.setFixedSize(100, 25)
                rejectButton.clicked.connect(self.reject)

            self.layout().addWidget(groupBoxNodeInfos,0,1,1,2)
            self.layout().addWidget(acceptButton,4,1,1,1) if contexte == 0  else self.layout().addWidget(acceptButton,4,2,1,1)
            if contexte == 0 : self.layout().addWidget(rejectButton,4,2,1,1)

            self.setStandardButtons(self.NoButton)


class funnyPushButton(QPushButton):
     def __init__(self, *args): super(funnyPushButton, self).__init__(*args)
            
     def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted, fully):
            setObjSizes(self, sizeWidth, sizeHeight)
            self.iconWidth = iconWidth 
            self.iconHeight = iconHeight
            self.fully = fully
            self.selfFocused = False
            self.subMenuVisble = False
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            if icon != "" :
               self.setIcon(QIcon(icon))
               self.setIconSize(QSize(self.iconWidth , self.iconHeight))
            self.setToolTip(toolTip)
            if isStyleSheeted :
               if self.fully : self.setStyleSheet("QPushButton { border: 1px solid; border-radius: 5px; opacity: 0.5;  background-color: white;}"
                                                  "QPushButton::checked {icon-size: %spx, %spx; border-color: blue; border-width: 4px; border-radius: 5px; background-color:qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #186dc1, stop: 1 #39eafd);}" % (iconWidth, iconHeight))
               else :  self.setStyleSheet("QPushButton {border-width: 1px; border-radius: 10px;  border-color: beige;}")
  
            self.setObjectName(name)
            if text != "" : self.setText(" %s" % text)

     def enterEvent(self, event):
          if not self.selfFocused :
             #NEW 3.6.0 
             self.setIconSize(QSize(int(self.iconWidth * 0.8), int(self.iconHeight * 0.8)))
             self.selfFocused = True
          
     def leaveEvent(self, event):
        if self.subMenuVisble == False : self.restoreButton()
     
     def restoreButton(self):
           self.selfFocused = False    
           self.setIconSize(QSize(self.iconWidth, self.iconHeight))


class myPushButton(QPushButton):
     def __init__(self, *args): super(myPushButton, self).__init__(*args)
            
     def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted):
            setObjSizes(self, sizeWidth, sizeHeight)
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            if icon != "" :
               self.setIcon(QIcon(icon))
               self.setIconSize(QSize(iconWidth, iconHeight))
            self.setToolTip(toolTip)
            if isStyleSheeted : self.setStyleSheet("QPushButton {border: none;}")
            self.setObjectName(name)
            if text != "" : self.setText(text)


class progressBar(QProgressBar):
    def __init__(self, * args):
        QProgressBar.__init__(self, * args)

    def initTextEdit(self, width, height, posX, posY, name, isGeom, initValue, viewText, alignement, isStyleSheet):
        setObjSizes(self, width, height)
        if isGeom: self.setGeometry(posX, posY, width, height)
        self.setProperty("value", initValue)
        self.setAlignment(alignement)
        self.setTextVisible(viewText)
        self.setObjectName(name)
        self.setAccessibleName(name)
        if isStyleSheet == 0:
            self.setStyleSheet(
                               "progressBar {border: 2px solid grey; border-radius: 5px; text-align: center;}"
                               "progressBar::chunk {background-color: #6C96C6; width: 10px; margin: 0.5px;}"
                               )


class textEditFolder(QTextEdit):
    def __init__(self, * args):
        QTextEdit.__init__(self, * args)

    def initTextEdit(self, SizeW, SizeH, posX, posY, name, isGeom, undoRedo, isReadOnly, isRichText, isSizable):
        if not isSizable:  self.setMaximumSize(SizeW, SizeH)
        if isGeom: self.setGeometry(posX, posY, SizeW, SizeH)
        self.setUndoRedoEnabled(undoRedo)
        self.setReadOnly(isReadOnly)
        self.setAcceptRichText(isRichText)
        self.setObjectName(name)
        self.setAccessibleName(name)
        self.setAcceptDrops(True)
        self.slotCtrl = None

    def setSlotCtrl(self, slotCtrl): self.slotCtrl = slotCtrl    
    def mouseDoubleClickEvent(self, event):
        if self.slotCtrl : self.slotCtrl.clicked.emit()


#NEW 4.1.5
class simpleWebViewer(QWebView):
     def  __init__(self, parent, objectName):
            QWebView.__init__(self, parent)
            self.parent = parent
            self.setObjectName(objectName)
            self.settings().setAttribute(QWebSettings.AutoLoadImages, True)
            self.settings().setObjectCacheCapacities(0,0,0)
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) 
            self.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
            self.page().linkClicked.connect(self.parent.getActions)
            
    

class simpleViewerWebData(QDialog):
      def  __init__(self, url, subTitle):
            super(simpleViewerWebData, self).__init__()
            
            self.setWindowTitle("Visualiseur Web - %s" % subTitle)
            self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 1200, 900
            self.resize(self.minWidth, self.minHeight)

            windowIcon(self, "temporalseries.png")
            
            self.webViewer = QWebView(self) 
            self.webViewer.setObjectName("webViewer")
            self.webViewer.settings().setAttribute(QWebSettings.AutoLoadImages, True)
            self.webViewer.setContextMenuPolicy(Qt.CustomContextMenu)
            self.webViewer.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
                       
            self.closeButton = funnyPushButton(self)
            self.closeButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "closeButton", "", "Fermer", True, getThemeIcon("close.png"), 24, 24, True, False)

            self.closeButton.clicked.connect(self.reject)

            self.setUrl(url)

      def setUrl(self, url): self.webViewer.load(QUrl(url))

      def resizeEvent(self, ev):
          
          self.webViewer.setGeometry(5, 5, self.width()-10, self.height()-40)
          self.closeButton.setGeometry(self.width()-60, self.height()-30, 24, 24)

     
class reportingStations(QDialog):
      def  __init__(self, parent, iface, canvas, buttonFullStations, html, namePlugin):
            super(reportingStations, self).__init__()

            self.namePlugin = namePlugin
            self.setWindowTitle("%s : Bulletin des stations" % self.namePlugin)
            self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 800, 600
            self.resize(self.minWidth, self.minHeight)

            self.tableFieldsStations = parent.tableFieldsStations
            self.htmlBulletin = ""
            self.refHtmlSynoptic = HTMLSTATISTICS 
            self.refHtmlSynopticJS = HTMLSTATISTICS

            self.statisticsHtml, self.graphicsHtml, self.profilsHtml, self.meteoHtml = "", "", "", ""
            self.timezone = TIMEZONE

            windowIcon(self, "inondation.png")

            self.baseurl = QUrl('file:///')
            self.parent = parent
            self.iface = iface
            self.canvas = canvas

            #NEW 4.1.5
            self.manager = QNetworkAccessManager(self.iface)             
            self.layer = getLayerByName(self.parent.layerStations)
            self.layerTerrain = getLayerByName(self.parent.layerTerrain)
            self.layerWater = getLayerByName(self.parent.layerWater)
            
            self.buttonFullStations = buttonFullStations
            self.path = "%s/%s" % (dirname(__file__), "data/stations")
            #NEW 4.1.0
            self.stationIcon = QIcon(getThemeIcon("viewstation.png"))
            self.allstationIcon = QIcon(getThemeIcon("inondation.png"))
            
            self.counter = 0
            self.replyObject = None
            self.dataStations = {}
            self.initDir = dirname(__file__)
            self.stations = {}
            self.typeInfos, self.lblTypeInfos, self.uniteInfos, self.titleTh = None, None, None, None
            self.infosExtraction = ""
     
            self.groupParams = QGroupBox(self)                       
            self.groupParams.setObjectName("groupParams")

            self.labelComboTypeInfos = QLabel(self.groupParams)
            self.labelComboTypeInfos.setObjectName("labelComboTypeInfos")
            self.labelComboTypeInfos.setText("Information : ")
            self.labelComboTypeInfos.setToolTip("Choix de l'information à rapatrier")
            self.labelComboTypeInfos.setAlignment(Qt.AlignRight)

            self.comboTypeInfos = QComboBox(self.groupParams)
            self.comboTypeInfos.setObjectName("comboTypeInfos")
            self.comboTypeInfos.setToolTip("Choix de l'information à rapatrier")
            self.comboTypeInfos.addItems(["Débit","Hauteur"])
            #NEW 4.1.0
            model = self.comboTypeInfos.model()
            for index in range(0, model.rowCount()):
                item = model.item(index)
                item.setIcon(QIcon(getThemeIcon("debit.png")) if index == 0 else QIcon(getThemeIcon("hauteur.png")))
            
            self.comboTypeInfos.setCurrentIndex(1)

            #PLACE AFTER comboTypeInfos CREATED
            self.refreshConf()

            self.labelComboDate = QLabel(self.groupParams)
            self.labelComboDate.setObjectName("labelComboDate")
            self.labelComboDate.setText("Plage : ")
            self.labelComboDate.setToolTip("Choix de la plage temporelle")
            self.labelComboDate.setAlignment(Qt.AlignRight)

            self.comboDate = QComboBox(self.groupParams)
            self.comboDate.setObjectName("comboDate")
            self.comboDate.setToolTip("Choix de la plage temporelle")
            self.comboDate.addItems(["Dernières 3 heures", "Dernières 6 heures", "Dernières 12 heures", "Dernières 24 heures", "Dernières 48 heures",
                                     "Dernières 72 heures", "Dernière semaine", "Dernière quinzaine", "Derniers 31 jours"])

            self.timeDataSeries = (180, 360, 720, 1440, 2880, 4320, 10080, 20160, 44640)
            self.comboDate.setCurrentIndex(1)

            #NEW 4.1.0
            model = self.comboDate.model()
            for index in range(0, model.rowCount()):
                item = model.item(index)
                item.setIcon(QIcon(getThemeIcon("montre.png")) if index < 3 else QIcon(getThemeIcon("calendar.png")))


            self.labelListStations = QLabel(self.groupParams)
            self.labelListStations.setObjectName("labelListStations")
            self.labelListStations.setText("Stations : ")
            self.labelListStations.setAlignment(Qt.AlignRight)

            self.model = QStandardItemModel()
            self.view = QTableView()
            self.view.setModel( self.model )
            self.view.horizontalHeader().setVisible(False)
            self.view.verticalHeader().setVisible(False)
            self.view.setColumnHidden(1, True)
            self.view.verticalHeader().setDefaultSectionSize(20)
            self.view.horizontalHeader().setDefaultSectionSize(500)
            self.view.setStyleSheet("QTableView {min-width: 400px; max-width: 500px; selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FFFFFF, stop: 1 #000000);}")
            self.view.setShowGrid(False)

            self.listStations = QComboBox(self.groupParams) 
            self.listStations.setEditable(True)
            self.listStations.setEnabled(True)
            self.listStations.setObjectName("listStations")
            self.listStations.setModel(self.model)
            self.listStations.setView(self.view)

            locale.setlocale(locale.LC_TIME,'')

            self.lcdNumber = QLCDNumber(self)            
            self.lcdNumber.setObjectName("lcdNumber")
            self.lcdNumber.setSegmentStyle(QLCDNumber.Filled)
            self.lcdNumber.setStyleSheet("QLCDNumber {color: #c3c3c3; border: 2px solid grey; border-radius: 5px; background-color: #6C96C6; text-align: center;}")
            self.lcdNumber.setDigitCount(8)
            self.lcdNumber.display(thedatetime.now().strftime("%H:%M:%S")) 
            self.idTimer = self.startTimer(1000)

            self.chrono = funnyPushButton(self) 
            self.chrono.initPushButton(25, 25, 235, 5, "chrono", "", "Démarrer le robot", True, getThemeIcon("chrono/chronoplay.png"), 25, 25, True, False)

            fontchrono = QFont()
            fontchrono.setFamily('Courier')
            fontchrono.setFixedPitch(True)
            fontchrono.setPointSize(8)
            fontchrono.setBold(True)

            self.chronoInfo = QLabel(self) 
            carIcon = QImage(getThemeIcon("chrono/chronoinfo.png")) 
            self.chronoInfo.setPixmap(QPixmap.fromImage(carIcon))
            self.chronoInfo.setVisible(False)
            self.chronoInfo.setToolTip("")

            self.chronoInfoText = QLabel(self.chronoInfo)
            self.chronoInfoText.setAlignment(Qt.AlignLeft)
            self.chronoInfoText.setFont(fontchrono)
            self.chronoInfoText.setText("0")
            self.chronoInfoText.setGeometry(38, 12, 80, 20)
            self.chronoInfoText.setStyleSheet("QLabel {border: 2px solid grey; border-radius: 5px; background-color: #009900; color: white;}")

            self.stopChrono = funnyPushButton(self.chronoInfo)
            self.stopChrono.initPushButton(25, 25, 100, 10, "stopChrono", "", "Arrêter le robot", True, getThemeIcon("chrono/stopchrono.png"), 25, 25, True, False)
            self.isStartChrono = False

            self.labellcdNumber = QLabel(self)
            self.labellcdNumber.setObjectName("labellcdNumber")
            self.labellcdNumber.setText("%s %s" % (thedatetime.now().strftime("%a"), thedatetime.now().strftime("%d %B %Y").lstrip("0")))
            self.labellcdNumber.setAlignment(Qt.AlignCenter)
            self.labellcdNumber.setStyleSheet("QLabel {color: #FFF; border: 2px solid grey; border-radius: 5px; background-color: #6C96C6; text-align: center;}")

            self.buttonRefreshConf = funnyPushButton(self) 
            self.buttonRefreshConf.initPushButton(25, 25, 40, 90, "buttonRefreshConf", "", "Recharger la configuration RDI", True, getThemeIcon("refresh.png"), 25, 25, True, False)
            self.buttonListActions = funnyPushButton(self) 
            self.buttonListActions.initPushButton(25, 25, 40, 90, "buttonListActions", "", "Actions ...", True, getThemeIcon("actions.png"), 25, 25, True, False)

            #NEW 4.1.5 
            contextMenu = getContextMenuSelection(None, self, self, True)
            self.buttonListActions.setMenu(contextMenu)        

            self.dataWebButton = funnyPushButton(self)
            self.dataWebButton.initPushButton(154, 25, self.width()-140,  self.height()-140, "dataWebButton", "", "Interroger les stations", True, getThemeIcon("download.png"), 154, 25, True, False)

            self.optionsHtml = QGroupBox(self)
            self.optionsHtml.setStyleSheet("QGroupBox {border: 2px solid grey; border-radius: 5px; background-color: #FFFFCC; color: black;}")
            self.buttongroup = QButtonGroup(self.optionsHtml)
            #NEW 4.1.0
            self.buttongroupLastId = 0

            texts = ["Bulletin", "Statistiques", "Graphiques", "Profils", "Météo"]
            icons = ["legendstatic_off.png", "bulletin_off.png", "stem.png", "profil.png", "meteo.png"]

            i = 0
            pos = 2
            for text in texts :
                button = QPushButton(self.optionsHtml)
                button.setStatusTip(text)
                button.setCheckable(True)
                button.setChecked(True) if i == self.buttongroupLastId else button.setChecked(False) #NEW 4.1.0
                self.buttongroup.addButton(button, i+1)
                self.buttongroup.setId(button, i+1)
                button.setObjectName("%sButton" % text.lower())
                button.setIcon(QIcon(getThemeIcon(icons[i])))
                button.setIconSize(QSize(18, 18))
                #NEW 4.1.0
                button.setToolTip("%s des stations" % text if text != "Profils" else "%s des stations<br>Uniquement pour information Hauteur." % text)
                button.setGeometry(pos, 2, 30, 25)
                i+= 1
                pos+= 30

            self.isPageLoaded = False
            
            self.busyIndicator = progressBar(self.groupParams)
            self.busyIndicator.setObjectName("busyIndicator")
            self.busyIndicator.setValue(0)
            self.busyIndicator.setVisible(False)
            
            #NEW 4.1.5 
            self.htmlReporting = simpleWebViewer(self, "htmlReporting")
            self.statsReporting = simpleWebViewer(self, "statsReporting")
            self.graphsReporting = simpleWebViewer(self, "graphsReporting")
            self.profilsReporting = simpleWebViewer(self, "profilsReporting")
            self.meteoReporting = simpleWebViewer(self, "meteoReporting")

            self.dictWebViewers = {1: self.htmlReporting, 2: self.statsReporting, 3: self.graphsReporting,
                                   4: self.profilsReporting, 5: self.meteoReporting}
            self.setHtmlReport()
            
            self.copyToClipBoardButton = funnyPushButton(self)
            self.copyToClipBoardButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "copyToClipBoardButton", "", "Copier vers le presse papier pour des applicatifs tiers", True, getThemeIcon("copytoclipboard.png"), 24, 24, True, False)

            self.printButton = funnyPushButton(self)
            self.printButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "printButton", "", "Imprimer", True, getThemeIcon("printer_off.png"), 24, 24, True, False)

            self.saveButton = funnyPushButton(self)
            self.saveButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "saveButton", "", "Sauvegarder l'onglet courant", True, getThemeIcon("save.png"), 24, 24, True, False)
         
            self.saveAllButton = funnyPushButton(self)
            self.saveAllButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "saveAllButton", "", "Sauvegarder tous les onglets", True, getThemeIcon("saveall.png"), 24, 24, True, False)

            self.closeButton = funnyPushButton(self)
            self.closeButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "closeButton", "", "Fermer", True, getThemeIcon("close.png"), 24, 24, True, False)

            self.barInfo = QgsMessageBar(self)
            self.barInfo.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

            self.comboTypeInfos.currentIndexChanged.connect(self.setOptionsForExtraction)

            self.dataWebButton.clicked.connect(self.getDataWeb)
            self.buttonRefreshConf.clicked.connect(self.refreshConf)
            self.copyToClipBoardButton.clicked.connect(self.exportReporting)
            self.saveButton.clicked.connect(self.saveReporting)

            self.saveAllButton.clicked.connect(self.saveAllReporting)
            self.printButton.clicked.connect(self.printReporting)
            self.closeButton.clicked.connect(self.reject)
            self.rejected.connect(self.closeDlg)

            self.buttongroup.idClicked.connect(self.setHtmlReport)
            self.chrono.clicked.connect(self.startStopChrono)
            self.stopChrono.clicked.connect(self.startStopChrono)

            self.updateListStations()
            self.initImgagesAndLabels()
            self.setOptionsForExtraction()

            QgsProject(self.iface).instance().cleared.connect(self.shutDown)


      def timerEvent(self, e):
          self.lcdNumber.display(QTime.currentTime().toString("hh:mm:ss"))
          if self.isStartChrono :
               if ((thedatetime.now()-self.startTimeChrono).total_seconds() * 1000) >= self.deltaChrono :
                    self.startTimeChrono = thedatetime.now()
                    self.endTimeChrono = self.startTimeChrono + timedelta(minutes=self.deltaChronoInMinutes)
                    self.endTimeChronoText = thedatetime.strptime(self.endTimeChrono.replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")                    
                    #EXECUTE THE ROBOT ACTION
                    self.getDataWeb() 

               diff = self.endTimeChronoText - thedatetime.strptime(thedatetime.now().replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
               self.chronoInfoText.setText("%s" % diff)
      
           
      def startStopChrono(self):
          if not self.isStartChrono :
                deltaChrono, ok = QInputDialog.getInt(None, "Paramètre", "<h2>%s :</h2><br>[%s]" % ("Intervalle de scrutation", "minutes"), 1, 0, 60, 1) 
                if not ok: return
                if deltaChrono == 0 : return
              
                self.startTimeChrono = thedatetime.now()
                self.deltaChronoInMinutes = deltaChrono 
                self.endTimeChrono = self.startTimeChrono + timedelta(minutes=self.deltaChronoInMinutes)
                self.endTimeChronoText = thedatetime.strptime(self.endTimeChrono.replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
                
                diff = self.endTimeChronoText - thedatetime.strptime(thedatetime.now().replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
                self.chronoInfoText.setText("%s" % diff)
                
                self.deltaChrono = int(deltaChrono)*1000*60               
               
          self.isStartChrono = not(self.isStartChrono)             
          if self.isStartChrono :  
              self.chronoInfo.setToolTip("Scrutation toutes les %s minutes" % self.deltaChronoInMinutes if self.deltaChronoInMinutes > 1 else "Scrutation minute")
          else : self.chronoInfo.setToolTip("")    
          self.chronoInfo.setVisible(self.isStartChrono)


      def refreshConf(self):
          self.layer = getLayerByName(self.parent.layerStations)
          self.layerTerrain = getLayerByName(self.parent.layerTerrain)
          self.layerWater = getLayerByName(self.parent.layerWater)

          typeInfos = "Q" if self.comboTypeInfos.currentIndex() == 0 else "H"
          condProfils = self.layer != None and self.layerTerrain != None and self.layerWater != None and typeInfos != "Q"
          try : self.buttongroup.button(4).setEnabled(condProfils)
          except : pass
          
          self.fileLogoLeft = self.parent.fileLogoLeft
          self.fileLogoRight = self.parent.fileLogoRight
          self.titleService = self.parent.titleService
          self.noaction = self.parent.noaction
          self.movieCtrl = self.parent.movieCtrl
          self.pathLocalFolderStations = self.parent.pathLocalFolderStations
          self.namePlugin = self.parent.namePlugin
          self.timezone = self.parent.timezone

          try :  
              if self.sender() == self.buttonRefreshConf : self.getDataWeb()
          except : pass  

      def shutDown(self) :
          self.parent.dialogReportingStations = None
          self.close()
 
      def initImgagesAndLabels(self) :
          self.dictLegend = COMMENTOBSERVATIONS  
          self.commentsSeuilRDI = COMMENTSSEUILRDI 
          for key in IMAGESFORBULLETIN : setattr(self, key, join(dirname(__file__), IMAGESFORBULLETIN[key]).replace("\\","/"))
            

      def closeDlg(self):  changeButtonIcon(self, self.buttonFullStations,"bulletin_off.png")
      
      #NEW 4.1.5  
      def selAllItems(self) : selAllItems(self.listStations)
      def unSelAllItems(self) : unSelAllItems(self.listStations)
      def invertSelItems(self) : invertSelItems(self.listStations)

      def updateListStations(self):
          self.listStations.clear()
          self.layer = getLayerByName(self.parent.layerStations)
          try :  
              if not self.layer : return
          except :
              self.parent.dialogReportingStations = None
              self.close()    
          if self.tableFieldsStations == {} : return

          fieldsNames = self.layer.fields().names()
          mapfieldsNames = map(lambda x:x.upper(), fieldsNames)

          filterWith  = self.parent.tableFieldsStations['FILTERWITH']
          if filterWith == "" : features = self.layer.getFeatures()
          else :
              exp = QgsExpression(filterWith.replace("{", "\"").replace("}", "\""))
              #exp = QgsExpression(" \"{}\" = '{}' ".format("DtFermetur", '0'))
              request = QgsFeatureRequest(exp)
              features = self.layer.getFeatures(request)
          
          self.listStations.addItem(self.allstationIcon, "Stations actives sélectionnées")

          fieldCdStationH = self.parent.tableFieldsStations['CdStationH']
          fieldLbStationH = self.parent.tableFieldsStations['LbStationH']
          fieldCdAncieRef = self.parent.tableFieldsStations['CdAncieRef']
          fieldSeuilRDI   = self.parent.tableFieldsStations['SEUIL_RDI']
 
          try : indexRdi = self.layer.fields().names().index(fieldSeuilRDI) 
          except : indexRdi = -1
          stations = {}

          transformer = getTransformer(self.layer.crs().authid(), 4326) 
          for feat in features:
              idStation = feat[fieldCdStationH]
              seuilRdi = feat[fieldSeuilRDI] if indexRdi >-1 else "Pas d'infos"
              point = feat.geometry().centroid().asPoint()    
              pointLL = transformer.transform(point, QgsCoordinateTransform.ForwardTransform) 
              longitude = pointLL.x()
              latitude =  pointLL.y()
              stations[idStation] = {"lbStation" : feat[fieldLbStationH], "idHydro" : feat[fieldCdAncieRef], "SEUIL_RDI" : seuilRdi if seuilRdi else "Pas d'infos", "latitude" : latitude, "longitude"  : longitude} 

          self.stations = stations
          orderedStations = dict(sorted(stations.items()))

          i = 1

          for idStation in orderedStations :
              lbStation, idHydro = orderedStations[idStation]["lbStation"], orderedStations[idStation]["idHydro"]
              self.listStations.addItem(self.stationIcon, "%s || %s || %s" % (idStation, lbStation, idHydro))
              item = self.listStations.model().item(i, 0)
              item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
              item.setData(Qt.Checked, Qt.CheckStateRole) 
              i+= 1  

          self.updateTextComboListStations()
          self.listStations.model().itemChanged.connect(self.updateTextComboListStations)

      def updateTextComboListStations(self):
          model = self.listStations.model()
          self.counter = 0
          for index in range(1, model.rowCount()):
              item = model.item(index)
              if  item.checkState() == Qt.Checked : self.counter+= 1
          
          item = self.listStations.model().item(0, 0)
          item.setText("Stations actives sélectionnées [%s]" % self.counter)

          self.dataWebButton.setEnabled(self.counter > 0)


      def getCommentaires(self, idStation, typeInfos, average, etendue, minY, maxY, coeffReg, ecartInterQuartile, q1, q3, percQ1, percQ3, ecartType, dateDeb, dateFin, stationDicoSeuils, date_obs, resultat_obs, unitInfos) :

          lblDateDeb = getLitteralDate(dateDeb, "", False, True)
          lblDateFin = getLitteralDate(dateFin, "", False, True)

          counterDepassement = {}
          nbDepassement = 0

          commentaires = "<u>Pour la période</u> :<br><br>- du %s<br>- au %s<br><br>\n" % (lblDateDeb, lblDateFin)

          comments, tendancea, titleImg2 = getCommentsForTrend(coeffReg, True)
          commentaires+= comments  
          commentaires+= "<br>"
          
          if ecartType >= 0 and ecartType <= 0.03 : commentaires+= "Les informations recueillies indiquent une très faible dispersion des mesures.\n"
          elif ecartType > 0.03 and ecartType <= 0.08 : commentaires+= "Les informations recueillies indiquent une  faible dispersion des mesures.\n"
          elif ecartType > 0.08 and ecartType <= 0.15 : commentaires+= "Les informations recueillies indiquent une dispersion des mesures.\n"
          elif ecartType > 0.15 and ecartType <= 0.22 : commentaires+= "Les informations recueillies indiquent une forte dispersion des mesures.\n"
          else : commentaires+= "Les informations recueillies indiquent une très forte dispersion des mesures.\n"

          commentaires+= "<br>\n"
          commentaires+= "<small><ul>\n"
          commentaires+= "<li><b>%s %%</b> des mesures inférieures à la borne inf. Qrt.1 : %s %s ;</li>\n" % (round(percQ1, 2), round(q1, 3), " m" if typeInfos == "H" else " m3/s")
          commentaires+= "<li><b>%s %%</b> des mesures inférieures à la borne sup. Qrt.2 : %s %s ;</li>\n" % (round(percQ3, 2), round(q3, 3), " m" if typeInfos == "H" else " m3/s")

          #NEW 4.1.0
          nbObs, counter = len(resultat_obs), 0   
          for ele in resultat_obs :
              if ele < average : counter+= 1

          commentaires+= "<li><b>%s %%</b> des mesures inférieures à la moyenne : %s %s.</li>" % (round((counter/nbObs)*100, 2), average, " m" if typeInfos == "H" else " m3/s")
          commentaires+= "</ul></small>\n"

          ratiosSeuil = []
          lastObs = resultat_obs[0]

          dep = 0
          if stationDicoSeuils != {} :
             commentsSeuil, titleSeuil, valuesSeuil = [], [], []
             for seuil in stationDicoSeuils.keys():
                 commentSeuil = stationDicoSeuils[seuil]['commentsSeuilRDI'].replace("<br>", " ")
                 commentsSeuil.append(commentSeuil)
                 valueSeuil = stationDicoSeuils[seuil]['valueSeuil']
                 valuesSeuil.append(valueSeuil)
                 titleSeuil.append(commentSeuil)
                 ratioSeuil = round( (lastObs*100) / valueSeuil, 2 )
                 ratiosSeuil.append(ratioSeuil)
             
             for valueSeuil in valuesSeuil :
                 counterSeuilDep = 0
                 for obs in resultat_obs :
                     if obs > valueSeuil : counterSeuilDep+= 1
                 counterDepassement[valueSeuil] = counterSeuilDep
    

             #ESTIMATION DEBIT OU HAUTEUR / SEUIL
             commentaires+= "La station dispose de %s seuil%s RDI : <b>%s</b>.\n"  % (len(valuesSeuil), "s" if len(valuesSeuil) > 1 else "", [commentSeuil for commentSeuil in commentsSeuil])
             commentaires+= "<br>\n"
             commentaires+= "%s : <b>%s</b>.\n" % ( "La valeur du seuil est la suivante" if len(valuesSeuil) ==1 else "Les valeurs des seuils sont les suivantes", ["%s m" % valueSeuil if typeInfos == "H" else "%s m3/s" % valueSeuil for valueSeuil in valuesSeuil])

             i = 0
             for seuil in counterDepassement :
                 if counterDepassement[seuil] > 0 : commentaires+= "<br>Le seuil '<b><i>%s</i></b>' a été dépassé %s fois.\n""" % (titleSeuil[i], counterDepassement[seuil]); dep+= 1; i+= 1; nbDepassement+= counterDepassement[seuil];
             if dep == 0 : commentaires+= "<br>Aucun seuil n'a été dépassé sur la période.\n"   
          else :

             commentaires+= "<br>\n"
             commentaires+= "La station ne dispose pas de seuils RDI.\n"     

          commentaires+= "<br>\n"

          commentaireStatut, imgStatut, imgInfoBulle = getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, unitInfos, typeInfos)
          commentaires+= commentaireStatut
          commentaires+= "<br>"

          return commentaires, dep, ratiosSeuil, imgStatut, commentaireStatut, nbDepassement


      def getTitle(self, title) :
          html = "<div class='box-title'>\n"
          html+= "   <div class='ruban'>\n"
          html+= "        <h2>%s</h2>\n" % title
          html+= "     </div>\n"
          html+= "   <div class='ruban_gauche'></div>\n"
          html+= "</div>\n"
          html+= "<br><br>\n"
          return html

      
      def getDataWeb(self):

          if self.counter == 0 : return

          self.layer.setSubsetString("")

          imgWaiting = "file:///%s/data/imgs/%s" % (dirname(__file__), "loading.gif")
          imgBuildingGraphics = "file:///%s/data/imgs/%s" % (dirname(__file__), "buildgraphics.gif")

          #NEW 4.1.5 
          self.reinitWebViewers()
          self.dictWebViewers[self.idButtonGroup].setHtml("<center><h4>Récupération des informations (stations & météo) ...</h4><br><img src=\"%s\"></center>" % imgWaiting) 
  
          html = ""
          #NEW 4.1.5
          toolsBarStation, links = {}, {}
          self.busyIndicator.setValue(0)

          try :  
              if not self.layer : return
          except :
              self.parent.dialogReportingStations = None
              self.close()    

          self.replyObject = None          

          typeInfos = "Q" if self.comboTypeInfos.currentIndex() == 0 else "H"
          unitInfos = "m3/s" if typeInfos == "Q" else "m"
          #NEW 4.1.0 
          imgTypeData = getThemeIcon("debit.png") if typeInfos == "Q" else getThemeIcon("hauteur.png")
          #NEW 4.1.6
          imgTypeData = imgTypeData.replace("\\", "/")
          

          condProfils = self.layer != None and self.layerTerrain != None and self.layerWater != None and typeInfos != "Q"
          self.buttongroup.button(4).setEnabled(condProfils)
          
          size = [36, 72, 144, 288, 576, 864, 2016, 4032, 8928][self.comboDate.currentIndex()] 

          #NEW 4.1.0
          self.infosExtraction = "Type d'information : %s &nbsp;<img src='file:///%s' class='iconLegend' title='%s'><br>" % (self.comboTypeInfos.currentText(), imgTypeData, self.comboTypeInfos.currentText()) 
          self.infosExtraction+= "Période collectée  : %s<br>" % self.comboDate.currentText()
          self.infosExtraction+= "Extraction du : %s" % thedatetime.now().strftime("%d/%m/%Y à %Hh%M")

          self.statisticsHtml = "<HTML><BODY><head><title>Statistiques des stations</title></head>"
          self.statisticsHtml+= self.refHtmlSynopticJS
          self.statisticsHtml+= HTMLSTYLESSTATISTICS

          self.graphicsHtml = "<HTML>\n<head>\n<title>Graphiques des stations</title>\n"  
          self.graphicsHtml+= "</head>\n"
          self.graphicsHtml+= self.refHtmlSynopticJS
          #NEW 4.1.5
          self.graphicsHtml+= HTMLGRAPHICSJS
          self.graphicsHtml+= NODEJSCANVAS
          self.graphicsHtml+= HTMLSTYLESCANVAS 

          #NEW 4.1.5
          self.profilsHtml = "<head>\n"
          self.profilsHtml+= "<meta charset=\"utf-8\" />\n"
          self.profilsHtml+= "<title>Profils des stations</title>\n"
          self.profilsHtml+= HTMLSTYLESCANVAS
          self.profilsHtml+= NODEJSCANVAS
          self.profilsHtml+= "</head>\n"
          
          self.meteoHtml = "<HTML>\n<head>\n<title>Météo des stations</title>\n"
          self.meteoHtml+= "</head>\n"
          self.meteoHtml+= "<style>\n"
          self.meteoHtml+= HTMLSTYLESMETEO

          self.meteoHtml+= "</style>\n"

          blocEnteteHtml = "<body>\n"
          blocEnteteHtml+= "<table width='100%'>\n"
          blocEnteteHtml+= "<tr>\n"
          blocEnteteHtml+= "<td width='10%%'><img src='file:///%s' width='90px' height='90px'></td>\n" % self.fileLogoLeft
          blocEnteteHtml+= "<td width='85%%'><center><h2>%s, [TITLE] des stations</h2>%s</center></td>\n" % (self.titleService, getLitteralDate(thedatetime.now(), "le", False, True))
          blocEnteteHtml+= "<td width='5%%'><img src='file:///%s' width='170px' height='80px'></td></tr>\n" %  self.fileLogoRight
          blocEnteteHtml+= "</table>\n" 

          html = "<HTML><BODY><head><title>Bulletin des stations</title></head>%s" % INFORMATIONSSTYLES 
          html+= blocEnteteHtml.replace("[TITLE]", "bulletin")
          
          self.statisticsHtml+= blocEnteteHtml.replace("[TITLE]", "Statistiques")
          self.graphicsHtml+= blocEnteteHtml.replace("[TITLE]", "Graphiques")
          self.profilsHtml+= blocEnteteHtml.replace("[TITLE]", "Profils")
          self.meteoHtml+= blocEnteteHtml.replace("[TITLE]", "météo")

          #NEW 4.1.6  
          pathIconMeteo = join(dirname(__file__), "icons/meteo/").replace("\\", "/")
          self.meteoHtml+= "<center>\n"
          self.meteoHtml+= "<table class='blueTable'>\n"
          self.meteoHtml+= "<tr>"
          self.meteoHtml+= "<th><img src='file:///%s/stations.png' class='icon'/>Station hydrométrique</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/temperature.png' class='icon'/>&nbsp;Températures</th>\n" % pathIconMeteo 
          self.meteoHtml+= "<th><img src='file:///%s/precipitation.png' class='icon'/>&nbsp;Précipitations actuelles</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/cloud_cover.png' class='icon'/>&nbsp;Couverture nuageuse</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/vent.png' class='icon'/>&nbsp;Vitesse du vent</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/direction.png' class='icon'/>&nbsp;Direction du vent</th>" % pathIconMeteo
          self.meteoHtml+= "</tr>\n"
 
          html+= HTMLSTYLESBULLETIN
          
          html+= """<table width='100%'>\n"""
          html+= """<tr><td colspan='6'><hr>\n"""
          html+= """<table id='stationsinfos' cellspacing=0 width='100%'>\n"""
          html+= """<tr>"""
          html+= """<th colspan='4' class='title'><h3><center>[%s] Stations hydrométriques</h3></center>\n""" % self.counter
          html+= """<center><small>%s</small></center></th>\n""" % self.infosExtraction
          html+= """<th colspan='2' scope='colgroup'><center>Dernière Mesure</center></th>\n"""
          html+= """<th colspan='2' scope='colgroup'><center>Mesure Max</center></th>\n"""
          html+= """<th class='title' colspan='4'><h3><center>Indicateurs</h3></th>\n"""
          html+= """</tr>\n"""
          html+= """<tr>\n"""
          html+= """<th></th>\n"""
          html+= """<th>Station</th>\n"""
          html+= """<th title='Délai entre deux interrogations de la station'><center><fonr style='font-size: 2.0em; font-weight: bold;'>&#9201;</font></center></th>\n""" 
          html+= """<th><center>Seuils<br>indicatifs RDI</center></th>\n"""
          html+= """<th><center>%s</center></th>\n""" % self.titleTh
          html+= """<th title="Heure locale"><center>Heure</center></th>\n"""
          html+= """<th><center>%s</center></th>\n""" % self.titleTh
          html+= """<th title="Heure locale"><center>Heure</center></th>\n"""
          html+= """<th title="Moyenne calculée sur la période d'extraction"><center>Moy.</center></th>\n"""
          html+= """<th title="Position par rapport à la moyenne sur la période d'extraction pour la dernière mesure"><center>Pos/Moy</center></th>\n"""
          html+= """<th title="Sens de l'évolution sur les 3 dernières heures d\'observation"><center>Ev.3h</center></th>\n"""
          html+= """<th title="Sens de l'évolution entre les 2 dernières mesures relevées"><center>Der.Ev.</center></th>\n"""
          html+= """</tr>"""
                    
          self.setEnabledCtrl(False)

          self.dataStations = {}

          #NEW 4.1.5 
          #dictLastObs from  self.dataStations[idStation] - lastData
          #dictLastEvol -> ADD TO self.dataStations (Arrow Last Evol) - tendance
          #dictMeteo -> NEW COLLECT weatherCode
          #NEW 4.1.5  
          dictMeteo = {}
          dictLabelSeuils = {"SH1" : "Seuil de vigilance", "SH2" : "Seuil de 1er débordement", "SH3" : "Seuil critique"} #TEMPO ... -> params.py

          i = 1
          model = self.listStations.model()
          self.index = 0
          nbTotal = model.rowCount()

          for index in range(1, nbTotal):

                  toolBarStation = ""

                  item = model.item(index)
                  if  item.checkState() == Qt.Checked :

                      infos = item.text().split(" || ")
                      idStation, lbStation, idHydro = infos[0], infos[1], infos[2]

                      url = APIHUBEAU % (idStation, typeInfos, size) 
                      loop = QEventLoop()
                      self.replyObject = self.manager.get(QNetworkRequest(QUrl(url)))
                      self.replyObject.finished.connect(loop.quit)
                      loop.exec_()

                      #NEW 4.1.5
                      link = "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie=%s&ZoomInitial=3" % (idStation, typeInfos)
                      stationHtml, toolBarStation = self.readContent(idStation, lbStation, idHydro, self.comboDate.currentIndex(), i, nbTotal, link)
                      if not dicoHasKey(toolsBarStation, idStation): toolsBarStation[idStation] = toolBarStation
                      if not dicoHasKey(links, idStation) : links[idStation] = link
                      
                      html+= stationHtml

                      latitude = self.stations[idStation]["latitude"]
                      longitude = self.stations[idStation]["longitude"]

                      url = APIMETEO % (latitude, longitude, self.parent.apimodelmeteo[1]) 
                      url = url.replace(" ", "%2F")  
                        
                      self.replyObject = self.manager.get(QNetworkRequest(QUrl(url)))
                      self.replyObject.finished.connect(loop.quit)
                      loop.exec_()

                      try  : answerAsText = eval(bytes(self.replyObject.readAll()).decode("utf-8"))
                      except : answerAsText = {}

                      if answerAsText != {} :
                          lblRainForce = getRainForce(answerAsText["current"]["precipitation"])
                          lblWindSpeed10m = getWindForce(answerAsText["current"]["wind_speed_10m"])
                          lblWindOrientation = getWindOrientation(answerAsText["current"]["wind_direction_10m"])
                          lblCloudCover = getCloudCover(answerAsText["current"]["cloud_cover"])  

                          #NEW 4.1.5
                          if not dicoHasKey(dictMeteo, idStation) : dictMeteo[idStation] = answerAsText["current"]["weather_code"]
                          
                          infosMeteo = """<tr>
                                             <td class='entite'><b>%s</b> (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>) %s</td>
                                             <td><b>%s %s</b><br><br>ressentie : %s %s</td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                         </tr>\n""" % (lbStation, links[idStation], idStation, toolsBarStation[idStation],
                                                       answerAsText["current"]["temperature_2m"], answerAsText["current_units"]["temperature_2m"],
                                                       answerAsText["current"]["apparent_temperature"], answerAsText["current_units"]["apparent_temperature"],
                                                       answerAsText["current"]["precipitation"], answerAsText["current_units"]["precipitation"], lblRainForce,
                                                       answerAsText["current"]["cloud_cover"], answerAsText["current_units"]["cloud_cover"], lblCloudCover,
                                                       answerAsText["current"]["wind_speed_10m"], answerAsText["current_units"]["wind_speed_10m"], lblWindSpeed10m,
                                                       answerAsText["current"]["wind_direction_10m"], answerAsText["current_units"]["wind_direction_10m"], lblWindOrientation) 
                          self.meteoHtml+= infosMeteo

                      else :
                          self.meteoHtml+= "<tr><td class='entite' colspan='6'><center><font style='background-color: #FF0000; color: white; font-weight: bold;'><u>%s %s (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>) - Pas de retour du service.</u></center></font></td></tr>" %  (toolsBarStation[idStation], lbStation, links[idStation], idStation) 

                      self.busyIndicator.setValue(int(100 * i/ self.counter))
                      self.busyIndicator.repaint()

                      i+= 1

          key = sorted(APIMODELMETEOITEMS.keys())[self.parent.apimodelmeteo[0]]
          organismeMeteo = APIMODELMETEOITEMS[key]["organisme"]
                    
          self.meteoHtml+= "</table>\n"
          self.meteoHtml+= "<hr width='65%'>"
          self.meteoHtml+= "<span class='hint'><u>Source</u> : <i><b>API</b> <a href='https://open-meteo.com/' target='blank'>https://open-meteo.com</a>, modèle : %s</i>.</span>" % organismeMeteo
          self.meteoHtml+= "</center>\n"
          
          html+= "</table>\n"
          html+= "</td></tr>\n"
          html+= "<tr><td colspan='4'><hr></td></tr>\n"
          legende = "<u>Légende des indicateurs</u> :\n"
          legende+= "<ul><li><b>&#8600;</b> : %s</li><li><b>&#8608;</b> : %s</li><li><b>&#8599;</b> : %s</li><li><b>Pas de contenu</b> : %s</li></ul> " % (self.dictLegend["e1"], self.dictLegend["e0"], self.dictLegend["e2"], self.dictLegend["e3"])

          html+= "<tr>"
          html+= "<td align=left><fonr style='font-size: 0.8em;'>%s</font></td>" % legende
          html+= "<td align=right valign='top' colspan='2'><fonr style='font-size: 0.8em;'><i>%s, édition le %s</i></font></td>\n" % (self.namePlugin, thedatetime.now().strftime("%d/%m/%Y à %Hh%M"))
          html+= "</tr>\n"
          html+= "</table>" 
          html+= "</body>\n</html>\n"
                        
          maxAverage = 0
          for idStation in self.dataStations.keys() : maxAverage = max(maxAverage, self.dataStations[idStation]['average'])

          colorR = list(np.random.choice(range(256), size=self.counter))
          colorG = list(np.random.choice(range(256), size=self.counter))
          colorB = list(np.random.choice(range(256), size=self.counter))

          hexaColors, labels = [], []
          for i in range(len(colorR)):
              r, g, b = colorR[i], colorG[i], colorB[i]
              hexaColors.append("#{0:02x}{1:02x}{2:02x}".format(clamp(r), clamp(g), clamp(b)))
          
          averageStations = []
          dictDeltaMinutesOscultation = {}
          for idStation in self.dataStations.keys() :
              lbStation = self.dataStations[idStation]['lbStation']
              deltaMinutesOscultation = self.dataStations[idStation]['deltaMinutesOscultation']
              averageStations.append(self.dataStations[idStation]['average'])
              
              if not deltaMinutesOscultation in dictDeltaMinutesOscultation.keys(): dictDeltaMinutesOscultation[deltaMinutesOscultation] = {'counter' : 1, 'listStations' : [lbStation]}
              else :
                  dictDeltaMinutesOscultation[deltaMinutesOscultation]['counter']+= 1
                  dictDeltaMinutesOscultation[deltaMinutesOscultation]['listStations'].append(lbStation)

          averageDatas = round(np.nanmean(averageStations), 2) 
          countInfAverage = 0  
          for idStation in self.dataStations.keys() :
              if self.dataStations[idStation]['average'] < averageDatas : countInfAverage+= 1 
          #BUG : ZERO DIVISION - NO SERVICE FOR THE STATION !! 
          if len(self.dataStations.keys()) > 0.0 : ratioInfAverageDatas = round((100 * countInfAverage) / len(self.dataStations.keys()), 2)
          else : ratioInfAverageDatas = round((100 * countInfAverage) / 1, 2)
            
          #START BLOC SOME FIGURES
          self.statisticsHtml+= "<center>\n"
          self.statisticsHtml+= self.getTitle("Statistiques de service <img src='file:///%s' class='icon' title='%s'></h2>" % (imgTypeData, self.comboTypeInfos.currentText()))
          self.statisticsHtml+= "<br>\n"
          self.statisticsHtml+= "<div class='tableFixHeaderChiffres'>\n"
          self.statisticsHtml+= "<details class='elite'><summary class='elite'><b>Quelques chiffres ...</b></summary>\n" 
          self.statisticsHtml+= "<table>\n"
          self.statisticsHtml+= "   <tr><td>\n"          

          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "hauteur.png" if typeInfos == "H" else "debit.png")
          img = img.replace("\\", "/")
          
          self.statisticsHtml+= """
          <div class="box-infos">
          <center>
          <table border=1 width='80%%'>
          <tr>
          <td width='25%%'>
              <table class="stat-box">
                <tr>
                    <td>
                    <span>
                    <img src="%s" title="Ratio de stations en deçà de la moyenne" width="24px" height="24px" />
                    </span></td>
                    <td><span>%s %s</span></td>
                    <td>%s%%</td>
                </tr>
                <tr>
                   <td colspan="3"><h2 class="number-fs">%s</h2></td>
                </tr>
                </table>
          </td>    
          """ % (img, self.comboTypeInfos.currentText(), "Moyenne" if typeInfos == "H" else "Moyen", ratioInfAverageDatas, averageDatas)

          infosDeltaMinutesOscultation, minDelatMinutes, counterAll, counterMinDeltaMinutes = "", 1000000, 0, 0 
          for deltaMinutesOscultation in dictDeltaMinutesOscultation.keys() :
              counter = dictDeltaMinutesOscultation[deltaMinutesOscultation]['counter']
              listStations = dictDeltaMinutesOscultation[deltaMinutesOscultation]['listStations']
              infosDeltaMinutesOscultation+= "<u><b>%s minutes</b></u> : %s %s<br>" % (deltaMinutesOscultation, counter, listStations)
              minDelatMinutes = min(minDelatMinutes, int(deltaMinutesOscultation))
              counterAll+= counter
              if minDelatMinutes == int(deltaMinutesOscultation) : counterMinDeltaMinutes = counter

          tauxResponse = round((counterAll * 100 )/ self.counter, 2) if self.counter > 0 else 0
          imgFleche = "%s/data/imgs/%s" % (dirname(__file__), "arrow-right.svg")
          imgFleche = imgFleche.replace("\\", "/")

          self.statisticsHtml+= """</td>
          <td width='25%%'>
              <table class="stat-box">
                <tr>
                <td><span>Taux de réponse des stations</span></td>
                <td><span>
                    <img src="file:///%s" alt="" />.
                  </span>
                </td>
                </tr>
                <tr>
                <td colspan="2"><h2 class="number-fs">%s %%</h2></td>
                </tr> 
              </table>
          </td>
          """ % (imgFleche, tauxResponse)

          self.statisticsHtml+= """
          <td width='40%'>
              <table class="stat-box">
               <tr>
                <td>
          """    
          if counterAll == 0 : counterAll = 1  
          ratioDeltaMinutes = round((counterMinDeltaMinutes*100)/counterAll ,2)    
          img = "%s/data/imgs/%s" % (dirname(__file__), "arrow-up-green.svg" if ratioDeltaMinutes > 50.0 else "arrow-down-red.svg")
          img = img.replace("\\", "/")

          self.statisticsHtml+= """
                    &#9201;</td>
                    <td>Délai de scrutation - %s</td>
                    <td>
                    <span class="stat-percentage red">
                    <img src="file:///%s" alt="" />%s%%
                    </span>
                    </td>
                </tr>
                <tr>
                <td colspan="3" style="text-align: justify;">
                <h2 class="infos-fs">""" % (minDelatMinutes, img, ratioDeltaMinutes )

          self.statisticsHtml+= infosDeltaMinutesOscultation  
          self.statisticsHtml+="""</h2>
               </td>
               </tr>
             </tbody>
            </table>
          </td>
          </tr>
          </table>
          </center>
          </div>
          </td></tr>
          </table>
          </details>
          </div>
          """
          #END BLOC SOME FIGURES

          #TABLEAU STATISTIQUES ET SEUILS - APPRECIATION DISPERSION POPULATION MESURES
          """   
          dico = {'typeStats' : typeStats, 'resultat_obs' : resultat_obs, 'date_obs' : date_obs, 'title' :  title,
                  'img' : img, 'minY' : minY, 'maxY' : maxY, 'theMaxDate' : theMaxDate, 'theLastDateHtml' : theLastDateHtml,
                  'typeInfos' : typeInfos, 'idHydro' : idHydro, 'lbStation' : lbStation,'average' : average,
                  'ecarttype' : ecartType, 'coeffReg' : coeffReg, 'constanteReg' : constanteReg, 'stationDicoSeuils' : stationDicoSeuils, 'IC95' : round(IC95, 2)}
          """
          #START BLOC STATISTICS
          self.statisticsHtml+= self.getTitle("Statistiques des mesures")

          legendImg = {"n-1" : {"img": "puce_-1.png", "title" : "Pas de seuil RDI."}, "n0" : {"img": "puce_0.png", "title" : "Pas de dépassement."},
                       "n1" : {"img": "puce_1.png", "title" : "Seuil de vigilance RDI dépassé."}, "n1.1" : {"img": "puce_1.1.png", "title" : "Proche du 1er seuil de débordement."},
                       "n2" : {"img": "puce_2.png", "title" : "Seuil de 1er débordement dépassé."}, "n2.1" : {"img": "puce_2.1.png", "title" : "Proche du seuil critique."},
                       "n3" : {"img": "puce_2.png", "title" : "Seuil critique dépassé."}, "nx" : {"img": "debordement.png", "title" : "Dépassement(s) de seuil(s)"}
                       }

          self.statisticsHtml+= "<br>\n"
          #NEW 4.1.6
          for key in legendImg.keys() :  self.statisticsHtml+= "&nbsp;<img src='file:///%s' class='iconLegend' title='%s'>\n" % (getThemeIcon(legendImg[key]["img"]).replace("\\", "/"), legendImg[key]["title"])


          self.statisticsHtml+= "<br><br>\n"
          self.statisticsHtml+= "<div class='tableFixHeader'>\n"
          self.statisticsHtml+= "  <table>\n"
          self.statisticsHtml+= "     <thead>\n"
          self.statisticsHtml+= "        <tr>\n"
          self.statisticsHtml+= "            <th class='titleStation' colspan='6'><center><b>Station</b></center></th>\n"
          self.statisticsHtml+= "        </tr>\n"          
          self.statisticsHtml+= "        <tr>\n"
          self.statisticsHtml+= "            <th class='indicateur1'>Etendue<br>min - max</th>\n"
          self.statisticsHtml+= "            <th class='indicateur2'>Plage<br>temporelle</th>\n"
          self.statisticsHtml+= "            <th class='indicateur3'>Moyenne</th>\n"
          self.statisticsHtml+= "            <th class='indicateur4'>Ecart-Type<br>ET</th>\n"
          self.statisticsHtml+= "            <th class='indicateur5'>Ecart-InterQuartile<br>(EI)</th>\n"
          self.statisticsHtml+= "            <th class='texte7'>Commentaires</th>\n"
          self.statisticsHtml+= "        </tr>\n"
          self.statisticsHtml+= "     </thead>\n"
          self.statisticsHtml+= "  </table>\n"
          self.statisticsHtml+= "</div>\n"
          self.statisticsHtml+= "<div class='tableFixHead'>\n"


          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "hauteur.png" if typeInfos == "H" else "debit.png")
          img = img.replace("\\", "/")
          lblTypeInfo = self.comboTypeInfos.currentText()          
          if typeInfos == "Q" : uniteMesure, labelLastObs = "m3/s", "dernier"
          else :  uniteMesure, labelLastObs = "m", "dernière" 
          
          icol = 1  
          for idStation in self.dataStations.keys() :
              infosStation = self.dataStations[idStation]
              lbStation = infosStation['lbStation']
              resultat_obs = infosStation['resultat_obs']
              date_obs = infosStation['date_obs']
              theMaxDate = infosStation['theMaxDate'].replace("<br>", "\n")
              minY = infosStation['minY']
              maxY = infosStation['maxY']
              average = infosStation['average']
              coeffReg = infosStation['coeffReg']
              ecartType = infosStation['ecarttype']
              stationDicoSeuils = infosStation['stationDicoSeuils']
              lastObs = resultat_obs[0]
              deltaMinutesOscultation = infosStation['deltaMinutesOscultation']
              IC95 = infosStation['IC95']
              
              lblDateObs = getLitteralDate(date_obs[0], "le")
              etendue = round((maxY-minY),3)

              q1, q2, q3 = getQuartiles(resultat_obs)
              ecartInterQuartile = q3-q1

              data0 = [val for val in resultat_obs if val >= minY and val < q1] 
              data1 = [val for val in resultat_obs if val >= q1 and val < q2]
              data2 = [val for val in resultat_obs if val >= q2 and val < q3]
              data3 = [val for val in resultat_obs if val >= q3]

              nbData0, nbData1, nbData2, nbData3, nbDataTotal = len(data0), len(data1), len(data2), len(data3), len(resultat_obs)
              ratio0 = round(100 * nbData0 / nbDataTotal, 2)
              theoRatio = [25, 50, 75]
              ratio1 = round(100 * nbData1 / nbDataTotal, 2)
              ratio2 = round(100 * nbData2 / nbDataTotal, 2)
              ratio3 = round(100 * nbData3 / nbDataTotal, 2)
              ratioTotal = 100 

              average0 = getMean(data0, 3) if data0 != [] else 0
              average1 = getMean(data1, 3) if data1 != [] else 0
              average2 = getMean(data2, 3) if data2 != [] else 0
              average3 = getMean(data3, 3) if data3 != [] else 0

              ecartType0 = getStDev(data0, 3) if data0 != [] else 0
              IC950 = round(average0-(1.96 * (ecartType0 / sqrt(len(data0)))), 2) if data0 != [] else 0
              intervalleConf0 = [round(average0-IC950, 2), round(average0+IC950, 2)] if data0 != [] else "[ - , - ]"

              ecartType1 = getStDev(data1, 3) if data1 != [] else 0
              IC951 = round(average1-(1.96 * (ecartType1 / sqrt(len(data1)))), 2) if data1 != [] else 0
              intervalleConf1 = [round(average1-IC951, 2), round(average1+IC951, 2)] if data1 != [] else "[ - , - ]"
              
              ecartType2 = getStDev(data2, 3) if data2 != [] else 0
              IC952 = round(average2-(1.96 * (ecartType2 / sqrt(len(data2)))), 2) if data2 != [] else 0
              intervalleConf2 = [round(average2-IC952, 2), round(average2+IC952, 2)] if data2 != [] else "[ - , - ]"

              ecartType3 = getStDev(data3, 3) if data3 != [] else 0
              IC953 = round(average3-(1.96 * (ecartType3 / sqrt(len(data3)))), 2) if data3 != [] else 0
              intervalleConf3 = [round(average3-IC953, 2), round(average3+IC953, 2)] if data3 != [] else "[ - , - ]"

              intervalleConf = [round(average-IC95, 2), round(average+IC95, 2)]

              commentaires, dep, ratiosSeuil, imgStatut, commentaireStatut, nbDepassement = self.getCommentaires(idStation, typeInfos, average, etendue, minY, maxY, coeffReg, ecartInterQuartile, q1, q3, ratio0, (ratio0+ratio1+ratio2), ecartType, date_obs[-1], date_obs[0], stationDicoSeuils, date_obs, resultat_obs, unitInfos)
             
              #SEUILS BLOCK 
              if stationDicoSeuils != {} :

                  htmlSeuils = "<br>\n<center>\n<table border=2 width='90%'>\n"
                  htmlSeuils+= "<tr>\n"

                  ratiosSeuil = []  
                  
                  for seuil in stationDicoSeuils.keys() :
                      valueSeuil = stationDicoSeuils[seuil]['valueSeuil']
                      commentsSeuilRDI = stationDicoSeuils[seuil]['commentsSeuilRDI'].replace("<br>", " ")
                      ratioSeuil = round( (lastObs *100) / valueSeuil, 2 )
                      ratiosSeuil.append(ratioSeuil)

                      htmlSeuils+= "<tr style='height: 15px;'>\n"
                      htmlSeuils+= "   <td style='width : 25%%; text-align: right;'><small>%s %s : %s (%s %%)</small></td>\n"  % (valueSeuil, uniteMesure, commentsSeuilRDI, ratioSeuil)
                      htmlSeuils+= "   <td width='75%'>\n"
                      htmlSeuils+= "       <div class='month-bar-light-back' style='width: 100%;height: 15px;'></div>\n"""
                      htmlSeuils+= "       <div class='month-bar-light' style='width: %s%%;height: 15px;'></div>\n" % int(ratioSeuil)
                      htmlSeuils+= "   </td>\n"
                      htmlSeuils+= "</tr>\n"

                  commentaireStatut, imgStatut, lblImgStatut = getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, uniteMesure, typeInfos)
                  
                  htmlSeuils+= "</table>\n</center>\n"
                  htmlSeuils+= "<br>\n"

              else : commentaireStatut, imgStatut, lblImgStatut = getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, uniteMesure, typeInfos)

              #NEW 4.1.0  
              if nbDepassement > 0 :
                 pluriel = "s" if nbDepassement > 1 else ""
                 imgDepassement = "<img src='file:///%s' class='icon' title='%s mesure%s supérieure%s aux seuils de dépassement et/ou de débordement sur la période'> " % (getThemeIcon("debordement.png"), nbDepassement, pluriel, pluriel)
              else : imgDepassement = ""
              
              self.statisticsHtml+= "<details class='elite'><summary class='elite'><b>%s (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>) <img src='%s' class='icon' title='%s'> %s</b></summary>\n" % (lbStation, links[idStation], idStation, imgStatut, lblImgStatut, imgDepassement) 
              self.statisticsHtml+= "<table>\n"
              self.statisticsHtml+= "<tbody>\n"            
              self.statisticsHtml+= "<tr class='titleStation'>\n"
              self.statisticsHtml+=  "   <td>%s</td><td class='texte1' colspan='5'><small>%s %s %s %s, %s</small></td>\n" % (toolsBarStation[idStation], labelLastObs, lblTypeInfo, lastObs, uniteMesure, lblDateObs) #NOM DE LA STATION
              self.statisticsHtml+= "</tr>\n"
              self.statisticsHtml+= "<tr>\n"
              self.statisticsHtml+=  "   <td class='indicateur1' width='100px;'>%s<br><small>%s - %s</small></td>\n" % (etendue, minY, maxY) #ETENDUE - MIN - MAX
              self.statisticsHtml+=  "   <td class='indicateur2'><small>%s<br>%s</small></td>\n" % (date_obs[-1], date_obs[0])#PLAGE TEMPORELLE
              self.statisticsHtml+=  "   <td class='indicateur3'>%s</td>\n" %  average #MOYENNE
              self.statisticsHtml+=  "   <td class='indicateur4'>%s</td>\n" %  ecartType #ECART-TYPE
              self.statisticsHtml+=  "   <td class='indicateur5'>%s</td>\n"  % round(ecartInterQuartile, 3) #ECART-INTERQUARTILE
              self.statisticsHtml+=  "   <td class='texte7'>%s</td>\n" %  commentaires #COMMENTAIRES
              self.statisticsHtml+= "</tr>\n"              
              self.statisticsHtml+= "<tr>\n"
              self.statisticsHtml+=  "   <td colspan='6' style='background-color: #ddd; text-align: center;'><center>Statistiques détaillées :</center></td>\n"
              self.statisticsHtml+= "</tr>\n"
              self.statisticsHtml+= "<tr>\n"
              self.statisticsHtml+=  "   <td class='texte1' colspan='6'>\n"
              self.statisticsHtml+=  "      <table style='font-size: smaller;'>\n"
              self.statisticsHtml+=  "        <tr style='background-color: #1C6EA4; box-shadow: 0px 5px 14px 0px #0e1635; color: white;'><td style='text-align: right; margin-right: 2px;'>Intervalle :</td><td>Valeur minimale</td><td>Qrt.1</td><td>Qrt.2</td><td>Qrt.3</td><td>Total</td></tr>\n"
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Valeur :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td> - </td></tr>\n" % (minY, q1, q2, q3)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Intervalle :</td><td>[ %s - %s [</td><td>[ %s - %s [</td><td>[ %s - %s [</td><td>[ %s - %s ]</td><td>[ %s - %s ]</td></tr>\n" % (minY, q1, q1, q2, q2, q3, q3, maxY, minY, maxY)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (nbData0, nbData1, nbData2, nbData3, nbDataTotal)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Part de la sous-population (%%) :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (ratio0, ratio1, ratio2, ratio3, ratioTotal)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Moyenne pour la sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (average0, average1, average2, average3, average)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Ecart-type pour la sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (ecartType0, ecartType1, ecartType2, ecartType3, ecartType)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Indice de confiance (IC95)<br>sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (IC950, IC951, IC952, IC953, IC95)
              self.statisticsHtml+=  "      </table>\n"  
              self.statisticsHtml+=  "   </td>\n"
              self.statisticsHtml+= "</tr>\n"

              if stationDicoSeuils != {} :
                 for seuil in stationDicoSeuils.keys() :
                     valueSeuil = stationDicoSeuils[seuil]['valueSeuil']
                     commentsSeuilRDI = stationDicoSeuils[seuil]['commentsSeuilRDI'].replace("<br>", " ")

                     subHtml, nbDebord = seriesUpSeuils(date_obs, resultat_obs, deltaMinutesOscultation, valueSeuil, seuil, commentsSeuilRDI, uniteMesure)   

                     self.statisticsHtml+= "<tr>\n"
                     self.statisticsHtml+=  "   <td colspan='6' style='background-color: #ddd; text-align: center;'><small>%s</small></td>\n" % subHtml
                     self.statisticsHtml+= "</tr>\n"

                     if nbDebord == 0 : break   


              if stationDicoSeuils != {} :  
                  self.statisticsHtml+= "<tr>\n"
                  self.statisticsHtml+= "<td colspan='6'>\n"
                  self.statisticsHtml+= htmlSeuils
                  self.statisticsHtml+= "</td>\n"
                  self.statisticsHtml+= "</tr>\n"

              self.statisticsHtml+= "</table>\n"  
              self.statisticsHtml+= "</details>\n"           

              self.statisticsHtml+= "<tr><td colspan=6 class='cellStation' height='5px'></td></tr>\n"

          self.statisticsHtml+= "</tbody>\n"  
          self.statisticsHtml+= "</table>\n"
          self.statisticsHtml+= "</div>\n"
          self.statisticsHtml+= "</center>\n"
          self.statisticsHtml+= """<br>\n"""
          #END BLOC STATISTICS
 
          #START BLOC GRAPHIC BY STATION
          dimObs = -1
          if self.comboDate.currentIndex() < 3 : labelObs = "Série courte : %s" % self.comboDate.currentText()
          elif self.comboDate.currentIndex() >= 3 and self.comboDate.currentIndex() < 6 : dimObs, labelObs = 150, "Série moyenne : %s" % self.comboDate.currentText()
          elif self.comboDate.currentIndex() >= 6 : dimObs, labelObs = 300, "Série longue : %s" % self.comboDate.currentText()

          self.graphicsHtml+= "<center>\n"
          self.graphicsHtml+= self.getTitle("<h2>Graphiques des stations (<small>%s</small>) <img src='file:///%s' class='icon' title='%s'></h2>" % (labelObs, imgTypeData, self.comboTypeInfos.currentText())) 

          #NEW 4.1.5 DUCON ajouter le code javascript dans Params + images (icons déjà copiés dans le dossier et ajouter à IMAGESFORBULLETIN !)
          self.graphicsHtml+= "<br><br><br>\n"
          self.graphicsHtml+= "<button id=\"btnExpandAllDetails\" onclick=\"javascript:openAll();\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Ouvrir tous les groupes\"></button>\n" % self.openAllNodes
          self.graphicsHtml+= "<button id=\"btnCloseAllDetails\" onclick=\"javascript:closeAll();\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Fermer tous les groupes\"></button>\n" % self.closeAllNodes
          self.graphicsHtml+= "<button id=\"btnInvAllDetails\" onclick=\"javascript:invertAll();\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Inverser les ouvertures des groupes\"></button>\n" % self.invertAllNodes
          self.graphicsHtml+= "<br>\n"


          self.graphicsHtml+= "<table width='90%'><tr><td>\n"
          ic = 0

          #NEW 4.1.5
          blocInitJS, blocInitGraph, blocInitDataForJS, blocReinit, blocDestroy = "", "", "", "", ""

          
          for idStation in self.dataStations.keys() :

              resultat_obs = self.dataStations[idStation]['resultat_obs']
              totalObs = len(resultat_obs)
              sDimObs = len(resultat_obs) if dimObs == -1 else min(len(resultat_obs), dimObs)
              
              date_obs = self.dataStations[idStation]['date_obs'][:sDimObs]
              lbStation = self.dataStations[idStation]['lbStation']
              resultat_obs = self.dataStations[idStation]['resultat_obs'][:sDimObs]
              stationDicoSeuils = self.dataStations[idStation]['stationDicoSeuils'] 

              labels, mesures = [], []
              for i in range(len(date_obs)):
                  labels.insert(0, date_obs[i].strftime("%d/%m/%Y à %Hh%M"))
                  mesures.insert(0, resultat_obs[i])
            
              dateDeb = getLitteralDate(date_obs[-1], "")
              dateFin = getLitteralDate(date_obs[0], "")

              #NEW 4.1.5
              self.graphicsHtml+= "<details class='elite'><summary class=\"summaryTheme\">"
              self.graphicsHtml+= " %s (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>) - %s dernières mesures sur %s totales. Du %s au %s.\n" % (lbStation, links[idStation], idStation, sDimObs, totalObs, dateDeb, dateFin)
              self.graphicsHtml+= "</summary>\n"
              self.graphicsHtml+= "<table><tbody><tr class='titleStation'><td class='texte1'>%s</td>\n" % toolsBarStation[idStation]
              self.graphicsHtml+= "<td><canvas id=\"line-chart-%s\" name=\"echo\" width='800px' height='400px'></canvas>\n" % (ic)
              self.graphicsHtml+= "<div><center><button onclick=\"javascript:addSeuil(lineChart%s, %s);\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Ajouter un seuil\"></button>\n" % (ic, sDimObs, self.editGraph)
              self.graphicsHtml+= "<button onclick=\"javascript:reinitAll('line-chart-%s');\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Réinitialiser le graphe\"></button></center></div>\n" % (ic, self.reloadGraph)
              self.graphicsHtml+= "</td></tr></tbody></table>\n"
              self.graphicsHtml+= "</details>\n"
              self.graphicsHtml+= "<br>\n"

              blocInitJS+= "var lineChart%s=null;\n" % ic
              blocDestroy+= "if (idChart=='line-chart-%s'){if(lineChart%s!=null){lineChart%s.destroy();}}\n"% (ic, ic, ic)
              blocReinit+= "if (idChart=='line-chart-%s'){lineChart%s = initGraph(idChart);}\n" % (ic, ic) if blocReinit == "" else "else if (idChart=='line-chart-%s'){lineChart%s = initGraph(idChart);}\n" % (ic, ic)
              blocInitGraph+= "var lineChart%s = initGraph('line-chart-%s');\n" % (ic, ic) 

              if blocInitDataForJS == "" :
                 blocInitDataForJS+= "if (idChart=='line-chart-%s'){\n" % ic
              else :
                 blocInitDataForJS+= "else if (idChart=='line-chart-%s'){\n" % ic

              blocInitDataForJS+= "var lineChart = new Chart(canvas, {\n"
              blocInitDataForJS+= "    type: 'line',\n"
              blocInitDataForJS+= "    data: {\n"
              blocInitDataForJS+= "      labels: %s,\n" % labels
              blocInitDataForJS+= "      datasets: [\n"
              blocInitDataForJS+= "{\n"
              blocInitDataForJS+= "data: %s,\n" % mesures
              blocInitDataForJS+= "label: \"%s [%s]\",\n" % (lbStation, idStation)
              blocInitDataForJS+= "backgroundColor: 'rgba(55, 165, 235, 0.5)',\n" 
              blocInitDataForJS+= "borderColor: \"#4bf8ed\",\n"
              blocInitDataForJS+= "fill: false}\n" if typeInfos == "H" else  "fill: true}\n" 

              #ADD SEUILS INFORMATIONS
              maxMesures = max(mesures)
              
              colorsSeuil = ["#FEFF05", "#FF7B05", "#FF0000"]
              icolors = 0
              if stationDicoSeuils != {} :
                 for key in stationDicoSeuils.keys():                     
                     mesures = []
                     valueSeuil = stationDicoSeuils[key]['valueSeuil']

                     commentsSeuilRDI = stationDicoSeuils[key]['commentsSeuilRDI'].replace("<br>", " ")
                                          
                     if valueSeuil != -1 :
                        for i in range(len(date_obs)): mesures.insert(0, valueSeuil)                        
                        condVisibility = testSeuilVisibility(idStation, valueSeuil, maxMesures)
                        
                        blocInitDataForJS+= ",\n"
                        blocInitDataForJS+= "{\n"
                        blocInitDataForJS+= "data: %s,\n" % mesures
                        blocInitDataForJS+= "label: \"%s [%s]\",\n" % (commentsSeuilRDI, valueSeuil)
                        blocInitDataForJS+= "borderColor: \"%s\",\n" % (colorsSeuil[icolors])
                        blocInitDataForJS+= "showLine: true,\n"
                        blocInitDataForJS+= "hidden: %s,\n" % condVisibility
                        blocInitDataForJS+= "fill: false}\n"
                        icolors+= 1 if icolors < 3 else 0                


              blocInitDataForJS+= "     ]\n"
              blocInitDataForJS+= "   },\n"              
              blocInitDataForJS+= "   options: {title: {display: true, text: 'Courbe des stations (%s)' }, elements: {point:{radius: 0}}}\n" % ("hauteur en m" if typeInfos == "H" else "débit m3/s")
              blocInitDataForJS+= "});\n"
              blocInitDataForJS+= "}\n"

              ic+= 1
              #END BLOC GRAPHIC
              
          self.graphicsHtml+= "</td>\n</tr>\n</table>\n<br>\n"
          self.graphicsHtml+= "</center>\n"

          #NEW 4.1.5 
          self.graphicsHtml+= "</center>\n"
          self.graphicsHtml+= "<script>\n"
          self.graphicsHtml+= blocInitJS
          self.graphicsHtml+= "function reinitAll(idChart){\n"
          self.graphicsHtml+= "         clearChart(idChart);\n"
          self.graphicsHtml+= blocReinit
          self.graphicsHtml+= "}\n"


          self.graphicsHtml+= "function clearChart(idChart) {\n"
          self.graphicsHtml+= "         event.preventDefault();\n"
          self.graphicsHtml+= "         var parent = document.getElementById(idChart);\n"
          self.graphicsHtml+= "         parent.innerHTML ='<canvas id='+idChart+' width=\"1450\" height=\"450\" style=\"border: 1px solid black;\"></canvas>';\n"
          self.graphicsHtml+= blocDestroy
          self.graphicsHtml+= "}\n"

          
          self.graphicsHtml+= "function initGraph(idChart) {\n"
          self.graphicsHtml+= "         var canvas = document.getElementById(idChart);\n"
          self.graphicsHtml+= " 	var context = canvas.getContext('2d');\n"
          self.graphicsHtml+= blocInitDataForJS
          self.graphicsHtml+= "return lineChart }\n"

          self.graphicsHtml+= "function addData(chart, label, color, data) {\n"
          self.graphicsHtml+= "        chart.data.datasets.push({label: label, fill : false, backgroundColor : color, borderWidth: 1.0, borderDash: [6, 6], borderColor : color, data: data});\n"
          self.graphicsHtml+= "        chart.update();\n"
          self.graphicsHtml+= "}\n"

          self.graphicsHtml+= "function addSeuil(lineChart, dimObs){\n"
          self.graphicsHtml+= "         let nameSeuil = prompt(\"Etiquette pour le seuil :\");\n"
          self.graphicsHtml+= "         if (nameSeuil) {\n"
          self.graphicsHtml+= "            let seuil = parseFloat(prompt(\"Valeur du seuil à ajouter :\"));\n"
          self.graphicsHtml+= "            if (seuil) { \n"
          self.graphicsHtml+= "               if (Number.isInteger(Number(seuil)) || (Number(seuil) === seuil && seuil % 1 !== 0)) {\n"
          self.graphicsHtml+= "                  arr = Array(dimObs).fill(seuil);\n"
          self.graphicsHtml+= "                  addData(lineChart, nameSeuil + ' [' +seuil + ']', '#ff0000', arr);\n"
          self.graphicsHtml+= "                  }\n"
          self.graphicsHtml+= "            }\n"
          self.graphicsHtml+= "          }\n"
          self.graphicsHtml+= "}\n"

          self.graphicsHtml+= blocInitGraph

          #NEW 4.1.5
          self.graphicsHtml+= "\n"
          self.graphicsHtml+= "/*\n"
          self.graphicsHtml+= "//EXEMPLE DE SEUIL PERMANENT\n"
          self.graphicsHtml+= "//paramètre d'Array : nombre d'observation, paramètre de fill : valeur du seuil à représenter\n"
          self.graphicsHtml+= "arr = Array(150).fill(1.025);\n"
          self.graphicsHtml+= "//paramètre d'addData : l'objet chart ciblé, l'étiquette, la couleur de l'étiquette, la collection arr\n"
          self.graphicsHtml+= "addData(lineChart3, 'Mon seuil mano [1.025]', '#ff0000', arr);\n"
          self.graphicsHtml+= "*/\n"
          self.graphicsHtml+= "</script>\n"
          self.graphicsHtml+= "</body>\n"

          #START BLOC PROFILS STATIONS
          fieldCdStationH = self.tableFieldsStations['CdStationH']
          if condProfils :
              
              newlayer = QgsVectorLayer("Point?crs=EPSG:2154", "projPoint", "memory")
              provider = newlayer.dataProvider()
              newlayer.startEditing()
              feature = QgsFeature()
              
              mem_layer_data = newlayer.dataProvider()
              attr = self.layer.dataProvider().fields().toList()
              mem_layer_data.addAttributes(attr)
              newlayer.updateFields()

              condCrs = (self.layer.crs() == self.layerTerrain.crs())
              transformCrs = None
              if not condCrs : transformCrs = QgsCoordinateTransform(self.layer.crs(), self.layerTerrain.crs(), QgsProject().instance())

              self.profilsHtml+= "<center>\n"
              #NEW 4.1.0
              self.profilsHtml+= self.getTitle("<h2>Profils en travers des stations <img src='file:///%s' class='icon' title='%s'></h2>") % (imgTypeData, self.comboTypeInfos.currentText())
              #NEW 4.1.5
              self.profilsHtml+= "<br><br><br>\n"
              self.profilsHtml+= "<button id=\"btnExpandAllDetails\" onclick=\"javascript:openAll();\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Ouvrir tous les groupes\"></button>\n" % self.openAllNodes
              self.profilsHtml+= "<button id=\"btnCloseAllDetails\" onclick=\"javascript:closeAll();\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Fermer tous les groupes\"></button>\n" % self.closeAllNodes
              self.profilsHtml+= "<button id=\"btnInvAllDetails\" onclick=\"javascript:invertAll();\"><img src=\"file:///%s\" class=\"iconLegend\" title=\"Inverser les ouvertures des groupes\"></button>\n" % self.invertAllNodes
              self.profilsHtml+= "<br>\n"

              ic, listStations = 0, []
  
              model = self.listStations.model()
              self.index = 0

              for index in range(1, nbTotal):
                  item = model.item(index)
                  if  item.checkState() == Qt.Checked :
                      infos = item.text().split(" || ")
                      idStation, lbStation, idHydro = infos[0], infos[1], infos[2]
                      listStations.append(idStation)
              
              vlayerLines = QgsVectorLayer('LineString?crs=epsg:2154', 'lines', 'memory')
              proLayerLines = vlayerLines.dataProvider()
              proLayerLines.addAttributes(attr)
              vlayerLines.updateFields()
              featLayerLines = QgsFeature()

              #NEW 4.1.5  
              posMini = 170
              jsData = ""
              bodyHtml = ""
              
              for station in listStations :
                  
                  self.layer.setSubsetString("")
                  sexp = "\"{}\" = '{}'".format(fieldCdStationH, station)
                  self.layer.setSubsetString(sexp)

                  for feat in self.layer.getFeatures():

                    #NEW 4.1.5
                    idStation = feat["CdStationH"]
                    lbStation = feat["LbStationH"]
                    seuilsRDI = self.dataStations[idStation]['stationDicoSeuils'] if dicoHasKey(self.dataStations, idStation) else {} 
                    tendance = self.dataStations[idStation]['tendance'] if dicoHasKey(self.dataStations, idStation) else '<b>&#8608;</b>'

                    counterSeuils = 0
                    for seuil in seuilsRDI.keys() :
                        if seuilsRDI[seuil]['valueSeuil'] > -1 : counterSeuils+= 1
                    
                    infosStation = self.dataStations[idStation] if dicoHasKey(self.dataStations, idStation) else []
                    lastObs = infosStation['resultat_obs'][0] if infosStation != [] else 0 
                    geom  =  feat.geometry()
                    point = feat.geometry().asPoint()
                    attributes = feat.attributes()

                    #SELECT BY LOCATION
                    #NEW 4.1.5 distance 25 -> 50
                    result = bufferPlus(self.layer, distance=50, output=r'TEMPORARY_OUTPUT')
                    bufferLayer = result['OUTPUT']
                    
                    result = processing.run("native:selectbylocation", { 'INPUT' : self.layerWater, 'INTERSECT' : bufferLayer, 'METHOD' : 0, 'PREDICATE' : [0] })
                    #NEW 4.1.5
                    line = None
                    for feat in self.layerWater.selectedFeatures() : line = feat.geometry(); break

                    if line :                  
                        newPoint = line.nearestPoint(geom)                         
                        hauteurRef = 0
                        
                        if newPoint :
                            
                            altiRef = round(getAltiPoint(self.layerTerrain, geom.asPoint(), condCrs, transformCrs), 2)
                            hauteurRef = altiRef + lastObs
                            
                            newLine = QgsGeometry().fromPolyline([QgsPoint(point.x(), point.y()), QgsPoint(newPoint.asPoint().x(), newPoint.asPoint().y())]) 
                            v_layer = QgsVectorLayer('LineString?crs=epsg:2154', 'line %s' % station, 'memory')
                            pr = v_layer.dataProvider()
                            seg = QgsFeature()
                            seg.setGeometry(newLine)
                            pr.addFeatures([ seg ])
                            v_layer.commitChanges()
                           
                            distance = ceil(geom.distance(newPoint))
                            #NEW 4.1.5
                            stepDistance = 300
                            alg_params = {'START_DISTANCE' : abs(stepDistance - distance),
                                          'END_DISTANCE' : stepDistance,
                                          'INPUT' : v_layer,
                                          'OUTPUT' : 'TEMPORARY_OUTPUT' }
                            result = processing.run('native:extendlines', alg_params)
                            layerLine = result['OUTPUT']
                            
                            for featLine in layerLine.getFeatures() : geomLine = featLine.geometry()
                            featLayerLines.setGeometry(geomLine)
                            featLayerLines.setAttributes(attributes)
                            proLayerLines.addFeatures([featLayerLines])

                            #NEW 4.1.5
                            stepper = 5 
                            result = processing.run("native:pointsalonglines",{'DISTANCE' : stepper,
                                                                               'END_OFFSET' : 0, 
                                                                               'INPUT' : layerLine,  
                                                                               'OUTPUT' : 'TEMPORARY_OUTPUT', 
                                                                               'START_OFFSET' : 0 })
                            layerResult = result['OUTPUT']

                            altitudesProfil = []
                            #NEW 4.1.5
                            listValuesTerrain = []
                            listValuesWater = []
                            listValuesLit = []
                            listValueSky = []                            
                            
                            for featPoints in layerResult.getFeatures() :
                                geomPoint = featPoints.geometry().asPoint()
                                altiPoint = getAltiPoint(self.layerTerrain, geomPoint, condCrs, transformCrs)
                                altitudesProfil.append(round(altiPoint, 2))


                            #CREATION DES DICTIONNAIRES DE DONNEES
                            correction = 30
                            minAlti = round(min(altitudesProfil), 2)
                            maxAlti = round(max(altitudesProfil), 2) + correction - minAlti
                              
                            midPos = int(len(altitudesProfil)/2)
                            miniTerrain = altitudesProfil[midPos-10:midPos+10]
                            averageMiniTerrain = mean(miniTerrain)
                            minMiniValue, maxMiniValue = min(miniTerrain), max(miniTerrain)
                              
                            sizeMapCanvas = 1560
                              
                            n = 0
                            xValue = altiRef + lastObs
                            index = 0
                            first = True
                            x = 0
                            jsDataStation = "/* ------------------------ BLOC STATION %s---------------------------------*/\n" % idStation
                            x0, alti0, rAltiRef, xAltiRef = 0, 0, 10000, 0

                            if altitudesProfil !=[] :
                                bodyHtml+= "<details class='elite'><summary class=\"summaryTheme\">"
                                bodyHtml+= " %s (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>) :\n" % (lbStation, links[idStation], idStation)
                                bodyHtml+= "</summary><table><tbody><tr class='titleStation'><td class='texte1'>%s</td>\n" % toolsBarStation[idStation]
                                bodyHtml+= "<td><canvas id='myCanvas%s' class='canvasProfil' width='%s' height='450'></canvas></td></tr></tbody></table>\n" % (idStation, sizeMapCanvas)
                                bodyHtml+= "</details><br>"

                                for alti in altitudesProfil :
                                      rAlti = round(alti + correction - minAlti, 2)
                                      if rAlti < rAltiRef : rAltiRef, xAltiRef = rAlti, (x/stepper)
                                      listValuesTerrain.append("{x: %s,y: %s}," % (x, rAlti))
                                      listValueSky.append("{x: %s,y: %s}," % (x, rAlti))
                                      if alti <= hauteurRef :
                                         if first : 
                                            listValuesLit.append("{x: %s,y: %s}," % (x, rAlti)) 
                                            x0, alti0 = x, rAlti
                                            listValuesWater.append("{x: %s,y: %s}," % (x, rAlti))
                                         else :
                                            listValuesWater.append("{x: %s,y: %s}," % (x, rAlti)) 
                                      x+= stepper

                                jsDataStation+= "var dataMNT%s = [" % idStation
                                for i in range(len(listValuesTerrain)): jsDataStation+= "%s" % (listValuesTerrain[i])
                                jsDataStation+= "];\n"
                                  
                                listValueSky.insert(0, listValuesTerrain[0])
                                listValueSky.insert(0, "{x: 0,y: %s}," % (maxAlti+25))
                                listValueSky.append("{x: %s,y: %s}, " % (x-stepper, (maxAlti+25))) 
                                listValueSky.append("{x: 0,y: %s}" % (maxAlti+25))
                                jsDataStation+= "var dataSky%s = [" % idStation
                                for i in range(len(listValueSky)): jsDataStation+= "%s" % (listValueSky[i])
                                jsDataStation+= "];\n"

                                listValuesLit.append("{x: %s,y: %s}" % (x0, alti0 ))
                                jsDataStation+= "var dataBed%s = [" % idStation
                                for i in range(len(listValuesLit)): jsDataStation+= "%s" % (listValuesLit[i])
                                jsDataStation+= "];\n"
                                  
                                listValuesWater.append(listValuesWater[0]) 
                                jsDataStation+= "var dataWater%s = [" % idStation
                                for i in range(len(listValuesWater)): jsDataStation+= "%s" % (listValuesWater[i])
                                jsDataStation+= "];\n"

                                jsData+= jsDataStation
                  
                                percent = floor(int((100 / (counterSeuils+3))))
                                heightTable = (counterSeuils+3) * 50
                                basePosTable = 100
                         
                                jsData+= "\nvar myLineChart%s = new LineChart({canvasId: \"myCanvas%s\", minX: 0, minY: %s, maxX: %s, maxY: %s, unitsPerTickX: 5, unitsPerTickY: 5});\n" % (idStation, idStation, 0, len(altitudesProfil)*stepper, maxAlti+25)
                                
                                #SKYS
                                color1, color2, color3, isNightly = '#F59F22', '#FFFFCC', '#2980B9', False 
                                if idStation in dictMeteo.keys() :
                                   weatherCode = dictMeteo[idStation] 
                                   if weatherCode == 2 : color1, color2, color3 = '#FFFFFF', '#6DD5FA', '#2980B9'
                                   elif weatherCode == 3 : color1, color2, color3 = '#DDD', '#BBB', '#EEE'
                                   elif weatherCode in (95, 96, 99) : color1, color2, color3 = '#A3A3A3', '#999', '#000'
                                   elif weatherCode in (53, 55, 61, 63, 65, 80, 82) : color1, color2, color3 = '#000', '#CCC', '#EEE'
                                   elif weatherCode in (48, 71, 73, 75, 77, 85) : color1, color2, color3 = '#C9C9C9', '#D3D3D3', '#000'
                                   elif weatherCode > 99 : color1, color2, color3, isNightly = '#000', '#4A4A4A', '#888888', True
                                   else : pass
                                             
                                lblBedWater = "Lit du cours d'eau"
                                colorTitle = "white" if isNightly else "black"
                                
                                imgProfilStation = "%s" % join(dirname(__file__), "data/stations/L_%s.webp" % idStation)
                                imgProfilStation = imgProfilStation.replace("\\", "/")
                                imgWeatherCode = getThemeIcon("%s.png" % dictMeteo[idStation] if idStation in dictMeteo.keys() else 0)
                                imgWeatherCode = imgWeatherCode.replace("\\", "/")
                                #TERRAIN WITH TRANSFORM  
                                jsData+= "myLineChart%s.drawTerrain('myCanvas%s', dataSky%s, '%s', '%s', '%s', 1, false, true, false);\n" % (idStation, idStation, idStation, color1, color2, color3)
                                if isNightly : jsData+= "myLineChart%s.drawStars('myCanvas%s');\n" % (idStation, idStation)
                                jsData+= "myLineChart%s.drawTerrain('myCanvas%s', dataMNT%s, '#40B21B', '#3E4A30', '#1B4C0C', 1, false, false, false);\n" % (idStation, idStation, idStation)                                
                                jsData+= "myLineChart%s.drawWater('myCanvas%s', dataWater%s, '#2A80DD', 1);\n" % (idStation, idStation, idStation)
                                jsData+= "myLineChart%s.drawLine('myCanvas%s', dataBed%s, 'blue', 0.5, true);\n" % (idStation, idStation, idStation)
                                jsData+= "myLineChart%s.drawXAxis('myCanvas%s', 'black', 'black', 'middle');\n" % (idStation, idStation)
                                jsData+= "myLineChart%s.drawYAxis('myCanvas%s', 'black', 'black', 'left', %s, %s);\n" % (idStation, idStation, correction, minAlti)

                                jsData+= "myLineChart%s.drawTitle('myCanvas%s', \"%s\", '%s', 0, 0, %s, 55, '0');\n" % (idStation, idStation, lbStation, colorTitle, floor(sizeMapCanvas/2)-len(lbStation)-60)
                                jsData+= "myLineChart%s.drawImage('myCanvas%s', \"file:///%s\", %s, 45, 64, 64);\n" % (idStation, idStation, imgWeatherCode, floor(sizeMapCanvas/2)-len(lbStation)-25-110)                                
                                jsData+= "myLineChart%s.drawImage('myCanvas%s', \"file:///%s\", %s, 140, 360, 255);\n\n" % (idStation, idStation, imgProfilStation, sizeMapCanvas-430)
                                jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"%s\", 0, 0, %s, %s, \"#FFFFFF\", false, false, '14px Arial, verdana, sans-serif');\n" % (idStation, idStation, lblBedWater, floor(sizeMapCanvas/2)-len(lblBedWater)-20, 380)
                                jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"Point A\", 0, 0, 60, 5, \"#000000\", true, false, '14px Arial, verdana, sans-serif');\n" % (idStation, idStation)
                                jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"Point B\", 0, 0, %s, 5, \"#000000\", true, false, '14px Arial, verdana, sans-serif');\n" % (idStation, idStation,sizeMapCanvas-110 )

                                sHeight = 300
                                basePB = sHeight + 50
                                ratio = 50

                                if infosStation != [] :
                                    jsData+= "var dataSCALE%s = [{x: 80, y: 50}, {x: 80, y: %s}];\n" % (idStation, basePB)
                                    jsData+= "myLineChart%s.drawSimpleLine('myCanvas%s', dataSCALE%s, 'black', 0.5, true);\n" % (idStation, idStation, idStation)
                                   
                                    jsData+= "var dataPB%s = [{x: 80, y: 350}, {x: 140, y: 350}];\n" % idStation
                                    jsData+= "myLineChart%s.drawSimpleLine('myCanvas%s', dataPB%s, 'black', 0.5, true);\n" % (idStation, idStation, idStation)
                                    jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"Tendance :\", 0, 0, 165, 340, 'rgba(0, 0, 255, 1.0)', false, false, '14px Arial, verdana, sans-serif');\n" % (idStation, idStation)
                                    jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"Dernière hauteur : %s m.\", 0, 0, 165, 310, 'rgba(0, 0, 255, 1.0)', false, false, '14px Arial, verdana, sans-serif');\n" % (idStation, idStation, lastObs)

                                    #HAUSSE 
                                    if tendance == "<b>&#8599;</b>" : jsData+= "myLineChart%s.drawArrow('myCanvas%s', 240, 360, 270, 340, 'rgba(0, 0, 255, 0.5)');\n" % (idStation, idStation)
                                    #BAISSE 
                                    elif tendance == "<b>&#8600;</b>" : jsData+= "myLineChart%s.drawArrow('myCanvas%s', 240, 360, 270, 380, 'rgba(0, 0, 255, 0.5)');\n" % (idStation, idStation)
                                    #CONSTANT
                                    else : jsData+= "myLineChart%s.drawArrow('myCanvas%s', 240, 360, 280, 360, 'rgba(0, 0, 255, 0.5)');\n" % (idStation, idStation)  

                                    if counterSeuils > 0 :
                                       for i in range(1, 4, 1):
                                           seuil = "S%s" % i
                                           if seuil in seuilsRDI.keys() :
                                              value = seuilsRDI[seuil]['valueSeuil']
                                              labelSeuil = seuilsRDI[seuil]['commentsSeuilRDI'].replace("<br>", " ")
                                              if value > -1 :
                                                 yPos = basePB-int(value*ratio)
                                                 colorStatut = "rgba(0, 255, 0, 0.5)" if lastObs < value else "rgba(255, 0, 0, 0.5)"
                                                 jsData+= "var dataSEUIL%s_%s = [{x: 80, y: %s}, {x: 140, y: %s}];\n" % (idStation, i, yPos, yPos)
                                                 jsData+= "myLineChart%s.drawSimpleLine('myCanvas%s', dataSEUIL%s_%s, '%s', 2, true);\n" % (idStation, idStation, idStation, i, "white")
                                                 jsData+= "myLineChart%s.drawCircle('myCanvas%s', %s, %s, '%s');\n" % (idStation, idStation, 145, yPos, colorStatut)
                                                 jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"%s %s m.\", 0, 0, 165, %s, '%s', false, false, '11px Arial, verdana, sans-serif');\n" % (idStation, idStation, labelSeuil, value, yPos-20, "white") 

                                    yPos = basePB-int(lastObs*ratio) 
                                    jsData+= "var dataLASTOBS%s = [{x: 110, y: 350}, {x: 110, y: %s}];\n" % (idStation, yPos)
                                    jsData+= "myLineChart%s.drawSimpleLine('myCanvas%s', dataLASTOBS%s, 'rgba(0, 0, 255, 0.5)', 40, true);\n" % (idStation, idStation, idStation) 
                                    jsData+= "\n"

                                else :
                                    jsData+= "myLineChart%s.drawLabel('myCanvas%s', \"Pas de service pour la station.\", 0, 0, 165, 340, 'rgba(255, 0, 0, 1.0)', true, true, '20px Arial, verdana, sans-serif');\n" % (idStation, idStation)

                        else :  bodyHtml+= "<div style='width:%spx; height: 50px; color: #FFF; background: linear-gradient(to bottom, rgba(66, 103, 133, 1) 0%%, rgba(53, 204, 116, 1) 52%%, rgba(199, 198, 139, 1) 100%%); margin-top: 5px;'><center><h2>Pas de représentation pour %s (%s).</h2></center></div><br>&nbsp;<br>\n" % (sizeMapCanvas, lbStation, idStation)


              self.profilsHtml+= JSCANVAS
              self.profilsHtml+= "\n<script>\n"
              self.profilsHtml+= "window.onload = function () {\n"
              self.profilsHtml+= jsData
              self.profilsHtml+= "\n};</script>\n"
              self.profilsHtml+= "<body>\n"
              self.profilsHtml+= bodyHtml
              self.profilsHtml+= "</body>\n"
              self.profilsHtml+= "</html>\n"


          #NEW 4.1.5 
          self.htmlReporting.setHtml(html)
          self.statsReporting.setHtml(self.statisticsHtml)
          self.graphsReporting.setHtml(self.graphicsHtml)
          self.profilsReporting.setHtml(self.profilsHtml)
          self.meteoReporting.setHtml(self.meteoHtml)

          if self.layerWater : self.layerWater.removeSelection()
          self.layer.setSubsetString("")
          self.setEnabledCtrl(True)
          self.htmlBulletin = html
          if self.parent.logConsole : self.exportReporting()
          self.setHtmlReport()

      #NEW 4.1.5
      def getToolBar(self, idStation, lbStation, idHydro, hasKey, typeInfos):
          html = "<div><menu>\n"
          html+= "<li><a href='VIEWSTATION?%s'><button><img src='file:///%s' class='iconLegend' title='Localiser la station sur la carte'></button></a></li>\n" % (idStation, self.viewstation)
          if hasKey :
              html+= "<li><a href='VIEWGRAPH?%s'><button><img src='file:///%s' class='iconLegend' title='Visualiser le graphique sur la période d&#39;extraction'></button></a></li>\n" % (idStation, self.graphique)
              html+= "<li><a href='VIEWDATA?%s'><button><img src='file:///%s' class='iconLegend' title='Tableau des données sur la période d&#39;extraction'></button></a></li>\n" % (idStation, self.tabledata)
          html+= self.subHTmlDocAss(idStation, lbStation, idHydro, typeInfos)
          html+= "</menu></div>\n"
          return html

      #NEW 4.1.5  
      def reinitWebViewers(self):
          for key in self.dictWebViewers.keys():  self.dictWebViewers[key].setHtml("")
          
      def setHtmlReport(self):
          #NEW 4.1.5
          idButtonGroup = self.buttongroup.checkedId()
          for key in self.dictWebViewers.keys():  self.dictWebViewers[key].setVisible(False) 
          self.dictWebViewers[idButtonGroup].setVisible(True) 
          self.setWindowTitle("%s : %s des stations" % (self.namePlugin, self.buttongroup.checkedButton().statusTip()))
          self.idButtonGroup = idButtonGroup
        

      def setEnabledCtrl(self, cond): 
          self.dataWebButton.setEnabled(cond)
          self.chrono.setVisible(cond)
          self.chronoInfo.setEnabled(cond)
          self.optionsHtml.setEnabled(cond)
          self.htmlReporting.setEnabled(cond)
          self.buttonRefreshConf.setEnabled(cond)
          self.copyToClipBoardButton.setEnabled(cond)
          self.saveButton.setEnabled(cond)
          self.saveAllButton.setEnabled(cond)
          self.printButton.setEnabled(cond)
          self.busyIndicator.setVisible(not cond)
          
          
      def getHtmlForDicoSeuils(self, idStation, valueInfo, withColor, lastData, fontColor):
         
          SEUIL_RDI = self.stations[idStation]['SEUIL_RDI']
          dicoSeuils = None
          stationDicoSeuils = {}
          color = " style='background-color : #CCC ;'" if withColor else ""
          title = ""
          try : dicoSeuils = ast.literal_eval(SEUIL_RDI)
          except : pass

          htmlDicoSeuils = "<table id='stationsinfos' border='0' width='100%'>\n"

          if withColor : depSeuil, valueDep, indexDep = None, None, -1

          if dicoSeuils :
             i = 1
             htmlDicoSeuilsTH, htmlDicoSeuilsTD = "", ""

             #FIRST SCRUTATION TO FIX RATIO COLUMNS
             nbCols = 1
             for key in dicoSeuils.keys() :
                 if key == "S%s%s" % (self.lblTypeInfos, i) :
                    valueSeuil = dicoSeuils[key]
                    if valueSeuil > -1 :
                       nKey = key.replace(self.lblTypeInfos, "")
                       nKeyDico = 'S3' if i > 3 else nKey
                       stationDicoSeuils[nKey] = { "valueSeuil" : valueSeuil, "commentsSeuilRDI" : COMMENTSSEUILRDI[nKeyDico]}  

                       nbCols+= 1

                    i+= 1   
             espaceCol = int(100 / nbCols)

             color50, color65, color75, color85, color100 = "#a5d1ff", "#81bcff", "#3679f5", "#FCA487", "#F4665F" 
             bgColor = "#E7F1F5" 
             
             i = 1
             for key in dicoSeuils.keys() :
                 if key == "S%s%s" % (self.lblTypeInfos, i) :
                    valueSeuil = dicoSeuils[key]
                    if valueSeuil > -1 :
                       if withColor and lastData :

                           if float(lastData) > valueSeuil : color = " style='background-color : %s ;'" % color100 ; title = " title='Le seuil S%s a été dépassé.'" % i ; depSeuil = True ; valueDep = valueSeuil; indexDep = i;
                           else :
                             seuilColor = int((float(lastData) / valueSeuil) * 100)
                             
                             if seuilColor < 50 : color = color50
                             elif 50<= seuilColor < 65 :  color = color65
                             elif 65<= seuilColor < 75 :  color = color75
                             elif 75<= seuilColor < 85 :  color = color85
                             else : color = color100
                             
                             color = " style='background : linear-gradient(to top, "+color+" 0%, "+color+" "+str(seuilColor)+"%, "+bgColor+" "+str(seuilColor+1)+"%, "+bgColor+" 100%);'"
                             title = " title='Dernière mesure en deçà du seuil S%s (%s%%).'" % (i, seuilColor)
                             depSeuil = False

                       subTitle = ""
                       nKey = key.replace(self.lblTypeInfos, "")
                       valueIndex = int(nKey.replace("S", ""))
                       if valueIndex > 3 : nKey = 'S3'
                       subTitle = COMMENTSSEUILRDI[nKey] 
     
                       htmlDicoSeuilsTH+= "<th%s width='%s%%'><b><center>%s</center></b></th>\n" % (title, espaceCol, subTitle)
                       htmlDicoSeuilsTD+= "<td%s%s><center>%s %s</center</td>\n" % (color, title, valueSeuil, self.uniteInfos)

                    i+= 1
             if htmlDicoSeuilsTH == "" : htmlDicoSeuils+= "<tr><td><font style='%s : black;'><center>Pas d'infos</center></font></td></tr>\n" % fontColor   
             else : htmlDicoSeuils+= "<tr>%s</tr>\n<tr>%s</tr>\n" % (htmlDicoSeuilsTH, htmlDicoSeuilsTD)
           
          else : htmlDicoSeuils+= "<tr><td><font style='color : %s;'><center>Pas d'infos</center></font></td></tr>\n" % fontColor  
          
          htmlDicoSeuils+= "</table>"          

          return htmlDicoSeuils, stationDicoSeuils  


      def setOptionsForExtraction(self):
          if self.comboTypeInfos.currentIndex()== 0 :
             self.typeInfos, self.lblTypeInfos, self.uniteInfos, self.titleTh = "Q", "D", "m<sup>3</sup>/s", "Débit<br>(m<sup>3</sup>/s)"
          else :    
             self.typeInfos, self.lblTypeInfos, self.uniteInfos, self.titleTh = "H", "H", "m", "Hauteur<br>(m)"
          index = self.buttongroup.checkedId()
          self.buttongroup.button(4).setEnabled(self.typeInfos == "H")
          if index == 4 : self.buttongroup.button(1).setChecked(True) 

      def getHtmlError(self, typeClass, idStation, idHydro, lbStation, img, lblError, link):
          htmlDicoSeuils, stationDicoSeuils = self.getHtmlForDicoSeuils(idStation, None, False, None, 'white')   

          html = """<tr class='%s'>\n""" % typeClass
          html+= """<td><div><menu>\n"""
          html+= """<li><a href='VIEWSTATION?%s'><button><img src='file:///%s' class='iconLegend' title='Localiser la station sur la carte'></button></a></li>\n""" % (idStation, img)
          html+= self.subHTmlDocAss(idStation, lbStation, idHydro, self.typeInfos) #NEW 4.1.0, -1)
          html+= """</div></menu></td>"""
          html+= """<td><b>%s</b> (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>)<br><img src='file:///%s' class='iconLegend'> %s</td>\n""" % (lbStation, link, idStation, self.warning, lblError)
          html+= """<td><center> - </center></td>\n"""
          html+= """<td style='background-color : #000 ;'><center><b><u>%s</u></b></center></td>\n""" % htmlDicoSeuils
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          
          return  html


      def readContent(self, idStation, lbStation, idHydro, iMaxTime, counterStation, nbTotal, link):
          html = ""
          answerAsText = None  
          answerAsText = bytes(self.replyObject.readAll()).decode("utf-8")
          datenow = thedatetime.now().strftime("%d/%m/%Y à %Hh%M")
          typeInfos = "Q" if self.comboTypeInfos.currentIndex()== 0 else "H"
          
          toolBarStation = self.getToolBar(idStation, lbStation, idHydro, False, typeInfos)
         
          img = "%s/%s.jpg" % (self.path, idStation)
          img = img.replace("\\", "/")
          title = "Station %s : %s" % (idStation, lbStation)

          if not answerAsText :
             html = self.getHtmlError('error', idStation, idHydro, lbStation, self.viewstation, "<b><i>Service non joignable.</i></b>", link);
             return html, toolBarStation
  
          else : 
                denum = 1000
                try : json_data = json.loads(answerAsText)
                except :
                        html = self.getHtmlError('error', idStation, idHydro, lbStation, self.viewstation, "<b><i>Format JSON de la réponse incorrect.</i></b>", link);
                        return html, toolBarStation                  

                if  dicoHasKey(json_data, "count") :
                    if int(json_data["count"]) == 0 :
                     html = self.getHtmlError('noservice', idStation, idHydro, lbStation, self.viewstation, "<b><i>Station non couverte pour cette information.</i></b>", link)                       
                
                if dicoHasKey(json_data, "data") :
                   mesures = json_data["data"]
                  
                   if mesures :

                        valuesMaxTime = [180, 360, 720, 1440, 2880, 4320, 10080, 20160, 44640]
                        maxTime = valuesMaxTime[iMaxTime]
                       
                        case = 0 if "resultat_obs" in mesures[0].keys() else  1
                        date_obs, resultat_obs, firstDate = [], [], False

                        typeStats = "linechart0"
                        borne = 10

                        if case == 0 :
                              theLastDate, theMaxDate, minY, maxY = "", "", 1000000000.0, 0.0
                              date0 = thedatetime.fromisoformat(mesures[0]["date_obs"][:-1])

                              try :
                                  deltaTimeOscultation = thedatetime.fromisoformat(mesures[0]["date_obs"][:-1]) - thedatetime.fromisoformat(mesures[1]["date_obs"][:-1])
                                  deltaMinutesOscultation = int(deltaTimeOscultation.total_seconds() / 60)    
                              except :
                                  if len(mesures) == 1 :
                                     deltaTimeOscultation = 0
                                     deltaMinutesOscultation = 1
                                  
                              if deltaMinutesOscultation == 0 : deltaMinutesOscultation = 1       
                              facteurDeltaTime = (60 / deltaMinutesOscultation)
                              borne = 0  
                              
                              for mesure in mesures :
                                  data = mesure["resultat_obs"]/denum  
                                  resultat_obs.append(data)
                                  if data > maxY : maxY = data; theMaxDate = jsonDateToStrDate(mesure["date_obs"], "", "<br>", self.timezone) ;
                                  if data < minY : minY = data
                                  if not firstDate : theLastDateHtml = jsonDateToStrDate(mesure["date_obs"], "", "<br>", self.timezone) ;  theLastDate = jsonDateToStrDate(mesure["date_obs"], "", " ", self.timezone) ; firstDate = True 
                                  date_obs.append(jsonToMatplotlibDate(mesure["date_obs"], self.timezone))
                                                                         
                                  currentDate = thedatetime.fromisoformat(mesure["date_obs"][:-1])

                                  if (date0 - currentDate).total_seconds()/60 <  valuesMaxTime[0] : borne+= 1  
                                  if (date0 - currentDate).total_seconds()/60 >  maxTime : break 
                                  
                              title = "%s\n[du %s au %s]" % (title, jsonDateToStrDate(mesure["date_obs"],"", " ", self.timezone), theLastDate)
                                  
                        else :
                              theMaxDate, maxY = "", 0.0
                              for mesure in mesures :
                                  data = mesure["resultat"]
                                  resultat_obs.append(data)
                                  if data > maxY : maxY = data ; theMaxDate = jsonDateToStrDate(mesure["date_prelevement"],"", "<br>", self.timezone) ;
                                  if not firstDate : self.title = "%s\n[du %s" % (self.title, jsonDateToStrDate(mesure["date_prelevement"], "", "<br>", self.timezone)); firstDate = True
                                  date_obs.append(jsonToMatplotlibDate(mesure["date_prelevement"], self.timezone))

                                  
                              title = "%s au %s]" % (title, jsonDateToStrDate(mesure["date_prelevement"], "", " ", self.timezone))

                        lastData = round(resultat_obs[0], 3)
                        
                        htmlDicoSeuils, stationDicoSeuils = self.getHtmlForDicoSeuils(idStation, None, True, lastData, 'black')
                        beforeLastData = round(resultat_obs[1], 3) if len(resultat_obs)> 1 else round(resultat_obs[0], 3)
                        maxY = round(maxY, 3)
                        average = getMean(resultat_obs, 3) 
                        ecartType = getStDev(resultat_obs, 3)
                        #INTERVALLE DE CONFIANCE (95%)
                        IC95 = average-(1.96 * (ecartType / sqrt(len(mesures))))
                        intervalleConf = [average-IC95, average+IC95]                        
                        ecart = (lastData - average)
                        
                        #INDICATEUR POSITION / AVERAGE
                        ratio = (lastData / average)

                        if lastData > average :
                           if ratio > 2.0 : 
                               if lastData > (average + (4*ecartType)) : commentRatio = "&#x3E;&#x3E;&#x3E;" ; titleCommentRatio = self.dictLegend["v4"]
                               elif lastData > (average + (2*ecartType)) : commentRatio = "&#x3E;&#x3E;" ; titleCommentRatio = self.dictLegend["v6"]
                               else : commentRatio = "&#x3E;" ; titleCommentRatio = self.dictLegend["v6"]
                           else :
                               if ratio < 1.03 : commentRatio = "&#x2248;" ; titleCommentRatio = self.dictLegend["v5"]
                               elif ratio < 1.06 : commentRatio = "&#x2265;" ; titleCommentRatio = self.dictLegend["v0"] #"&#x2A7E;" - CODE ALTERNATIF SUP ou EGAL  
                               else : commentRatio = "&#x3E;" ; titleCommentRatio = self.dictLegend["v6"]
                        elif lastData == average or ratio >= 0.95 :
                             if lastData == average : commentRatio = "=" ; titleCommentRatio = self.dictLegend["v0"]
                             else : commentRatio = "&#x2248;" ; titleCommentRatio = self.dictLegend["v0"] #"&plusmn;" - CODE ALTERNATIF PLUS OU MOINS
                        else :
                           if ratio < 0.5 : 
                               if lastData > (average - (4*ecartType)) : commentRatio = "&#x3C;&#x3C;&#x3C;"; titleCommentRatio = self.dictLegend["v1"]
                               elif lastData > (average - (2*ecartType)) : commentRatio = "&#x3C;&#x3C;"; titleCommentRatio = self.dictLegend["v3"]
                               else : commentRatio = "&#x3C;"; titleCommentRatio = self.dictLegend["v3"]
                           else :
                               commentRatio = "&#x3C;" ; titleCommentRatio = self.dictLegend["v2"] #CODE INF ou EGAL &#x2264; 
                            
                       
                        #INDICATEUR EV.3H
                        observations = [i for i in range(borne)]
                        coeffReg, constanteReg = None, None

                        if borne == 1 :  tendancea = "<b> </b>"; titleImg2 = "%s (%s)" % (self.dictLegend["e3"], "3 dernières heures")
                        else :                        
                            try :
                                slope, intercept, r_value, p_value, std_err = linregress(observations, resultat_obs[:borne])                                
                                coeffReg = round(slope, 5)
                                constanteReg = round(intercept, 5)
                                comments, tendancea, titleImg2 = getCommentsForTrend(slope, False)    
                            except : 
                                tendancea = "<b>?</b>"; titleImg2 = "%s (%s)" % (self.dictLegend["e3"], "3 dernières heures")    

                        
                        #INDICATEUR LAST EVOLUTION
                        if beforeLastData == lastData : tendance = "<b>&#8608;</b>"; titleImg3 = "%s (%s)" % (self.dictLegend["e0"], "Deux dernières mesures")
                        elif beforeLastData > lastData : tendance = "<b>&#8600;</b>"; titleImg3 = "%s (%s)" % (self.dictLegend["e1"], "Deux dernières mesures")
                        else : tendance = "<b>&#8599;</b>"; titleImg3 = "%s (%s)" % (self.dictLegend["e2"], "Deux dernières mesures")                      

                        #NEW 4.1.5 ADD tendance to self.dataStations
                        self.dataStations[idStation] = {'typeStats' : typeStats, 'resultat_obs' : resultat_obs, 'date_obs' : date_obs, 'title' :  title, 'img' : img, 'minY' : minY, 'maxY' : maxY,
                                                        'theMaxDate' : theMaxDate, 'theLastDateHtml' : theLastDateHtml, 'lastData' : lastData, 'tendance' : tendance,
                                                        'typeInfos' : typeInfos, 'idHydro' : idHydro, 'lbStation' : lbStation,
                                                        'average' : average, 'ecarttype' : ecartType, 'coeffReg' : coeffReg, 'constanteReg' : constanteReg,
                                                        'stationDicoSeuils' : stationDicoSeuils, 'deltaMinutesOscultation' : deltaMinutesOscultation, 'IC95' : round(IC95, 2)}

                        #NEW 4.1.5
                        #link = "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie=%s&ZoomInitial=3" % (idStation, typeInfos)
                                                
                        html = "<tr>\n"
                        html+= "<td>\n"

                        #NEW 4.1.5
                        toolBarStation = self.getToolBar(idStation, lbStation, idHydro, dicoHasKey(self.dataStations, idStation), typeInfos) 
                        html+= toolBarStation
                        
                        html+= "</td>\n"
                        html+= "<td><b>%s</b> (<span class='small'><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a></span>)</td>\n" % (lbStation, link, idStation) 
                        html+= "<td><center>%s</center></td>\n" % ("%s<br>min." % int(deltaMinutesOscultation))
                        html+= "<td>%s</td>\n" % htmlDicoSeuils
                        html+= "<td><center><b>%s</b></center></td>\n" % lastData
                        html+= "<td><center>%s</center></td>\n" % theLastDateHtml
                        html+= "<td><center><b>%s</b></center></td>\n" % maxY
                        html+= "<td><center>%s</center></td>\n" % theMaxDate
                        html+= "<td><center><b>%s</b></center></td>\n" % average
                        html+= "<td title='%s'><center><fonr style='font-size: 2.0em; font-weight: bold;'>%s</font></center></td>\n" % (titleCommentRatio, commentRatio) 
                        html+= "<td title='%s'><center><fonr style='font-size: 2.0em; font-weight: bold;'>%s</font></center></td>\n" % (titleImg2, tendancea) #, titleImg2)
                        html+= "<td title='%s'><center><fonr style='font-size: 2.0em; font-weight: bold;'>%s</font></center></td>\n" % (titleImg3, tendance) #, titleImg3)

                        self.index+= 1
                else :
                        html = self.getHtmlError('error', idStation, idHydro, lbStation, self.viewstation, "<b><i>Pas de données trouvées dans la réponse du service web.</i></b>", link)
                        
          return html, toolBarStation  
  
      def subHTmlDocAss(self, idStation, lbStation, idHydro, typeInfos): #NEW 4.1.5 , index):  
          listDocAss = getListDocForMenu(self, None, idStation, self.pathLocalFolderStations)
    
          if listDocAss != [] : icon, title = self.docAss, "Documents associés et sites web d&#39;information pour la station"
          else : icon, title = self.webAss, "Sites web d&#39;information pour la station"
          
          html = """<li class="menu-deroulant"><button onclick="return false;"><img src='file:///%s' class='iconLegend' title="%s"></button>\n""" % (icon, title)
          html+= """<ul class="sous-menu">\n"""
          
          if listDocAss != [] :

             html+= """<table class='titleDocAss'>\n<tr><td>Documents locaux associés</td></tr>\n</table><hr>\n"""
             
             for docAss in listDocAss :
                 extension, icon = getExtension(docAss), None
                 if extension.startswith(".") :
                    extension = extension[1:]
                    icon = getThemeIcon("%s.png" % extension).replace("\\", "/").replace("//", "/")
                 html+= """<li><space><a href="VIEWDOC?%s"><img src='file:///%s' class='iconLegend'>\n<span style="position:relative;font-size: 12px;"> %s</span></a></space></li>\n""" % (docAss, icon if icon else self.urlweb, basename(docAss))

             html+= """<br>"""

          if lbStation.find("'") > -1 : lbStation = lbStation.replace( "'", "&#39;")
          #NEW 4.1.5
          listWebAss = {"statsStation" : (self.statsStation, "Données statistiques HubEau (moyenne du débit, journalier ou mensuel) pour la station %s" % lbStation, "Données statistiques", "https://www.hydro.eaufrance.fr/stationhydro/%s/monthly_vcn3_01/statistique/visualisation-echantillon" % idStation),
                        "temporalSeries" : (self.temporalseries, "Données élaborées (moyenne du débit, journalier ou mensuel) pour la station %s" % lbStation, "Données élaborées", "https://hubeau.eaufrance.fr/sites/default/files/api/demo/hydro_elab.htm?code_station=%s&grandeur=QmJ&fdp=3" % idStation),
                        "vigicrues" : (self.vigicrues, "Consulter Vigicrues pour la station %s" % lbStation, "Vigicrues", "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie=%s&ZoomInitial=3" % (idStation, typeInfos)),
                        "hydro" : (self.hydro, "Fiche Synthèse DREAL pour la station %s" % lbStation, "Fiche DREAL", "http://www.hydro.eaufrance.fr/stationhydro/%s/fiche" % idStation),
                        "inrae" : (self.inrae, "Satistiques sur INRAE pour la station %s" % lbStation, "Satistiques INRAE", "https://hydrogr.github.io/BDD-HydroClim/images/fr/%s_INRAE_BDD-HydroClim_fact_sheet_FR.png" % idStation),
                        "birdz" : (self.birdz, "Données sur OPENDATASOFT pour la station %s" % lbStation, "Données OPENDATASOFT", "https://birdz.opendatasoft.com/explore/dataset/vigicrues-hauteurs-et-debits-des-cours-deau/table/?flg=en&q=%s" % idHydro[0:4])
                       }          


          html+= "<table class='titleDocAss'><tr><td>Sites Web</td></tr></table><hr>\n"

          html+= "<li><space>\n"  
          for key in listWebAss :
              icon = listWebAss[key][0]
              title = listWebAss[key][1]
              text = listWebAss[key][2]
              url = listWebAss[key][3]
              html+= "&nbsp;<a href=\"%s\"><button><img src='file:///%s' class='iconLegend' title='%s'></button></a>\n" % (url, icon if icon else self.urlweb, title)

          html+= "</space></li>\n" 
          html+= "</ul>\n"   
          return html

  
      def getActions(self, url):

          if type(url) == QUrl :
              
             if url.toString().startswith("VIEWSTATION?") :

                 idStation = url.toString().split("?")[1]
                 fieldCdStationH = self.tableFieldsStations['CdStationH']
                 exp = QgsExpression(" \"{}\" = '{}' ".format(fieldCdStationH, idStation.replace("'", "''")))
                 request = QgsFeatureRequest(exp)
                 
                 geomType = self.layer.geometryType()

                 for feat in self.layer.getFeatures(request):
                      geom = feat.geometry()
                      if geomType != 2 : geom = geom.buffer(500, 12)
                      rect = geom.boundingBox()
                      self.canvas.setExtent(rect)
                      self.canvas.refresh()
                      break

             elif url.toString().startswith("VIEWGRAPH?") :

                 idStation = url.toString().split("?")[1]

                 if dicoHasKey(self.dataStations, idStation) :
                     infos = self.dataStations[idStation]

                     self.movieCtrl.setVisible(True)
                     self.dlgStats = windowPlotStatsAndGraphs(self, "https://hubeau.eaufrance.fr", infos['typeStats'],
                                                              infos['resultat_obs'], infos['date_obs'], infos['title'],
                                                              infos['img'], infos['minY'], infos['maxY'], infos['typeInfos'],
                                                              infos['idHydro'], infos['lbStation'], idStation, self.pathLocalFolderStations, self.timezone, self.titleService,
                                                              infos['average'], infos['ecarttype'], infos['coeffReg'], infos['constanteReg'], infos['stationDicoSeuils'])
                     self.movieCtrl.setVisible(False)

                     self.dlgStats.show()

                 else :
                       img = "%s/%s.jpg" % (self.path, idStation) 
                       msgDlgBox = myMsgDlgBox(self, QMessageBox.Information, "Information %s" % self.parent.namePlugin,
                                          "<center><img src='%s'><br>Pas de service pour la station <b>%s</b>.</center>" % (img, idStation), "rdistop.png", "inondation.png", 1, None, self)
                       msgDlgBox.exec_()


             elif url.toString().startswith("VIEWDATA?") :
                  idStation = url.toString().split("?")[1]

                  if dicoHasKey(self.dataStations, idStation) :
                      infos = self.dataStations[idStation]

                      records = [ [infos['date_obs'][i], infos['resultat_obs'][i]] for i in range(len(infos['date_obs']))]
                      
                      typeInfos = "Débit en m3/s" if infos['typeInfos'] == "Q" else "Hauteur en m"  
                      
                      self.dlgData = simpleViewerData(idStation, infos['lbStation'], ['date_obs', 'resultat_obs'], records, typeInfos)
                      self.dlgData.exec_()             
                  else :
                       img = "%s/%s.jpg" % (self.path, idStation) 
                       msgDlgBox = myMsgDlgBox(self, QMessageBox.Information, "Information %s" % self.parent.namePlugin,
                                          "<center><img src='%s'><br>Pas de service pour la station <b>%s</b>.</center>" % (img, idStation), "rdistop.png", "inondation.png", 1, None, self)
                       msgDlgBox.exec_()                 

             elif url.toString().startswith("VIEWDOC?") :

                  docAss = url.toString().split("?")[1] 
                  openFile("%s" % docAss)

             elif url.toString().startswith("VIEWSERIES?") :

                 idStation = url.toString().split("?")[1]

                 url = "https://hubeau.eaufrance.fr/sites/default/files/api/demo/hydro_elab.htm?code_station=%s&grandeur=QmJ&fdp=3" % idStation

                 self.dlgStats = simpleViewerWebData(url, "Séries temporelles anciennes")
                 self.dlgStats.exec_()

             else :
                 
                 try : webbrowseropen(url.toString())
                 except : pass 

      def cleanAnchors(self, html) :
           Elts = ["VIEWSTATION?", "VIEWGRAPH?", "VIEWDATA?"]

           newHtml = html
           for elt in Elts :
               posd = -1
               posd = html.find(elt)
               while posd > 0 :
                     posf = html.find("\"", posd)
                     toKill = html[posd : posf]
                     newHtml = newHtml.replace(toKill, "#")
                     posd = html.find(elt, posf)
                    
           return newHtml

      def saveAllReporting(self):
          dlgFile = QFileDialog(self, "Sélectionner le dossier pour la sauvegarde")
          dlgFile.setDirectory(self.initDir)
          dlgFile.setFileMode(QFileDialog.DirectoryOnly)

          tempoFile = "%s/tempo.html" % (self.initDir)
          fixeLabelsFileDialog(self, dlgFile, 0, False)      

          if dlgFile.exec_():
             dirFiles = dlgFile.selectedFiles()[0]
             self.initDir = dirFiles
             datation = thedatetime.now().strftime("%Y%m%d-%Hh%M")   
             #LOOP ON ALL REPORTS
             indexReport = self.buttongroup.checkedId()-1
             index = 1
             tempoFile = "%s/tempo.html" % (dirFiles)

             #NEW 4.1.5
             for key in self.dictWebViewers.keys():
                 if exists(tempoFile) : removeFile(tempoFile)
                 html = self.dictWebViewers[key].page().currentFrame().toHtml()
                 fileName = "%s/RDI_%s_%s.html" % (dirFiles, self.buttongroup.button(index).statusTip(), datation)
                 self.getSaveReporting(fileName, tempoFile, html, False)
                 index+= 1
             self.initDir = dirFiles

             if exists(tempoFile) : removeFile(tempoFile)
             
             self.barInfo.pushMessage("Information :", "Fichiers sauvegardés dans le dossier : <b>%s</b> !" % self.initDir, Qgis.Info, 3)

        
      def saveReporting(self):
          
            fileDialog = QFileDialog(self, "Sauvegarder la page %s au format HTML" % self.buttongroup.checkedButton().statusTip()) 
            fileDialog.setNameFilters(("*.html", ))
            fileDialog.selectNameFilter("*.html")
            fileDialog.setViewMode(QFileDialog.Detail)
            fileDialog.setDirectory(self.initDir)
            fileDialog.setAcceptMode(QFileDialog.AcceptSave)
            fixeLabelsFileDialog(self, fileDialog, 1, False)
            windowIcon(self, "save.png")
            fileDialog.selectFile("RDI_%s_%s.html" % (self.buttongroup.checkedButton().statusTip(), thedatetime.now().strftime("%Y%m%d-%Hh%M")))
            
            if fileDialog.exec_():
               fileName = fileDialog.selectedFiles()[0]
               self.initDir = dirname(fileName)
               tempoFile = "%s/tempo.html" % (self.initDir)

               #NEW 4.1.5
               html = self.dictWebViewers[self.idButtonGroup].page().currentFrame().toHtml()
               self.getSaveReporting(fileName, tempoFile, html, True)

            if exists(tempoFile) : removeFile(tempoFile)               


      def getSaveReporting(self, fileName, tempoFile, html, isPushMsg):
         
           relativeFolder = pathLibPath(fileName).stem 
           imgFolder = "%s/%s" % (dirname(fileName), relativeFolder)
           subFolder = ""
           pathLibPath(imgFolder).mkdir(parents=True, exist_ok=True)

           #NEW 4.1.5 
           imgToCopy = [self.editGraph, self.reloadGraph, self.openAllNodes, self.closeAllNodes, self.invertAllNodes,
                        self.viewstation, self.docAss, self.statsStation, self.temporalseries, self.webAss,
                        self.vigicrues, self.hydro, self.inrae, self.birdz, self.noaction, self.graphique,
                        self.tabledata, self.warning, self.urlweb, self.fileLogoLeft, self.fileLogoRight]
 
           
           #NEW 4.1.6
           texts = ["%s" % join(dirname(__file__), "data/logos/"), "%s" % join(dirname(__file__), "data/imgs/"),
                    "%s" % join(dirname(__file__), "data/graph/"), "%s" % join(dirname(__file__), "data/stations/"),
                    "%s" % join(dirname(__file__), "icons/meteo/"), "%s" % join(dirname(__file__), "icons/mime/")]
           
           for text in texts :
               html = html.replace("file:///%s" % text.replace("\\", "/"), "%s/" % relativeFolder)

               subFolder = "/" + text.split("/")[-1] + "/"
               pathLibPath(imgFolder+"/"+subFolder+"/").mkdir(parents=True, exist_ok=True)

               images = glob.glob(text + '/*')
               for image in images:                       
                   if os.path.isdir(image): 
                      pass 
                      """ 
                      subFolder = "/" + image.replace("\\", "/").split("/")[-1] + "/"
                      pathLibPath(imgFolder+"/"+subFolder+"/").mkdir(parents=True, exist_ok=True)
                      """
                   else : shutil.copy2(image, imgFolder + subFolder)
               
           #COPY ICONS  
           for img in imgToCopy :
               #NEW 4.1.6
               #html = html.replace("file:///%s/meteo" % dirname(img).replace("\\", "/"), "%s" % relativeFolder)
               html = html.replace("file:///%s" % dirname(img).replace("\\", "/"), "%s" % relativeFolder)
               shutil.copy2(img, imgFolder)



           #CLEAN ANCHORS AND SYSTEMIC ACTIONS
           html = self.cleanAnchors(html)

           with open(tempoFile, "w", encoding="utf-8", errors="surrogateescape") as inFile :
                try :
                    html = "%s" % (html)
                    inFile.write(html)
                except :
                    inFile.write("<html><style>h1 {font-size: 1.5em;}</style><h1>error</h1></html>")
                    self.barInfo.pushMessage("Avertissement :", "Impossible de sauvegarder le bulletin !", Qgis.Warning, 3)
                    return

           #CLEAN BAD DIM GRAPH
           while not exists(tempoFile) : pass
           
           f = open(tempoFile, "r")
           lines = f.readlines()
           #NEW 4.1.0
           html = cleanHtmlGraphics(lines)           
           with open(fileName, "w", encoding="utf-8", errors="surrogateescape") as inFile : inFile.write(html)
           #NEW 4.1.5
           if isPushMsg : self.barInfo.pushMessage("Information :", "Fichier <b>%s</b> sauvegardé !" % basename(fileName), Qgis.Info, 3)


      def exportReporting(self): 
          typeInfos =  None
          for key in self.dataStations :
              typeInfos = self.dataStations[key]['typeInfos']
              break
          if not typeInfos : return
          if typeInfos == "Q" : lblTypeInfos, uniteInfos, titleTh= "D", "m<sup>3</sup>/s", "Débit<br>(m<sup>3</sup>/s)" 
          else : lblTypeInfos, uniteInfos, titleTh = "H", "m", "Hauteur<br>(m)"

          html = "<font style='font-size: xx-small;'>%s</font><br>" % self.infosExtraction.replace("<br>", ", ")
          html+= """<table border='1'>
                    <thead>
                    <tr>
                        <th colspan='2'></th>
                        <th colspan='2' bgcolor='#f5f5f5' style='font-family: Marianne; font-size: 11px;'>Dernière observation</th>
                        <th colspan='2' style='font-family: Marianne; font-size: 11px;'><center>ENJEUX</center></th>
                        <th colspan='2' bgcolor='#f5f5f5' style='font-family: Marianne; font-size: 11px;'>Bulletin précédent</th>
                        <th colspan='2' bgcolor='#f5f5f5' style='font-family: Marianne; font-size: 11px;'>Max sur la période d'observation</th>
                    </tr>    
                    <tr bgcolor='#f5f5f5'>
                        <th style='font-family: Marianne; font-size: 11px;'>Station hydrométrique</th>
                        <th style='font-family: Marianne; font-size: 11px;'>Seuils<br>indicatifs RDI</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>%s</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>heure</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Constats, débordements et enjeux</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Prévisions de débordements et enjeux</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>%s</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Heure</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>%s</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Heure</th>
                    </tr></thead>""" % (titleTh, titleTh, titleTh)

          noData = " - " 
          
          for key in self.dataStations :
              infos = self.dataStations[key]

              heure = infos['theLastDateHtml'].split("<br>")[-1]
              infosMax = infos['theMaxDate'].split("<br>")
              dateMax, heureMax = infosMax[0], infosMax[-1]
              SEUIL_RDI = self.stations[key]['SEUIL_RDI'] 
              dicoSeuils = None
              try : dicoSeuils = ast.literal_eval(SEUIL_RDI)
              except : pass

              htmlDicoSeuils = ""
                
              if dicoSeuils :
                 i = 1
                 for key in dicoSeuils.keys() :
                     if key == "S%s%s" % (lblTypeInfos, i) :
                        valueSeuil = dicoSeuils[key]
                        if valueSeuil > -1 :
                           htmlDicoSeuils+= "<b>%s</b> : %s %s<br>" % (key.replace(lblTypeInfos, ""), valueSeuil, uniteInfos)
                        i+= 1
                 if htmlDicoSeuils == "" : htmlDicoSeuils = "Pas d'infos"    
              else : htmlDicoSeuils+= "Pas d'infos" 


              html+= """<tr>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'><b><center>%s</center></b></td>
                          <td style='font-family: Marianne; font-size: 10px;'><center>%s</center></td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td> 
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'><b><center>%s</center></b></td>
                          <td style='font-family: Marianne; font-size: 10px;'><center>%s<br>%s</center></td>
                        </tr>                           
                      """ % (infos['lbStation'], htmlDicoSeuils, infos['resultat_obs'][0], heure, noData, noData, noData, noData, infos['maxY'], dateMax, heureMax)

          html+= "</table>"
          comments = ""
          for key in COMMENTSSEUILRDI.keys() : 
              comment = COMMENTSSEUILRDI[key].replace("<br>", " ") 
              comments+= "%s : %s" % (key, comment) if comments == "" else  ", %s : %s" % (key, comment)
          html+= "<font style='font-size: xx-small;'>%s</font>" % comments  
            
          reportRichTextMime = QMimeData()
          reportRichTextMime.setHtml(html)
          QApplication.clipboard().setMimeData(reportRichTextMime)

          if self.parent.logConsole : self.parent.logConsole.setAnnontation(html.replace("<font style='font-size: xx-small;'>", "<font style='font-size: 18px;'>").replace("font-family: Marianne; font-size: 10px;", "font-family: Marianne; font-size: 14px;")+"<br><br>", True)


      def printReporting(self):
          printer = QPrinter(); printer.setPageSize(QPrinter.A4); printer.setOrientation(QPrinter.Portrait);
          printer.setPageMargins(5, 5, 5, 5, QPrinter.Millimeter); printer.setOutputFormat(QPrinter.NativeFormat);
          orientation = QPrinter.Portrait
          #NEW 4.1.5 
          dlgPrint = printerPreview(self, self.dictWebViewers[self.idButtonGroup], "%s : Imprimer le bulletin" % self.namePlugin, orientation, "printer.png")

      def resizeEvent(self, ev):
          self.barInfo.setGeometry(0, 0, self.width(), 60)

          self.groupParams.setGeometry(5, 0, self.width()-10, 100)
          self.labelComboTypeInfos.setGeometry(5, 30, 80, 25)
          self.comboTypeInfos.setGeometry(85, 25, 90, 25)
          self.labelComboDate.setGeometry(175, 30, 50, 25) 
          self.comboDate.setGeometry(235, 25, 170, 25)
          
          self.labelListStations.setGeometry(5, 60, 80, 25) 
          self.listStations.setGeometry(85, 55, 300, 25)
          self.buttonListActions.setGeometry(390, 55, 25, 25)

          self.labellcdNumber.setGeometry(self.width()-210, 5, 160, 25)
          self.lcdNumber.setGeometry(self.width()-210, 30, 160, 25)
          self.chrono.setGeometry(self.width()-140, 60, 25, 25)
          self.chronoInfo.setGeometry(self.width()-195, 50, 125, 43)
          self.dataWebButton.setGeometry(420, 25, 154, 25)
          self.optionsHtml.setGeometry(420, 55, 154, 30)
                    
          self.busyIndicator.setGeometry(575, 60, self.width()-self.labelListStations.width()-self.listStations.width()-250, 15)

          self.buttonRefreshConf.setGeometry(10, self.height()-40, 24, 24)

          #NEW 4.1.5 
          for key in self.dictWebViewers.keys(): self.dictWebViewers[key].setGeometry(5, 85, self.width()-10, self.height()-135)
          
          self.copyToClipBoardButton.setGeometry(self.width()-220, self.height()-40, 24, 24)
          self.printButton.setGeometry(self.width()-180, self.height()-40, 24, 24)
          self.saveButton.setGeometry(self.width()-140, self.height()-40, 24, 24)
          self.saveAllButton.setGeometry(self.width()-100, self.height()-40, 24, 24)
          self.closeButton.setGeometry(self.width()-60, self.height()-40, 24, 24) 



class simpleViewerData(QDialog):
      def  __init__(self, idStation, lbStation, fieldNames, records, typeInfos):
            super(simpleViewerData, self).__init__()
            #NEW 4.1.5
            self.setWindowTitle("Visualiseur Données (%s) : Station %s (%s), %s observations." % (typeInfos, lbStation, idStation, len(records)))
            self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 650, 650
            self.resize(self.minWidth, self.minHeight)

            self.idStation = idStation
            self.initDir = dirname(__file__)

            windowIcon(self, "tabledatam.png")

            self.dataViewer = QTableView(self) 
            self.dataViewer.setAccessibleDescription("")

            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            sizePolicy.setHorizontalStretch(0)
            sizePolicy.setVerticalStretch(3)
            sizePolicy.setHeightForWidth(self.dataViewer.sizePolicy().hasHeightForWidth())
            self.dataViewer.setSizePolicy(sizePolicy)
            self.dataViewer.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.dataViewer.setSelectionMode(QAbstractItemView.SingleSelection)
            self.dataViewer.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
            self.dataViewer.setVisible(True)
            self.dataViewer.setAlternatingRowColors(True)
            self.dataViewer.setStyleSheet("QTableView {alternate-background-color: cyan;background-color: white;}")


            model = baseTableModel(fieldNames, records, self)
            self.dataViewer.setModel(model)

            self.dataViewer.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

            self.saveButton = funnyPushButton(self)
            self.saveButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "saveButton", "", "Sauvegarder l'onglet courant", True, getThemeIcon("save.png"), 24, 24, True, False)

            self.closeButton = funnyPushButton(self)
            self.closeButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "closeButton", "", "Fermer", True, getThemeIcon("close.png"), 24, 24, True, False)

            self.saveButton.clicked.connect(self.saveDataInFile)
            self.closeButton.clicked.connect(self.reject)


      def saveDataInFile(self):
            
            fileDialog = QFileDialog(self, "Sauvegarder les données")
            fileDialog.setNameFilters(("*.csv", ))
            fileDialog.selectNameFilter("*.csv")
            fileDialog.setViewMode(QFileDialog.Detail)
            fileDialog.setDirectory(self.initDir)
            fileDialog.setAcceptMode(QFileDialog.AcceptSave)

            fixeLabelsFileDialog(self, fileDialog, 1, False)
            windowIcon(self, "save.png")
            fileDialog.selectFile("%s_%s.csv" % (self.idStation, thedatetime.now().strftime("%d%m%Y-%Hh%Mm%Ss")))
            
            if fileDialog.exec_():
               fileName = fileDialog.selectedFiles()[0]
               self.initDir = dirname(fileName)
               
               model = self.dataViewer.model()
               with open(fileName, "w") as fileOutput:
                   writer = csv.writer(fileOutput, lineterminator='\n')
                   fields = [col for col in model.columnNames()]
                   writer.writerow(fields)
               
                   for row in range(model.rowCount()) :
                        data = [ model.getData(row, column) for column in range(model.columnCount()) ]
                        writer.writerow(data)


      def resizeEvent(self, ev):
          
          self.dataViewer.setGeometry(5, 5, self.width()-10, self.height()-40)
          self.saveButton.setGeometry(self.width()-100, self.height()-30, 24, 24) 
          self.closeButton.setGeometry(self.width()-60, self.height()-30, 24, 24)        
          

class baseTableModel(QAbstractTableModel):
    def __init__(self, header=None, data=None, parent=None):
        super(baseTableModel, self).__init__(parent)
        self.header = header if header else []
        self.resdata = data if data else []

    def headerToString(self, sep=u"\t"):
        header = self.header
        return sep.join(header)

    def rowToString(self, row, sep=u"\t"):
        text = u""
        for col in range(self.columnCount()):
            text += u"%s" % self.getData(row, col) + sep
        return text[:-1]

    def getData(self, row, col):
        try : return self.resdata[row][col]
        except : pass

    def columnNames(self): return list(self.header)
    def rowCount(self, parent=None): return len(self.resdata)
    def columnCount(self, parent=None): return len(self.header)
    def data(self, index, role):
        if role != Qt.DisplayRole and role != Qt.FontRole: return None

        val = self.getData(index.row(), index.column())

        if role == Qt.FontRole:  
            if val is not None: return None
            f = QFont()
            f.setItalic(True)
            return f

        if val is None: return "NULL"
        elif isinstance(val, memoryview): return None
        elif isinstance(val, (str, unicode)) and len(val) > 300: return u"%s..." % val[:300]
        return unicode(val)  

    def headerData(self, section, orientation, role):
        if role != Qt.DisplayRole: return None
        if orientation == Qt.Vertical: return section + 1
        else : return self.header[section]


#NEW 4.1.0
class dynaLocationMarker(QgsMapCanvasItem):
	class aniObject(QObject):
		def __init__(self):
			super(dynaLocationMarker.aniObject, self).__init__()
			self._size = 0
			self.startsize = 0
			self.maxsize = 30 

		@pyqtProperty(int)
		def size(self): return self._size

		@size.setter
		def size(self, value): self._size = value

	def __init__(self, canvas, x, y, color):
		self.canvas = canvas
		self.color = color
		self.map_pos = QgsPointXY(x, y)
		self.aniObject = dynaLocationMarker.aniObject()
		QgsMapCanvasItem.__init__(self, canvas)
		self.anim = QPropertyAnimation(self.aniObject, b"size")
		self.anim.setDuration(1000)
		self.anim.setStartValue(self.aniObject.startsize)
		self.anim.setEndValue(self.aniObject.maxsize)
		self.anim.setLoopCount(-1)
		self.anim.valueChanged.connect(self.value_changed)
		self.anim.start()

	@property
	def size(self): return self.aniObject.size
	@property
	def halfsize(self): return self.aniObject.maxsize / 2.0
	@property
	def maxsize(self): return self.aniObject.maxsize
	def value_changed(self, value): self.update()

	def paint(self, painter, xxx, xxx2):
		self.setCenter(self.map_pos)
		rect = QRectF(0 - self.halfsize, 0 - self.halfsize, self.size, self.size)
		painter.setRenderHint(QPainter.Antialiasing)
		painter.setBrush(self.color) 
		painter.setPen(self.color) 
		painter.drawEllipse(QPointF(0,0), self.size, self.size)

	def boundingRect(self): return QRectF(-self.halfsize * 2.0, -self.halfsize * 2.0, 2.0 * self.maxsize, 2.0 * self.maxsize)
	def setCenter(self, map_pos): self.map_pos = map_pos; self.setPos(self.toCanvasCoordinates(self.map_pos))
	def updatePosition(self): self.setCenter(self.map_pos)
