legacy/outils/tableauSignesVariations/index.js

import $ from 'jquery'
import { j3pAddElt, j3pAjouteBouton, j3pAjouteDiv, j3pVirgule, j3pDetruit, j3pDiv, j3pElement, j3pNombre, j3pPaletteMathquill, j3pStyle, j3pValeurde, j3pEnsureHtmlElement, j3pGetNewId } from 'src/legacy/core/functions'
import { j3pCreeRectangle, j3pCreeSegment } from 'src/legacy/core/functionsSvg'
import { j3pAffiche, mqRestriction } from 'src/lib/mathquill/functions'
import { hasProp } from 'src/lib/utils/object'
/**
 * @fileOverview Cet outil ne fait que déclarer la fct j3ptableau_signes_variations
 */

// @todo nettoyer la gestion des inputMq pour la compatibilité avec ValidationZones, ici on a des doublons entre mqList et tabDef.validationZonesInputs (ça devrait être la même liste)
// => il faudrait plutôt ajouter une propriété validationZones à l’objet retourné par tableauSignesVariations si la section le réclame (via des options)
//    pour que la section puisse appeler directement tabVar.validationZones.valideReponses() (sans créer elle-même ses zones) plutôt que de les créer par ailleurs.
//    Ça suppose que l’objet ValidationZones ait une nouvelle méthode addZone (pour que la section puisse avoir des inputs dans le tableau et hors du tableau dans le même objet ValidationZones)

// @todo finir de documenter tous ces objets et params
/**
 * @typedef TabDef
 * @type Object
 * @property {Object} mef données de mise en forme
 * @property {number} mef.L largeur
 * @property {number} mef.h hauteur
 * @property {string} mef.macouleur la couleur sous la forme #rrvvbb
 * @property {string[]} liste_boutons la liste des boutons matquill à afficher
 * @property {TabDatasLigne[]} lignes
 * @property {HTMLElement[]} [validationZonesInputs] Le contenu de validationZones.zones.inputs
 * @property {string[]} [zonesdesaisie_charnieres] Des id de inputMq, affecté par tableauSignesVariations
 */
/**
 * @typedef TabDatasLigne
 * @type Object
 * @property {string} type signes|variations
 * @property {number} hauteur
 * @property {number[]} imagestheoriques
 * @property {HTMLElement[]} liste_deroulante
 * @property {string[]} zonesdesaisie des id d'éléments
 * @property {string[]} imagesreponses les réponses de l'élève dans les inputMq
 * @property {string[]} imagesreponses_x les réponses de l'élève dans les inputMq de la ligne des x
 * @property {Object} valeurs_charnieres
 * @property {string[]} valeurs_charnieres.valeurs_approchees
 * @property {string[]} valeurs_charnieres.valeurs_theoriques
 * @property {string[]} valeurs_charnieres.valeurs_affichees
 */
/**
 * Crée un tableau de signe ou un tableau de variations.
 * Attention à bien fournir tabDef.validationZonesInputs si vous utilisez aussi ValidationZones avec des zones de saisie dans le tableau
 * @param {HTMLElement|string} divparent
 * @param {string} divId L'id du div qui sera créé pour y mettre le corrigé, peut être vide (il sera généré)
 * @param {TabDef} tabDef La définition de base du tableau, une propriété perso sera ajoutée à cet objet.
 * @param {boolean} modeCorrection
 * @param {boolean} [isDebug=false] passer true pour avoir les messages de debug en console
 * @param {function} [finishCallback] une callback éventuelle qui sera appelée quand tout sera terminé
 */
export default function tableauSignesVariations (divparent, divId, tabDef, modeCorrection, isDebug, finishCallback) {
  // la création du tableau est scindée en deux parties, pour ajouter une pause après l’affichage des formules de la première colonne
  // (necessaire pour que le rendu soit effectué et donc pouvoir calculer la position des éléments pour afficher la première barre verticale
  function creationTableau1 () {
    let valeur, valeur2, valeurTheorique
    // si le tableau valeurs_charnieres.valeurs_affichees n’est pas défini je le déclare égal au tableau valeurs_theoriques
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      // var est_tableau=tabDef.valeurs_charnieres.valeurs_affichees.constructor.toString().indexOf("Array") > -1
      if (!tabDef.lignes[i].valeurs_charnieres.valeurs_affichees) {
        tabDef.lignes[i].valeurs_charnieres.valeurs_affichees = []
        for (let index = 0; index <= tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques.length - 1; index++) {
          tabDef.lignes[i].valeurs_charnieres.valeurs_affichees[index] = tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques[index]
        }
      }
      // si le tab imagestheoriques n’est pas défini je le déclare égal aux coord 0 du tableau images
      if (!tabDef.lignes[i].imagestheoriques) {
        tabDef.lignes[i].imagestheoriques = []
        for (let index = 0; index <= tabDef.lignes[i].images.length - 1; index++) {
          tabDef.lignes[i].imagestheoriques[index] = tabDef.lignes[i].images[index][0]
        }
      }
      if (!modeCorrection) {
        // pour garder la mémoire des id des listes déroulantes pour pouvoir les geler en correction
        tabDef.lignes[i].liste_deroulante = []
        // pareil pour les zones de saisie :
        tabDef.lignes[i].zonesdesaisie = []
      }
    }
    // pour les zones de saisies de la ligne des x : (on garde les id pour pouvoir les geler en correction
    tabDef.zonesdesaisie_charnieres = []

    // il faut imposer un minimum de largeur, sinon ça fait n’importe quoi (des flèches de variation qui reviennent en arrière)
    const nbCharnieres = tabDef.lignes[0].valeurs_charnieres.valeurs_theoriques.length
    const largeurMin = 150 + nbCharnieres * 80
    // on modifie l’objet passé en argument (il est utilisé en var globale dans plusieurs fonctions), tant pis…
    if (tabDef.mef.L < largeurMin) tabDef.mef.L = largeurMin
    svg.setAttribute('width', tabDef.mef.L + 2)
    let h = tabDef.mef.h
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      h += tabDef.lignes[i].hauteur
      // console.log("tabDef.lignes[i].hauteur="+tabDef.lignes[i].hauteur)
    }
    svg.setAttribute('height', h + 2)
    // pour accueillir la palette de boutons :
    j3pDiv(zoneSvgCorr, divId + 'MepPalette', '', [0, h + 10])
    // ASYNC 1
    setTimeout(() => {
      zoneSvgCorr.appendChild(svg)
      // a mon avis pas necessaire
      zoneSvgCorr.style.color = tabDef.mef.macouleur
      // Affichage de la ligne des x :
      j3pCreeRectangle(svg, {
        id: divId + 'cadre',
        x: 1,
        y: 1,
        width: tabDef.mef.L,
        height: tabDef.mef.h,
        couleur: tabDef.mef.macouleur,
        epaisseur: 1
      })
      // il me faut la liste ordonnées des valeurs charnières (valeurs qui seront affichées sur la ligne des x):
      // REMARQUE : a voir si l’on en fait un (ou des) tableaux de l’objet, si recup souhaitable pour placer un point mobile...

      // pour stocker le i dans lignes[i]
      charnieresMemoire[0] = []
      // pour l’indice correspondant de tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques.length
      charnieresMemoire[1] = []
      // et enfin je stocke si à l’origine c'était une valeur charnière ou une image (qqfois qu’on demande un antécédent....)
      charnieresMemoire[2] = []
      for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
        charnieresLignes[i] = []
        charnieresLignesApprochees[i] = []
        if (!modeCorrection) {
          // je créé également un tableau qui receuillera les réponses des élèves, stockées dans l’objet général pour récupération ultérieure
          tabDef.lignes[i].valeurs_charnieres.reponses = []
          // je créé le tableau qui receuillera les réponses de l’élève, si zone de saisie il y a (pour les images)
          tabDef.lignes[i].imagesreponses = []
          // tableau qui accueillera les reponses des eleves si on demande l’antécédent d’une image'
          tabDef.lignes[i].imagesreponses_x = []
        }
        for (let j = 0; j <= tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques.length - 1; j++) {
          valeurTheorique = tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques[j]
          valeur2 = tabDef.lignes[i].valeurs_charnieres.valeurs_approchees[j]
          if (!estDedans(charnieresApprochees, valeur2)) {
            // var valeur3=tabDef.lignes[i].valeurs_charnieres.valeurs_a_afficher[j];
            //  var valeur3=true;
            // var valeur4=tabDef.lignes[i].valeurs_charnieres.valeurs_affichees[j];
            valeur = tabDef.lignes[i].valeurs_charnieres.valeurs_affichees[j]
            if (modeCorrection && valeur === '?') {
              // si l’on corrige et qu’on demandait une zone de saisie, je remplace par la réponse de l’élève
              valeur = tabDef.lignes[i].valeurs_charnieres.reponses[j]
            }
            charnieres.push(valeur)
            charnieresApprochees.push(valeur2)
            charnieresTheoriques.push(valeurTheorique)
            charnieresMemoire[0].push(i)
            charnieresMemoire[1].push(j)
            charnieresMemoire[2].push('charniere')
          }
          if (!estDedans(charnieresLignes[i], valeurTheorique)) {
            charnieresLignes[i].push(valeurTheorique)
            charnieresLignesApprochees[i].push(valeur2)
          }
        }
        // il faut tester aussi les images :
        for (let j = 0; j <= tabDef.lignes[i].images.length - 1; j++) {
          // ce sera pour garder en mémoire les réponses élèves dans le cas où deux zones de saisies autour de la valeur interdite  (voir dans l’écouteur) :
          // console.log("-----tabDef.lignes["+i+"].imagesreponses="+tabDef.lignes[i].imagesreponses)
          if (!modeCorrection) {
            tabDef.lignes[i].imagesreponses[j] = []
          }
          valeur = tabDef.lignes[i].images[j][0]
          valeur2 = tabDef.lignes[i].imagesapprochees[j]
          valeurTheorique = tabDef.lignes[i].imagestheoriques[j]
          if (!estDedans(charnieresApprochees, valeur2)) {
            // var valeur3=tabDef.lignes[i].images_a_afficher[j];
            // var valeur=tabDef.lignes[i].images[j][0]
            // var valeur3=true;
            charnieres.push(valeur)
            // charnieresLignes[i].push(valeur)
            charnieresApprochees.push(valeur2)
            charnieresTheoriques.push(valeurTheorique)
            charnieresMemoire[0].push(i)
            charnieresMemoire[1].push(j)
            charnieresMemoire[2].push('image')
          }
          if (!estDedans(charnieresLignes[i], valeur)) {
            charnieresLignes[i].push(valeur)
          }
        }
      }
      if (isDebug) {
        console.debug('charnieres_lignes[0]=' + charnieresLignes[0])
        console.debug('charnieres_lignes[1]=' + charnieresLignes[1])
        console.debug('charnieres?length=' + charnieres.length)
        console.debug('charnieres_approchees=' + charnieresApprochees + ' et charnieres=' + charnieres + ' et charnieres_theoriques=' + charnieresTheoriques + ' et ch_m[0]=' + charnieresMemoire[0] + ' et ch_m[1]=' + charnieresMemoire[1])
        console.debug(' et charnieres_memoire[2]=' + charnieresMemoire[2])
      }

      // on trie :
      // @todo utiliser ordonnerTableau à la place, et si ça marche supprimer ordonnerTableauAvirer
      ordonnerTableauAvirer([charnieresApprochees, charnieres, charnieresTheoriques, charnieresMemoire[0], charnieresMemoire[1], charnieresMemoire[2]])
      if (isDebug) {
        console.debug('Après ordre : ch_approchees=' + charnieresApprochees + ' et ch=' + charnieres + ' et ch_theoriques=' + charnieresTheoriques + ' et ch_memoire[0]=' + charnieresMemoire[0] + ' et ch_memoire[1]=' + charnieresMemoire[1])
        console.debug(' et charnieres_memoire[2]=' + charnieresMemoire[2])
        console.debug('charnieres?length=' + charnieres.length)
      }

      // Affichage des rectangles par ligne :
      hauteurTemp = tabDef.mef.h
      const txt2Txt = []
      for (const [i, ligne] of tabDef.lignes.entries()) {
        j3pCreeRectangle(svg, {
          id: divId + 'cadre_' + i,
          x: 1,
          y: hauteurTemp,
          width: tabDef.mef.L,
          height: ligne.hauteur,
          couleur: tabDef.mef.macouleur,
          epaisseur: 1
        })
        // placement des textes dans la colonne de gauche (et on détecte la largeur max das largeurSigneDe
        if (ligne.type === 'signes') {
          // TEXTES A EXTERNALISER
          if (ligne.expression === '&1&') {
            txt2Txt[i] = 'Signe(s) de &1&'
          } else {
            txt2Txt[i] = 'Signe(s) de $' + ligne.expression + '$'
          }
        }
        if (ligne.type === 'variations') {
          txt2Txt[i] = 'Variation(s)<br/>de $' + ligne.expression + '$'
          // j’en profite pour créer un tableau qui contiendra les coordonnées des flèches.
          ligne.coord_fleches = []
        }
        j3pCreeSegment(svg, {
          id: divId + 's1' + i,
          x1: 0,
          y1: hauteurTemp + ligne.hauteur,
          x2: tabDef.mef.L,
          y2: hauteurTemp + ligne.hauteur,
          couleur: tabDef.macouleur,
          epaisseur: 1
        })
        const divSigneDe = j3pAddElt(zoneSvgCorr, 'div', '', {
          id: divId + 'signede' + i,
          style: {
            color: tabDef.mef.macouleur,
            position: 'absolute',
            top: '0',
            left: '0'
          }
        })
        // Voilà ce que j’ai enlevé dans inputmq1 : , correction: j3p.storage.solution[1]
        // Le 21/10/21 Rémi
        const { inputmqList, parent } = j3pAffiche(divSigneDe, 'Mep' + divId + 'txt2' + i, txt2Txt[i], {
          inputmq1: { texte: '' }
        })
        if (inputmqList.length > 0) {
          mqRestriction(inputmqList[0], '\\d,.+-x/', { commandes: ['fraction'] })
          mqList.push(inputmqList[0])
        }
        // var posTxt = h/(nb_lignes_signes+1)+i*h/(nb_lignes_signes+1)-monobj.getBoundingClientRect().height;
        // alignés en haut.
        let posTxt = hauteurTemp
        // pour les centrer :
        posTxt += ligne.hauteur / 2
        posTxt -= parent.getBoundingClientRect().height / 2
        divSigneDe.style.top = posTxt + 'px'
        divSigneDe.style.left = '2px'

        hauteurTemp += ligne.hauteur
      } // boucle lignes

      // un pb de détection des largeurs des codes mathquill à cause de son affichage async
      // ASYNC 2
      setTimeout(() => creationTableau2Etape1(hauteurTemp), 0)
    }, 0)
  } // creation_tableau1

  // fonction appelée à la place de creation_tableau1 lors de l’ajout d’une ligne au tableau'
  function creationTableau1bis () {
    // si le tableau valeurs_charnieres.valeurs_affichees n’est pas défini je le déclare égal au tableau valeurs_theoriques
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      if (!tabDef.lignes[i].valeurs_charnieres.valeurs_affichees) {
        tabDef.lignes[i].valeurs_charnieres.valeurs_affichees = []
        for (let index = 0; index <= tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques.length - 1; index++) {
          tabDef.lignes[i].valeurs_charnieres.valeurs_affichees[index] = tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques[index]
        }
      }
      // si le tab imagestheoriques n’est pas défini je le déclare égal aux coord 0 du tableau images
      if (!tabDef.lignes[i].imagestheoriques) {
        tabDef.lignes[i].imagestheoriques = []
        // for (let index = 0; index <= tabDef.lignes[i].images.length - 1; index++) tabDef.lignes[i].imagestheoriques[index]=tabDef.lignes[i].images[index][0];
      }
      if (!modeCorrection) {
        // pour garder la mémoire des id des listes déroulantes pour pouvoir les geler en correction
        tabDef.lignes[i].liste_deroulante = []
        // pareil pour les zones de saisie :
        tabDef.lignes[i].zonesdesaisie = []
      }
    }
    // pour les zones de saisies de la ligne des x : (on garde les id pour pouvoir les geler en correction

    svg.setAttribute('width', tabDef.mef.L + 2)
    let h = tabDef.mef.h
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      h += tabDef.lignes[i].hauteur
      // console.log("tabDef.lignes[i].hauteur="+tabDef.lignes[i].hauteur)
    }
    svg.setAttribute('height', h + 2)
    // pour accueillir la palette de boutons :
    j3pDiv(zoneSvgCorr, divId + 'MepPalette', '', [0, h])

    zoneSvgCorr.appendChild(svg)
    // a mon avis pas necessaire
    zoneSvgCorr.style.color = tabDef.mef.macouleur
    // Affichage de la ligne des x :
    j3pCreeRectangle(svg, {
      id: divId + 'cadre',
      x: 1,
      y: 1,
      width: tabDef.mef.L,
      height: tabDef.mef.h,
      couleur: tabDef.mef.macouleur,
      epaisseur: 1
    })
    hauteurTemp = tabDef.mef.h
    const txt2Txt = []
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      j3pCreeRectangle(svg, {
        id: divId + 'cadre_' + i,
        x: 1,
        y: hauteurTemp,
        width: tabDef.mef.L,
        height: tabDef.lignes[i].hauteur,
        couleur: tabDef.mef.macouleur,
        epaisseur: 1
      })
      // placement des textes dans la colonne de gauche (et on détecte la largeur max das largeurSigneDe
      if (tabDef.lignes[i].type === 'signes') {
        // TEXTES A EXTERNALISER
        if (tabDef.lignes[i].expression === '&1&') {
          txt2Txt[i] = 'Signe(s) de &1&'
        } else {
          txt2Txt[i] = 'Signe(s) de $' + tabDef.lignes[i].expression + '$'
        }
        //
      }
      if (tabDef.lignes[i].type === 'variations') {
        txt2Txt[i] = 'Variation(s) de $' + tabDef.lignes[i].expression + '$'
      }
      j3pCreeSegment(svg, {
        id: divId + 's1' + i,
        x1: 0,
        y1: hauteurTemp + tabDef.lignes[i].hauteur,
        x2: tabDef.mef.L,
        y2: hauteurTemp + tabDef.lignes[i].hauteur,
        couleur: tabDef.macouleur,
        epaisseur: 1
      })
      const divSigneDe = j3pAjouteDiv(zoneSvgCorr, divId + 'signede' + i, '', {
        style: {
          color: tabDef.mef.macouleur,
          position: 'absolute',
          top: '0',
          left: '0'
        }
      })
      // Voilà ce que j’ai enlevé dans inputmq1 : , correction: j3p.storage.solution[1]
      const { inputmqList, parent } = j3pAffiche(divSigneDe, 'Mep' + divId + 'txt2' + i, txt2Txt[i], {
        inputmq1: {
          texte: ''
        }
      })
      if (inputmqList.length > 0) {
        mqRestriction(inputmqList[0], '\\d,.+-x/', { commandes: ['fraction'] })
        mqList.push(inputmqList[0])
      }
      // var posTxt = h/(nb_lignes_signes+1)+i*h/(nb_lignes_signes+1)-monobj.getBoundingClientRect().height;
      // alignés en haut.
      let posTxt = hauteurTemp
      // pour les centrer :
      posTxt += tabDef.lignes[i].hauteur / 2
      posTxt -= parent.getBoundingClientRect().height
      divSigneDe.style.top = posTxt + 'px'
      divSigneDe.style.left = '2px'

      hauteurTemp += tabDef.lignes[i].hauteur
    }
    // un pb de détection des largeurs des codes mathquill à cause du temps d’affichage...
    setTimeout(() => {
      creationTableau2Etape2(hauteurTemp)
    }, 0)
  }

  // fonction appelée à la place de creation_tableau1 lors de l’ajout d’un intervalle au tableau'
  function creationTableau1ter (nbInt) {
    let val1, val2, longueur
    // on procède différemment : l’utilisateur a la main sur le nb d’intervalles, on part du principe qu’on n’aura pas d’images dans le tableau à placer
    // le tableau a déjà été tracé, on ne recommence pas la seconde étape de construction, on récupère la première charnière affichée, la dernière et on complete

    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      // if (tabDef.lignes[i].type=="signes"){
      val1 = tabDef.lignes[i].valeurs_charnieres.valeurs_affichees[0]
      longueur = tabDef.lignes[i].valeurs_charnieres.valeurs_affichees.length
      val2 = tabDef.lignes[i].valeurs_charnieres.valeurs_affichees[longueur - 1]
      tabDef.lignes[i].valeurs_charnieres.valeurs_affichees = []
      tabDef.lignes[i].valeurs_charnieres.valeurs_affichees.push(val1)
      for (let j = 1; j < nbInt; j++) {
        tabDef.lignes[i].valeurs_charnieres.valeurs_affichees.push('?')
      }
      tabDef.lignes[i].valeurs_charnieres.valeurs_affichees.push(val2)
      //   }

      // si le tab imagestheoriques n’est pas défini je le déclare égal aux coord 0 du tableau images
      if (!tabDef.lignes[i].imagestheoriques) {
        tabDef.lignes[i].imagestheoriques = []
        for (let index = 0; index <= tabDef.lignes[i].images.length - 1; index++) {
          tabDef.lignes[i].imagestheoriques[index] = tabDef.lignes[i].images[index][0]
        }
      }
      if (!modeCorrection) {
        // pour garder la mémoire des id des listes déroulantes pour pouvoir les geler en correction
        tabDef.lignes[i].liste_deroulante = []
        // pareil pour les zones de saisie :
        tabDef.lignes[i].zonesdesaisie = []
      }
    }
    // on reset les charnières en conservant les extrémités et en mettant des ? au milieu
    const last = charnieres.pop()
    charnieres.splice(1)
    for (let j = 1; j < nbInt; j++) {
      charnieres.push('?')
    }
    charnieres.push(last)
    if (isDebug) console.debug('tabDef dans j3ptableau_signes_variations', tabDef)
    // Il reste à nettoyer le contenu des lignes puis à appeler l’étape2 de creation_tableau2
    for (let i = 0; i < charnieres.length; i++) {
      if (j3pElement(divId + 'zoneNulle' + i, null)) {
        j3pDetruit(divId + 'zoneNulle' + i)
      }
    }
    for (let i = 0; i < divList.length; i++) {
      for (let k = 0; k < divList[i].length; k++) {
        j3pDetruit(divList[i][k])
      }
    }
    setTimeout(() => {
      creationTableau2Etape2(hauteurTemp)
    }, 0)
  }

  // fonction qui teste la présence de element dans tab
  function estDedans (tab, element) {
    return tab.includes(element)
  }

  /**
   * Retourne un nouveau tableau sans l’élément dont on file l’index (sans modifier le tableau d’origne)
   * Rq https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/splice fait ça en natif (en modifiant le tableau)
   * @private
   * @param {any[]} tab
   * @param {number} index
   * @return {any[]}
   */
  function supprimeElement (tab, index) {
    return tab.slice(0, index).concat(tab.slice(index + 1))
  }

  // fonction qui ordonne de la même façon le tableau2 que le tableau1, on tient compte de la présence éventuelle de -inf et +inf
  // Elle a été réécrite dans ordonnerTableau (avec du for & les évals virés)
  // À virer quand la remplaçante aura été certifiée bon pour le service
  function ordonnerTableauAvirer (tableau) {
    let contientMoinsInf, contientPlusInf
    const tabs = {}
    for (let i = 0; i <= 5; i++) tabs['tableau' + i + '_temp'] = tableau[i]
    if (estDedans(tableau[0], '-inf') || estDedans(tableau[0], '-\\infty')) {
      contientMoinsInf = true
      const lindexDuMoinsInf = Math.max(tableau[0].indexOf('-inf'), tableau[0].indexOf('-\\infty'))
      for (let i = 0; i <= 5; i++) tabs['temp_moinsinf' + i] = tabs['tableau' + i + '_temp'][lindexDuMoinsInf]
      if (isDebug) console.debug('lindexDuMoinsInf=' + lindexDuMoinsInf)
      // on regarde si c’est true ou false pour le remettre à la fin (à mon avis ne sert à rien)
      // var affichage_moins_inf=tableau2_temp[lindexDuMoinsInf]
      for (let i = 0; i <= 5; i++) tabs['tableau' + i + '_temp'] = supprimeElement(tabs['tableau' + i + '_temp'], lindexDuMoinsInf)
    } else {
      contientMoinsInf = false
    }
    if (estDedans(tableau[0], '+inf') || estDedans(tableau[0], '+\\infty')) {
      contientPlusInf = true
      let lindexDuPlusInf = Math.max(tableau[0].indexOf('+inf'), tableau[0].indexOf('+\\infty'))
      if (contientMoinsInf) {
        lindexDuPlusInf--
      }// car on enlevera un élément...
      for (let i = 0; i <= 5; i++) tabs['temp_plusinf' + i] = tabs['tableau' + i + '_temp'][lindexDuPlusInf]
      if (isDebug) console.debug('lindexDuPlusInf=' + lindexDuPlusInf)
      // on regarde si c’est true ou false pour le remettre à la fin
      // var affichage_plus_inf=tableau2_temp[lindexDuPlusInf]
      for (let i = 0; i <= 5; i++) tabs['tableau' + i + '_temp'] = supprimeElement(tabs['tableau' + i + '_temp'], lindexDuPlusInf)
    } else {
      contientPlusInf = false
    }
    for (let t = 1; t < tabs.tableau0_temp.length; t++) {
      for (let i = 0; i < tabs.tableau0_temp.length - 1; i++) {
        // console.log("tabs.tableau0_temp["+i+"]="+tabs.tableau0_temp[i]+" et tabs.tableau0_temp["+(i+1)+"]="+tabs.tableau0_temp[i+1])
        if (tabs.tableau0_temp[i] > tabs.tableau0_temp[i + 1]) {
          // je permute :
          for (let k = 1; k <= tableau.length - 1; k++) {
            const temp = tabs['tableau' + k + '_temp'][i + 1]
            tabs['tableau' + k + '_temp'][i + 1] = tabs['tableau' + k + '_temp'][i]
            tabs['tableau' + k + '_temp'][i] = temp
          }
          const k2 = tabs.tableau0_temp[i] - tabs.tableau0_temp[i + 1]
          tabs.tableau0_temp[i] -= k2
          tabs.tableau0_temp[i + 1] += k2
        }
      }
    }
    const tab = {}
    tab.tab0 = []
    tab.tab1 = []
    tab.tab2 = []
    tab.tab3 = []
    tab.tab4 = []
    tab.tab5 = []
    if (contientMoinsInf) {
      tab.tab0.push(tabs.temp_moinsinf0)
      tab.tab1.push(tabs.temp_moinsinf1)
      tab.tab2.push(tabs.temp_moinsinf2)
      tab.tab3.push(tabs.temp_moinsinf3)
      tab.tab4.push(tabs.temp_moinsinf4)
      tab.tab5.push(tabs.temp_moinsinf5)
    }
    for (let i = 0; i < tabs.tableau0_temp.length; i++) {
      tab.tab0.push(tabs.tableau0_temp[i])
      tab.tab1.push(tabs.tableau1_temp[i])
      tab.tab2.push(tabs.tableau2_temp[i])
      tab.tab3.push(tabs.tableau3_temp[i])
      tab.tab4.push(tabs.tableau4_temp[i])
      tab.tab5.push(tabs.tableau5_temp[i])
    }
    if (contientPlusInf) {
      if (isDebug) console.debug('j’ai du +inf et avant mon tableau= ' + tab.tab1)
      tab.tab0.push(tabs.temp_plusinf0)
      tab.tab1.push(tabs.temp_plusinf1)
      tab.tab2.push(tabs.temp_plusinf2)
      tab.tab3.push(tabs.temp_plusinf3)
      tab.tab4.push(tabs.temp_plusinf4)
      tab.tab5.push(tabs.temp_plusinf5)
      if (isDebug) console.debug('APRES : ' + tab.tab1)
    }
    return tab
  }

  function affichePaletteMQ (id) {
    // j3pEmpty('MepBarreBoutonsMQ') // devenu inutile car j3pPaletteMathquill le détruira s’il existe déjà
    j3pPaletteMathquill(divId + 'MepPalette', id, { liste: tabDef.liste_boutons })
  }

  function positionneZoneCharnieres (id) {
    // on passe en paramètre un txt du style  divId+"nomzone_nulle"+moni+"inputmq1".
    // PB dans la boucle la valeur de i a été incrémentée, en la mettant dans le nom de l’id on peut la récupérer
    const pos1 = id.indexOf('nomzone_nulle')
    const pos2 = id.indexOf('zone_nulle')
    const pos3 = id.indexOf('inputmq1')
    const posi = id.indexOf('nomzone_nulle') + 13
    const moni = Number(id.charAt(posi))
    // pour se rappeler de quelle ligne c'était une charnière...
    const ancienI = charnieresMemoire[0][moni - 1]
    // et son index dans ce tableau.
    const ancienIndice = charnieresMemoire[1][moni - 1]
    if (isDebug) console.debug('moni-1=' + (moni - 1) + 'ancienI=' + ancienI + ' et ancienIndice=' + ancienIndice)
    const lareponse = $('#' + id).mathquill('latex')
    if (charnieresMemoire[2][moni - 1] === 'charniere') {
      tabDef.lignes[ancienI].valeurs_charnieres.reponses[ancienIndice] = lareponse
    }
    if (charnieresMemoire[2][moni - 1] === 'image') {
      tabDef.lignes[ancienI].imagesreponses_x[ancienIndice] = lareponse
    }
    const elt = j3pElement(id.substring(0, pos1) + id.substring(pos2, pos3))
    if (isDebug) console.debug('largeurSigneDe=' + largeurSigneDe)
    if (isDebug) console.debug('nbRacines=' + nbRacines)
    if (moni === 1) {
      // console.log('valeur de gauche left à ' + (largeurSigneDe + (moni - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1)))
      elt.style.left = (largeurSigneDe + (moni - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1)) + 'px'
    } else {
      if (moni === nbRacines) {
        if (isDebug) console.debug('valeur de droite left à ' + (largeurSigneDe + (moni - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1) - elt.getBoundingClientRect().width - 4))
        elt.style.left = (largeurSigneDe + (moni - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1) - elt.getBoundingClientRect().width - 4) + 'px'
      } else {
        if (isDebug) console.debug('valeur qcq left à ' + (largeurSigneDe + (moni - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1) - elt.getBoundingClientRect().width / 2))
        elt.style.left = (largeurSigneDe + (moni - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1) - elt.getBoundingClientRect().width / 2) + 'px'
      }
    }
    if (isDebug) console.debug('elt.style.left=' + elt.style.left)
    elt.style.top = ((tabDef.mef.h) / 2 - elt.getBoundingClientRect().height / 2) + 'px'
    // j’ajoute également l’écouteur pour la palette car sinon, si on tabule, la palette n’est pas la bonne...
    affichePaletteMQ(divId + 'nomzone_nulle' + moni + 'inputmq1')
  } // positionneZoneCharnieres

  function positionneZoneSignes (id, hauteurTemp) {
    // PB i j k  virer nom et inputmq1 pour récupérer le bon nom et les bonnes valeurs de i j k (incrémentées) sinon
    const pos0 = id.indexOf(divId)
    const pos1 = id.indexOf('nomimage_fct')
    const pos2 = id.indexOf('imageFct')
    const pos3 = id.indexOf('inputmq1')
    // la valeur de j était indiquée après nomimage_fct
    const posj = pos1 + 12
    // A FAIRE : changer les charAt par des substring ? (bug si >9 mais faut pas pousser...)
    const monj = id.charAt(posj)
    // la valeur de i avant
    const moni = Number(id.charAt(pos1 - 1))
    // la valeur de k avant :
    const monk = Number(id.charAt(3))
    if (isDebug) console.debug('===========================')
    if (isDebug) console.debug('pos1=' + pos1 + ' et pos2=' + pos2 + ' et pos3=' + pos3 + 'monj=' + monj + ' moni=' + moni + ' monk=' + monk)
    const elt = j3pElement(id.substring(pos0, pos1) + id.substring(pos2, pos3))
    if (isDebug) console.debug('elt=' + id.substring(pos0, pos1) + id.substring(pos2, pos3))
    const laLargeur = elt.offsetLeft
    if (isDebug) console.debug('laLargeur=' + laLargeur)
    if (monj !== 0) {
      // la racine de gauche je décale pas elle est alignée à gauche
      // je redéfinis le positionnement...
      let leDecalage
      if (monj === nbRacines) {
        // la racine de droite
        if (isDebug) console.debug('Racine de droite')
        // var pos3=pos2-elt.getBoundingClientRect().width;
        leDecalage = elt.getBoundingClientRect().width
      } else {
        // cas général
        // var pos3=pos2-elt.getBoundingClientRect().width/2
        leDecalage = elt.getBoundingClientRect().width / 2
        if (isDebug) console.debug('CAS GENERAL ' + leDecalage)
      }

      // elt.style.left = pos3+"px";
      const valeurX = positionValeursSignes[monj]
      if (isDebug) console.debug('valeurX=' + valeurX)
      elt.style.left = valeurX - leDecalage + 'px'
    }
    elt.style.top = (j3pNombre(hauteurTemp) + tabDef.lignes[moni].hauteur / 2 - elt.getBoundingClientRect().height / 2) + 'px'
    // on renseigne le tableau des réponses :
    // const lareponse = $('#' + id).mathquill('latex')
    // console.log("lareponse="+lareponse+" id="+id);
    tabDef.lignes[moni].imagesreponses[monk] = $('#' + id).mathquill('latex')
    // console.log("tabDef.lignes["+moni+"].imagesreponses="+tabDef.lignes[moni].imagesreponses);
    // j’ajoute également l’écouteur pour la palette car sinon, si on tabule, la palette n’est pas la bonne...
    affichePaletteMQ('tab' + monk + divId + moni + 'nomimage_fct' + monj + 'inputmq1')
  } // positionneZoneSignes

  function positionneZoneVariations (id) {
    // on passe en paramètre un txt du style  k+divId+"image_txt"+i+jEquivalent+"inputmq1".
    // PB dans la boucle les valeurs de i,j,k ont été incrémentées, en les mettant dans le nom de l’id on peut les récupérer
    // var pos0=id.indexOf(divId)
    // var pos1=id.indexOf("image_txt")
    const pos3 = id.indexOf('inputmq1')
    const lareponse = $('#' + id).mathquill('latex')
    let leTexte, leDecalage
    if (id.includes('image_gauche')) {
      // appelé par une zone de texte à gauche de la double barre
      leTexte = 'image_gauche'
      leDecalage = 12
    } else {
      if (id.includes('image_droite')) {
        leTexte = 'image_droite'
        leDecalage = 12
      } else {
        leTexte = 'image_txt'
        leDecalage = 9
      }
    }
    const posi = id.indexOf(leTexte) + leDecalage
    const moni = Number(id.charAt(posi))
    const monj = Number(id.charAt(posi + 1))
    // la valeur de k avant :
    const monk = Number(id.charAt(3))
    // console.log("moni-1="+(moni-1)+"monj="+monj)
    let eltImgGauche, valeurX, valeurY, decalage
    if (id.indexOf('image_gauche') !== -1) {
      // appelé par une zone de texte à gauche de la double barre
      tabDef.lignes[moni].imagesreponses[monk][0] = lareponse
      eltImgGauche = j3pElement(divId + 'image_gauche' + id.substring(posi, pos3))
      // console.log("tabDef.lignes["+moni+"].imagesreponses["+monk+"][0]="+tabDef.lignes[moni].imagesreponses[monk][0])
      valeurX = tabDef.lignes[moni].images_coord_x[monk][0]
      valeurY = tabDef.lignes[moni].images_coord_y[monk][0]
      decalage = eltImgGauche.getBoundingClientRect().width
    } else {
      if (id.indexOf('image_droite') !== -1) {
        // appelé par une zone de texte à gauche de la double barre
        tabDef.lignes[moni].imagesreponses[monk][1] = lareponse
        eltImgGauche = j3pElement(divId + 'image_droite' + id.substring(posi, pos3))
        // console.log("tabDef.lignes["+moni+"].imagesreponses["+monk+"][1]="+tabDef.lignes[moni].imagesreponses[monk][1])
        valeurX = tabDef.lignes[moni].images_coord_x[monk][1]
        valeurY = tabDef.lignes[moni].images_coord_y[monk][1]
      } else {
        tabDef.lignes[moni].imagesreponses[monk] = lareponse
        eltImgGauche = j3pElement(divId + 'zone_image' + id.substring(posi, pos3))
        valeurX = tabDef.lignes[moni].images_coord_x[monk]
        valeurY = tabDef.lignes[moni].images_coord_y[monk]
        decalage = eltImgGauche.getBoundingClientRect().width / 2
        if (monj === 0) {
          // premiere charniere:
          decalage = 0
        }
        if (monj === charnieresTheoriques.length - 1) {
          // derniere charniere
          decalage = eltImgGauche.getBoundingClientRect().width
        }
      }
    }
    // console.log("moni="+moni)tabDef.lignes[ancienI]
    // console.log("------id.substring(0,pos1)="+id.substring(0,pos1)+" et id.substring(posi,pos3)="+id.substring(posi,pos3))
    // je repositionne ma zone de saisie :
    if (!id.includes('image_droite')) eltImgGauche.style.left = valeurX - decalage + 'px'
    eltImgGauche.style.top = (valeurY - eltImgGauche.getBoundingClientRect().height / 2) + 'px'
    // j’ajoute également l’écouteur pour la palette car sinon, si on tabule, la palette n’est pas la bonne...
    affichePaletteMQ('tab' + monk + divId + leTexte + moni + monj + 'inputmq1')
  }

  function creerFleches (i) {
    divList[i] = []
    hauteurTemp = tabDef.mef.h
    for (let indexI = 0; indexI <= i - 1; indexI++) {
      hauteurTemp += tabDef.lignes[indexI].hauteur
    }
    // les coordonnées des charnières
    const tabXCharnieres = []
    const tabYCharnieres = []
    // les y des débuts de chaque variation
    tabYCharnieres[0] = []
    // les y des fins de chaque variation
    tabYCharnieres[1] = []
    // pas forcément utile...

    for (let j = 0; j <= tabDef.lignes[i].variations.length - 1; j++) {
      tabDef.lignes[i].coord_fleches[j] = {}// pour stocker les coordonnées de la flèche, pour le point mobile.
      // une flèche par variation
      // console.log("sens="+tabDef.lignes[i].variations[j]+" et j="+j);
      if (isDebug) console.debug('tabDef.lignes[' + i + '].intervalles[' + j + '][0]=' + tabDef.lignes[i].intervalles[j][0])
      const charniereDebut = tabDef.lignes[i].intervalles[j][0]
      const charniereFin = tabDef.lignes[i].intervalles[j][1]
      // je récupère la position en x de mes deux charnières
      // POSSIBILITE : NE PAS TRACER LES SEGMENTS DANS LE SWITCH, LE FAIRE A LA FIN APRES AVOIR CONSTRUIT LES IMAGES POUR TENIR COMPTE DE LA LARGEUR DES IMAGES
      if (isDebug) console.debug('nbRacines=' + nbRacines)
      let xdebut = largeurSigneDe
      let xfin = tabDef.mef.L
      for (let k = 0; k <= charnieres.length - 1; k++) {
        if (j3pNombre(charniereDebut) === charnieresApprochees[k]) {
          // console.log("le debut est en "+charnieres[k])
          xdebut = (largeurSigneDe + k * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1)) + 30
        }
        if (j3pNombre(charniereFin) === charnieresApprochees[k]) {
          // console.log("la fin est en "+charnieres[k])
          xfin = (largeurSigneDe + k * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1)) - 30
        }
      }
      tabXCharnieres.push(xdebut - 30)
      if (j === tabDef.lignes[i].variations.length - 1) {
        tabXCharnieres.push(xfin + 30)
      }
      if (isDebug) console.debug('&&&&&&&&&&&&&&&&&&&&&&  x_debut=' + xdebut + ' et x_fin=' + xfin)
      if (isDebug) console.debug('&&&&&&&&&&&&&&&&&&&&&&  largeurSigneDe=' + largeurSigneDe + ' et nbRacines=' + nbRacines)
      const longFleche = 10
      const monepaisseur = 1
      // si on redessine les flèches, il faut effacer les anciennes, l’épaisseur est également plus grande pour pouvoir les repérer et cliquer facilement
      let monepaisseur2, monepaisseur3
      if (tabDef.lignes[i].cliquable[j]) {
        monepaisseur2 = monepaisseur + 2
        monepaisseur3 = monepaisseur + 8
        // on supprime les <line> des anciennes fleches (si elles existent, sinon ça fait rien)
        j3pDetruit(
          divId + 's5' + i + j,
          divId + 's6' + i + j,
          divId + 's7' + i + j,
          divId + 's8' + i + j
        )
      } else {
        monepaisseur2 = monepaisseur
      }
      const decalagexFleche = (j === 0) ? 20 : 0
      const decalagexFleche2 = (j === tabDef.lignes[i].variations.length - 1) ? 20 : 0
      const decalageyFleche = 5
      let segment2, longueur, hauteur, decX1, decY1, decX2, decY2, ydebut, yfin
      switch (tabDef.lignes[i].variations[j]) {
        case 'croit':
          ydebut = hauteurTemp + tabDef.lignes[i].hauteur - 15
          yfin = hauteurTemp + 15
          j3pCreeSegment(svg, {
            id: divId + 's5' + i + j,
            x1: xdebut + decalagexFleche,
            y1: ydebut - decalageyFleche,
            x2: xfin - decalagexFleche2,
            y2: yfin + decalageyFleche,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          divList[i].push(divId + 's5' + i + j)
          segment2 = j3pCreeSegment(svg, {
            id: divId + 's8' + i + j,
            x1: xdebut,
            y1: ydebut - decalageyFleche,
            x2: xfin - decalagexFleche2,
            y2: yfin + decalageyFleche,
            opacite: 0,
            couleur: 'rgb(255,0,0)',
            epaisseur: monepaisseur3
          })
          divList[i].push(divId + 's8' + i + j)
          // la flèche au bout, avec calcul savant du décalage... :
          longueur = xfin - xdebut
          hauteur = -yfin + ydebut
          decX1 = Math.sin(Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          decY1 = Math.cos(Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          decX2 = Math.cos(Math.PI / 2 - Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          decY2 = Math.sin(Math.PI / 2 - Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          // console.log("j="+j+"et xdebut="+xdebut+" et ydebut="+ydebut+" et xfin="+xfin+" et yfin="+yfin)
          j3pCreeSegment(svg, {
            id: divId + 's6' + i + j,
            x1: xfin - decalagexFleche2,
            y1: yfin + decalageyFleche,
            x2: xfin - decalagexFleche2 - decX1,
            y2: yfin + decalageyFleche + decY1,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          divList[i].push(divId + 's6' + i + j)
          j3pCreeSegment(svg, {
            id: divId + 's7' + i + j,
            x1: xfin - decalagexFleche2,
            y1: yfin + decalageyFleche,
            x2: xfin - decalagexFleche2 - decX2,
            y2: yfin + decalageyFleche + decY2,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          divList[i].push(divId + 's7' + i + j)

          if (tabDef.lignes[i].cliquable[j] && !modeCorrection) {
            segment2.addEventListener('click', reponseClic.bind(null, i, j))
            segment2.addEventListener('mouseover', onMouseOver)
          }
          break

        case 'decroit':
          yfin = hauteurTemp + tabDef.lignes[i].hauteur - 15
          ydebut = hauteurTemp + 15
          j3pCreeSegment(svg, {
            id: divId + 's5' + i + j,
            x1: xdebut + decalagexFleche,
            y1: ydebut + decalageyFleche,
            x2: xfin - decalagexFleche2,
            y2: yfin - decalageyFleche,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          segment2 = j3pCreeSegment(svg, {
            id: divId + 's8' + i + j,
            x1: xdebut,
            y1: ydebut + decalageyFleche,
            x2: xfin - decalagexFleche2,
            y2: yfin - decalageyFleche,
            opacite: 0,
            couleur: 'rgb(255,0,0)',
            epaisseur: monepaisseur3
          })
          divList[i].push(divId + 's5' + i + j)
          divList[i].push(divId + 's8' + i + j)
          // la flèche au bout, avec calcul savant du décalage... :
          // console.log("x_debut="+xdebut+" et x_fin="+xfin)
          longueur = xfin - xdebut
          hauteur = yfin - ydebut
          decX1 = Math.sin(Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          decY1 = Math.cos(Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          decX2 = Math.cos(Math.PI / 2 - Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          decY2 = Math.sin(Math.PI / 2 - Math.atan((longueur / hauteur)) - 22.5 * Math.PI / 180) * longFleche
          j3pCreeSegment(svg, {
            id: divId + 's6' + i + j,
            x1: xfin - decalagexFleche2,
            y1: yfin - decalageyFleche,
            x2: xfin - decalagexFleche2 - decX1,
            y2: yfin - decalageyFleche - decY1,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          j3pCreeSegment(svg, {
            id: divId + 's7' + i + j,
            x1: xfin - decalagexFleche2,
            y1: yfin - decalageyFleche,
            x2: xfin - decalagexFleche2 - decX2,
            y2: yfin - decalageyFleche - decY2,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          divList[i].push(divId + 's6' + i + j)
          divList[i].push(divId + 's7' + i + j)

          if (tabDef.lignes[i].cliquable[j] && !modeCorrection) {
            segment2.addEventListener('click', reponseClic.bind(null, i, j))
            segment2.addEventListener('mouseover', onMouseOver)
          }
          break

        case 'const':
          ydebut = hauteurTemp + tabDef.lignes[i].hauteur / 2
          yfin = ydebut
          j3pCreeSegment(svg, {
            id: divId + 's5' + i + j,
            x1: xdebut,
            y1: ydebut,
            x2: xfin,
            y2: yfin,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          segment2 = j3pCreeSegment(svg, {
            id: divId + 's8' + i + j,
            x1: xdebut,
            y1: ydebut,
            x2: xfin,
            y2: yfin,
            opacite: 0,
            couleur: 'rgb(255,0,0)',
            epaisseur: monepaisseur3
          })
          divList[i].push(divId + 's5' + i + j)
          divList[i].push(divId + 's8' + i + j)
          longueur = xfin - xdebut
          hauteur = yfin - ydebut
          decX1 = Math.cos(22.5 * Math.PI / 180) * longFleche
          decY1 = Math.sin(22.5 * Math.PI / 180) * longFleche
          decX2 = decX1
          decY2 = decY1
          j3pCreeSegment(svg, {
            id: divId + 's6' + i + j,
            x1: xfin,
            y1: yfin,
            x2: xfin - decX1,
            y2: yfin - decY1,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          j3pCreeSegment(svg, {
            id: divId + 's7' + i + j,
            x1: xfin,
            y1: yfin,
            x2: xfin - decX2,
            y2: yfin + decY2,
            couleur: tabDef.mef.macouleur,
            epaisseur: monepaisseur2
          })
          divList[i].push(divId + 's6' + i + j)
          divList[i].push(divId + 's7' + i + j)
          if (tabDef.lignes[i].cliquable[j] && !modeCorrection) {
            segment2.addEventListener('click', reponseClic.bind(null, i, j))
            segment2.addEventListener('mouseover', onMouseOver)
          }
          break
      }
      // je mets au chaud mes ordonnées de mes charnières :
      tabYCharnieres[0].push(ydebut)
      // le yfin peut être différent du ydebut suivant...
      tabYCharnieres[1].push(yfin)
      /* if (j==tabDef.lignes[i].variations.length-1){
                    tabYCharnieres.push(yfin);
            } */
      // je garde au chaud les coordonnées de mes flèches, pour affichage d’un point mobile :
      tabDef.lignes[i].coord_fleches[j].x_debut = xdebut
      tabDef.lignes[i].coord_fleches[j].x_fin = xfin
      tabDef.lignes[i].coord_fleches[j].y_debut = ydebut
      tabDef.lignes[i].coord_fleches[j].y_fin = yfin
      // A FAIRE : modifier ces coord au clic (à voir)
    }
    if (isDebug) console.debug(tabXCharnieres)
    if (isDebug) console.debug('=====les y des debuts=' + tabYCharnieres[0])
    if (isDebug) console.debug('=====les y des fins=' + tabYCharnieres[1])
    // tableaux qui me serviront à garder les coord des images
    tabDef.lignes[i].images_coord_x = []
    tabDef.lignes[i].images_coord_y = []

    for (let k = 0; k <= tabDef.lignes[i].images.length - 1; k++) {
      const imageX = tabDef.lignes[i].imagestheoriques[k]
      let imageY = tabDef.lignes[i].images[k][1]
      if (modeCorrection && imageY === '?') {
        imageY = tabDef.lignes[i].imagesreponses[k]
      }
      if (isDebug) console.debug('imageX=' + imageX + ' et imageY=' + imageY)
      // je récupère la position en x de mes deux charnières
      let premiereCharniere = false
      let derniereCharniere = false
      // Je balaye les charnières pour savoir quel est l’indice k correspondant'
      let jEquivalent = 0
      let charniereEnCours = charnieresTheoriques[0]
      let xDeLImage = largeurSigneDe
      for (let j = 0; j <= charnieresTheoriques.length - 1; j++) {
        if (imageX === charnieresTheoriques[j]) {
          if (isDebug) console.debug('le debut est en ' + charnieresTheoriques[j] + ' AVEC nbRacines=' + nbRacines)
          xDeLImage = (largeurSigneDe + j * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1))
          // je garde en mémoire la charnière trouvée
          charniereEnCours = charnieresTheoriques[j]
          if (j === 0) {
            premiereCharniere = true
          }
          if (j === charnieresTheoriques.length - 1) {
            derniereCharniere = true
          }
          jEquivalent = j
          // ce sera donc tabDef.lignes[i].imagestheoriques[k]=charnieres_theoriques[jEquivalent]
        }
      }
      // console.log("xDeLImage="+xDeLImage)
      let yDeLImage, yDeLImageGauche
      if (estDedans(tabXCharnieres, xDeLImage)) {
        // je détermine son ordonnée en y en récupérant d’abord son index dans le tabXCharnieres
        const position = tabXCharnieres.indexOf(xDeLImage)
        yDeLImage = tabYCharnieres[0][position]
        yDeLImageGauche = tabYCharnieres[1][position - 1]
        // cas particulier si j’arrive à la dernière charnière :
        if (position === tabXCharnieres.length - 1) {
          yDeLImage = yDeLImageGauche
        }
        // console.log("xDeLImage"+xDeLImage+" avec imageX="+imageX+" est une charniere et coord en y="+yDeLImage)
      } else {
        // je détermine son ordonnée par interpolation affine, il faut d’abord savoir entre quelles valeurs charnières elle se trouve
        let compteur = 0
        let xCharniere = tabXCharnieres[compteur]
        let a = xCharniere
        while (xCharniere < xDeLImage) {
          compteur++
          a = xCharniere
          xCharniere = tabXCharnieres[compteur]
        }
        const b = xCharniere
        const position1 = tabXCharnieres.indexOf(a)
        const y1 = tabYCharnieres[0][position1]
        // var position2 = tabXCharnieres.indexOf(b)
        const y2 = tabYCharnieres[1][position1]
        // console.log("a="+a+" et b="+b+" y1="+y1+" et y2="+y2)
        // l’interpolation affine :
        const coeff = (xDeLImage - a) / (b - a)
        yDeLImage = y1 + coeff * (y2 - y1)
        // console.log("xDeLImage"+xDeLImage+" avec imageX="+imageX+" n' est pas  une charniere et coord en y="+yDeLImage)
      }

      // affichage de l’image :
      // d’abord on efface les éventuelles anciennes :
      j3pDetruit(
        // ATTENTION, lui contient un input, on le supprime ici et on le recrée plus loin, mais ValidationZones l’aura perdu et valideUneZone répondra toujours false
        // => il faudra remettre le bon input dans les zones plus loin
        divId + 'zone_image' + i + jEquivalent,
        divId + 'image_gauche' + i + jEquivalent,
        divId + 'image_droite' + i + jEquivalent
      )
      let lid
      if (imageY.indexOf('||') !== -1 || imageY === 'I') {
        if (isDebug) console.debug(imageY + ' contient une valeur interdite')
        // au cas où il y ait des zones de saisie je garde en 0 et en 1 leurs coordonnées pour les repositionner correctement
        tabDef.lignes[i].images_coord_x[k] = []
        tabDef.lignes[i].images_coord_y[k] = []
        // je regarde si qqchose avant et/ou après
        let imageYAvant = ''
        let imageYApres = ''
        if (imageY.length === 2) {
          // pas d’image
          if (isDebug) console.debug('pas d’image juste une valeur interdite')
        } else {
          if (imageY.indexOf('||') !== 0) {
            // une image avant :
            imageYAvant = imageY.substring(0, imageY.indexOf('||'))
          }
          if (imageY.indexOf('||') !== imageY.length - 2) {
            // une image après :
            imageYApres = imageY.substring(imageY.indexOf('||') + 2, imageY.length)
          }
        }
        if (modeCorrection) {
          if (imageYAvant === '?') {
            imageYAvant = tabDef.lignes[i].imagesreponses[k][0]// imagesreponses est un tableau à un (une seule limite à gauche ou à droite - à verifier) ou deux élements
          }
          if (imageYApres === '?') {
            // console.log("IMAGE Y APRES VAUT ?")
            // console.log("tabDef.lignes["+i+"].imagesreponses["+k+"][0]=",tabDef.lignes[i].imagesreponses[k][0]," et tabDef.lignes[i].imagesreponses[k][1]=",tabDef.lignes[i].imagesreponses[k][1])
            if (tabDef.lignes[i].imagesreponses[k][1]) {
              imageYApres = tabDef.lignes[i].imagesreponses[k][1]
            } else {
              imageYApres = tabDef.lignes[i].imagesreponses[k][0]// il n’y avait qu’une limite à droite
            }
            // console.log("IMAGE Y APRES VAUT ",imageYApres)
          }
        }
        if (modeCorrection && isDebug) console.debug('------imageYAvant=' + imageYAvant + ' et imageYApres=' + imageYApres)
        if (imageYAvant === '-inf') imageYAvant = '-\\infty'
        if (imageYAvant === '+inf') imageYAvant = '+\\infty'
        if (imageYApres === '-inf') imageYApres = '-\\infty'
        if (imageYApres === '+inf') imageYApres = '+\\infty'
        // on trace la double barre
        let decalage = -3
        if (premiereCharniere) decalage = -5
        if (derniereCharniere) decalage = 0
        // la double barre
        if (!j3pElement(divId + 's2' + i + jEquivalent, null)) {
          j3pCreeSegment(svg, {
            id: divId + 's2' + i + jEquivalent,
            x1: xDeLImage - decalage,
            y1: hauteurTemp,
            x2: xDeLImage - decalage,
            y2: hauteurTemp + tabDef.lignes[i].hauteur,
            couleur: tabDef.mef.macouleur,
            epaisseur: 1
          })
        }
        if (!j3pElement(divId + 's3' + i + jEquivalent, null)) {
          j3pCreeSegment(svg, {
            id: divId + 's3' + i + jEquivalent,
            x1: xDeLImage - decalage - 5,
            y1: hauteurTemp,
            x2: xDeLImage - decalage - 5,
            y2: hauteurTemp + tabDef.lignes[i].hauteur,
            couleur: tabDef.mef.macouleur,
            epaisseur: 1
          })
        }
        divList[i].push(divId + 's2' + i + jEquivalent)

        divList[i].push(divId + 's3' + i + jEquivalent)

        // les valeurs
        const styleImage = {}
        styleImage.style = {}
        styleImage.style.position = 'absolute'
        styleImage.style.top = '0px'
        styleImage.style.left = '0px'
        styleImage.style.color = tabDef.mef.macouleur
        // var imageGauche = j3pAjouteDiv(zoneSvgCorr, divId + 'image_gauche' + i + jEquivalent, '', { style: 'position:absolute;top:0px;left:0px;color:' + tabDef.mef.macouleur })
        // var imageDroite = j3pAjouteDiv(zoneSvgCorr, divId + 'image_droite' + i + jEquivalent, '', { style: 'position:absolute;top:0px;left:0px;color:' + tabDef.mef.macouleur })
        j3pDetruit(divId + 'image_gauche' + i + jEquivalent)
        j3pDetruit(divId + 'image_droite' + i + jEquivalent)
        const imageGauche = j3pAjouteDiv(zoneSvgCorr, divId + 'image_gauche' + i + jEquivalent, '', styleImage)
        const imageDroite = j3pAjouteDiv(zoneSvgCorr, divId + 'image_droite' + i + jEquivalent, '', styleImage)
        divList[i].push(divId + 'image_gauche' + i + jEquivalent)
        divList[i].push(divId + 'image_droite' + i + jEquivalent)

        if (imageYAvant === '?') {
          // une zone de saisie à gauche :
          // cas particulier : si l’on clique sur une flèche et qu’une zone de saisie il faut pas effacer le contenu de la zone :
          // on teste donc si imagesreponses est pas complété (sinon on récupère son contenu)
          let leTexteGauche = ''
          if (tabDef.lignes[i].imagesreponses[k][0]) leTexteGauche = tabDef.lignes[i].imagesreponses[k][0]
          const zoneImageGauche = j3pAffiche(divId + 'image_gauche' + i + jEquivalent, 'tab' + k + divId + 'image_gauche' + i + jEquivalent, '&1&',
            {
              style: { color: '#654321' },
              inputmq1: { texte: leTexteGauche, correction: tabDef.lignes[i].images[k][2] }
            })
          mqList.push(zoneImageGauche.inputmqList[0])
          mqRestriction(zoneImageGauche.inputmqList[0], restrict, {
            commandes: tabDef.liste_boutons
          })
          const imagegaucheMQ = j3pElement('tab' + k + divId + 'image_gauche' + i + jEquivalent + 'inputmq1')
          // je stocke les coordonnées et xDeLImage yDeLImage dans un tableau pour pouvoir m’en servir, notamment lors du repositionnement des
          // zones de saisie dans l’écouteur onkeyup
          tabDef.lignes[i].images_coord_x[k][0] = xDeLImage
          tabDef.lignes[i].images_coord_y[k][0] = yDeLImageGauche
          // une feinte de siou, blur permet d’enlever le focus, comme ça on est obligé de cliquer sur la zone, ça fait apparaitre la palette.'
          $(imagegaucheMQ).blur()
          lid = 'tab' + k + divId + 'image_gauche' + i + jEquivalent + 'inputmq1'
          tabDef.lignes[i].zonesdesaisie.push(lid)
          // Modification du css pour ajouter un fond blanc à la zone de saisie :
          imagegaucheMQ.style.backgroundColor = '#FFFFFF'
          // Gestion de la palette :
          imagegaucheMQ.addEventListener('mouseup', affichePaletteMQ.bind(null, lid))
          imagegaucheMQ.addEventListener('keyup', positionneZoneVariations.bind(null, lid))
          imagegaucheMQ.addEventListener('focusin', positionneZoneVariations.bind(null, lid))
          // je vais également stocker la réponse à chaque frappe, dans tabDef.lignes[i].imagesreponses mais au même indice et à la première ligne i sur laquelle la charnière se trouve
          // comme les charnières ont été réordonnées, on utilise charnieres_memoire qui avait stocké le i de lignes[i] en mémoire ainsi que l’indice correspondant'
          // lorsque l’on n’utilise pas le clavier mais uniquement les boutons de la palette... (il faut aussi repositionner la zone et compélter le tableau des réponses
        } else {
          j3pAffiche(divId + 'image_gauche' + i + jEquivalent, divId + 'image_gauche_txt' + i + jEquivalent, '$' + imageYAvant + '$')
        }
        imageGauche.style.left = xDeLImage - decalage - imageGauche.getBoundingClientRect().width - 5 - 1 + 'px'
        imageGauche.style.top = (yDeLImageGauche - imageGauche.getBoundingClientRect().height / 2) + 'px'

        if (imageYApres === '?') {
          // une zone de saisie à droite :
          // cas particulier : si l’on clique sur une flèche et qu’une zone de saisie il faut pas effacer le contenu de la zone :
          // on teste donc si imagesreponses est pas complété (sinon on récupère son contenu)
          let leTexteDroite = ''
          if (isDebug) console.debug('tabDef.lignes[i].imagesreponses[' + k + '][1]=' + tabDef.lignes[i].imagesreponses[k][1])
          // A MODIFIER AJOUTER [1]
          if (tabDef.lignes[i].imagesreponses[k][1] !== undefined) {
            leTexteDroite = tabDef.lignes[i].imagesreponses[k][1]
          }
          const zoneImageDroite = j3pAffiche(divId + 'image_droite' + i + jEquivalent, 'tab' + k + divId + 'image_droite' + i + jEquivalent, '&1&',
            {
              style: { color: '#654321' },
              inputmq1: { texte: leTexteDroite, correction: tabDef.lignes[i].images[k][2] }
            })
          mqList.push(zoneImageDroite.inputmqList[0])
          mqRestriction(zoneImageDroite.inputmqList[0], restrict, {
            commandes: tabDef.liste_boutons
          })

          const imagedroiteMQ = j3pElement('tab' + k + divId + 'image_droite' + i + jEquivalent + 'inputmq1')
          // je stocke les coordonnées et xDeLImage yDeLImage dans un tableau pour pouvoir m’en servir, notamment lors du repositionnement des
          // zones de saisie dans l’écouteur onkeyup
          tabDef.lignes[i].images_coord_x[k][1] = xDeLImage
          tabDef.lignes[i].images_coord_y[k][1] = yDeLImage
          // une feinte de sioux, blur permet d’enlever le focus, comme ça on est obligé de cliquer sur la zone, ça fait apparaître la palette.
          $(imagedroiteMQ).blur()
          lid = 'tab' + k + divId + 'image_droite' + i + jEquivalent + 'inputmq1'
          tabDef.lignes[i].zonesdesaisie.push(lid)
          // Modification du css pour ajouter un fond blanc à la zone de saisie :
          imagedroiteMQ.style.backgroundColor = '#FFFFFF'
          // Gestion de la palette
          imagedroiteMQ.addEventListener('mouseup', affichePaletteMQ.bind(null, lid))
          // je vais également stocker la réponse à chaque frappe, dans tabDef.lignes[i].imagesreponses mais au même indice et à la première ligne i sur laquelle la charnière se trouve
          // comme les charnières ont été réordonnées, on utilise charnieres_memoire qui avait stocké le i de lignes[i] en mémoire ainsi que l’indice correspondant'
          imagedroiteMQ.addEventListener('keyup', positionneZoneVariations.bind(null, lid))
          // lorsque l’on n’utilise pas le clavier mais uniquement les boutons de la palette... (il faut aussi repositionner la zone et compléter le tableau des réponses
          imagedroiteMQ.addEventListener('focusin', positionneZoneVariations.bind(null, lid))
        } else {
          j3pAffiche(divId + 'image_droite' + i + jEquivalent, divId + 'image_droite_txt' + i + jEquivalent, '$' + imageYApres + '$')
        }
        imageDroite.style.left = xDeLImage - decalage + 1 + 'px'
        imageDroite.style.top = (yDeLImage - imageDroite.getBoundingClientRect().height / 2) + 'px'
      } else {
        // pas de valeur interdite
        // conversion en chaine latex...
        if (imageY === '-inf') {
          imageY = '-\\infty'
        }
        if (imageY === '+inf') {
          imageY = '+\\infty'
        }
        let zoneimageMQ
        const zoneImage = j3pAddElt(zoneSvgCorr, 'div', '', {
          id: divId + 'zone_image' + i + jEquivalent,
          style: {
            color: tabDef.mef.macouleur,
            position: 'absolute',
            top: '0',
            left: '0'
          }
        })
        if (imageY === '?') {
          if (isDebug) console.debug('UNE SEULE ZONE DE SAISIE A AFFICHER, on attend comme réponse : ' + tabDef.lignes[i].images[k][2])
          // la correction est dans tabDef.lignes[i].images[k][2], penser à corriger +-inf en code latex

          // cas particulier : si l’on clique sur une flèche et qu’une zone de saisie il faut pas effacer le contenu de la zone :
          // on teste donc si imagesreponses est pas complété (sinon on récupère son contenu)
          let leTexte = ''
          if (tabDef.lignes[i].imagesreponses[k]) {
            leTexte = tabDef.lignes[i].imagesreponses[k]
          }
          // avant de recréer un input, faudrait plutôt regarder s’il n’est pas déjà dans tabDef.validationZonesInputs
          // on laisse tomber pour le moment vu qu’on le rattrape plus loin
          const laZoneImage = j3pAffiche(zoneImage, 'tab' + k + divId + 'image_txt' + i + jEquivalent, '&1&',
            {
              style: { color: '#654321' },
              inputmq1: { texte: leTexte, correction: tabDef.lignes[i].images[k][2] }
            })
          mqList.push(laZoneImage.inputmqList[0])
          mqRestriction(laZoneImage.inputmqList[0], restrict, {
            commandes: tabDef.liste_boutons
          })
          zoneimageMQ = j3pElement('tab' + k + divId + 'image_txt' + i + jEquivalent + 'inputmq1')
          zoneimageMQ.style.backgroundColor = 'rgba(255,255,255,0.2)'
        } else {
          j3pAffiche(zoneImage, divId + 'image_txt' + i + jEquivalent, '$' + imageY + '$')
        }
        divList[i].push(zoneImage)

        if (isDebug) console.debug('imageY=' + imageY)
        setTimeout(() => {
          // j’attends que zoneImage soit construit pour calculer sa dimension
          let decalage = zoneImage.getBoundingClientRect().width / 2
          if (premiereCharniere) decalage = 0
          if (derniereCharniere) decalage = zoneImage.getBoundingClientRect().width + 6
          zoneImage.style.left = xDeLImage - decalage + 'px'
          zoneImage.style.top = (yDeLImage - zoneImage.getBoundingClientRect().height / 2) + 'px'
          // on créé un segment en pointillé (j’efface d’abord celui qui a été éventuellement construit)
          j3pDetruit(divId + 's2' + i + jEquivalent)
          j3pCreeSegment(svg, {
            id: divId + 's2' + i + jEquivalent,
            x1: xDeLImage,
            y1: hauteurTemp,
            x2: xDeLImage,
            y2: yDeLImage - zoneImage.getBoundingClientRect().height / 2,
            couleur: tabDef.mef.macouleur,
            epaisseur: 1
          })
        }, 0)
        divList[i].push(divId + 's2' + i + jEquivalent)

        // écouteur si c’est une zone de saisie :
        if (imageY === '?') {
          // je stocke les coordonnées et xDeLImage yDeLImage dans un tableau pour pouvoir m’en servir, notamment lors du repositionnement des
          // zones de saisie dans l’écouteur onkeyup
          tabDef.lignes[i].images_coord_x[k] = xDeLImage
          tabDef.lignes[i].images_coord_y[k] = yDeLImage
          // une feinte de balayeur (©Arnaud), blur permet d’enlever le focus, comme ça on est obligé de cliquer sur la zone et ça fait apparaitre la palette…
          $(zoneimageMQ).blur()
          lid = 'tab' + k + divId + 'image_txt' + i + jEquivalent + 'inputmq1'
          tabDef.lignes[i].zonesdesaisie.push(lid)
          // Modification du css pour ajouter un fond blanc à la zone de saisie :
          zoneimageMQ.style.backgroundColor = 'rgba(255,255,255,0.4)'
          // Gestion de la palette :
          zoneimageMQ.addEventListener('mouseup', affichePaletteMQ.bind(null, lid))
          zoneimageMQ.addEventListener('keyup', positionneZoneVariations.bind(null, lid))
          zoneimageMQ.addEventListener('focusin', positionneZoneVariations.bind(null, lid))
          // je vais également stocker la réponse à chaque frappe, dans tabDef.lignes[i].imagesreponses mais au même indice et à la première ligne i sur laquelle la charnière se trouve
          // comme les charnières ont été réordonnées, on utilise charnieres_memoire qui avait stocké le i de lignes[i] en mémoire ainsi que l’indice correspondant'
          // lorsque l’on n’utilise pas le clavier mais uniquement les boutons de la palette... (il faut aussi repositionner la zone et compélter le tableau des réponses
        }
      }

      // on trace les traits verticaux "précédents" (cad sur les lignes supérieures si ce n’est pas une racine
      for (let itemp = 0; itemp <= i - 1; itemp++) {
        // je teste si pas une racine
        if (!estDedans(charnieresLignes[itemp], charniereEnCours)) {
          // la charnière en cours n’est pas une charnière de la ligne
          let ydeb = tabDef.mef.h
          for (let ktemp = 0; ktemp <= itemp - 1; ktemp++) {
            ydeb += tabDef.lignes[ktemp].hauteur
          }
          const yfin = ydeb + tabDef.lignes[itemp].hauteur
          j3pCreeSegment(svg, {
            id: divId + 's300' + itemp,
            x1: xDeLImage,
            y1: ydeb,
            x2: xDeLImage,
            y2: yfin,
            couleur: tabDef.mef.macouleur,
            epaisseur: 1,
            pointilles: 2
          })
          divList[i].push(divId + 's300')
        }
      }
    }
    // s’il y du ValidationZones qui a la main sur cette zone, faut lui rendre !
    if (tabDef.validationZonesInputs?.length) {
      // y’a des ValidationZones, on regarde s’il faut en réaffecter
      for (const [i, input] of tabDef.validationZonesInputs.entries()) {
        if (input?.id) {
          const newInput = j3pElement(input.id, null)
          if (newInput && newInput !== input) {
            // avant de substituer, faut récupérer toutes les props ajoutées par ValidationZones
            for (const [p, v] of Object.entries(input)) {
              if (!hasProp(newInput, p)) newInput[p] = v
            }
            // faut réaffecter dans le tableau, pas la variable input
            tabDef.validationZonesInputs[i] = newInput
          }
        }
      }
    }
  } // creerFleches

  // listener du mouseOver sur les flèches (this est l’élément svg)
  function onMouseOver () {
    this.style.cursor = 'pointer'
  }

  // au clic (mouseup) sur une flèche (attention, this est null)
  // FIXME ça marche sur tablette ? Faudrait pas plutôt être sur l’événement click ?
  function reponseClic (i, j) {
    // console.log("i="+i+" et j="+j)
    const tabVariationsPossibles = ['const', 'decroit', 'croit']
    // on change la croissance de la flèche, on appelle à nouveau la fonction pour recréer le tabvar
    tabDef.perso.variationsModifiees = true
    switch (tabDef.lignes[i].variations[j]) {
      case 'croit':
        // elle va décroitre
        tabDef.lignes[i].variations[j] = tabVariationsPossibles[1]
        break

      case 'decroit':
        // elle sera constante
        tabDef.lignes[i].variations[j] = tabVariationsPossibles[0]
        break

      case 'const':
        // elle sera croissante
        tabDef.lignes[i].variations[j] = tabVariationsPossibles[2]
        break
    }
    // on redessine les flèches
    creerFleches(i)
  }

  function tabSignesCreerLigne (i) {
    tabDef.lignes[i] = {}
    tabDef.lignes[i].type = 'signes'
    // quelle hauteur ?
    tabDef.lignes[i].hauteur = 50
    tabDef.lignes[i].valeurs_charnieres = {}
    tabDef.lignes[i].valeurs_charnieres.valeurs_approchees = []
    // les signes
    tabDef.lignes[i].signes = []
    tabDef.lignes[i].imagesapprochees = []
    tabDef.lignes[i].imagesreponses = []
    // on peut ajouter des valeurs de x avec leurs images.
    tabDef.lignes[i].images = []
    // A FAIRE : METTRE UNE ZONE DE SAISIE MQ
    tabDef.lignes[i].expression = '&1&'
    // A FAIRE : DETECTER LES VALEURS CHARNIERES PRESENTES POUR LES REMETTRE et dpnc le nb d’intervalles par la suite' charnieresTheoriques
    tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques = charnieresTheoriques
    tabDef.lignes[i].valeurs_charnieres.valeurs_approchees = charnieresApprochees

    // je n’ai pas en stock, à voir si cela posera pb...
    //   tabDef.lignes[i].valeurs_charnieres.valeurs_affichees=tabDef.lignes[tabDef.lignes.length-1].valeurs_charnieres.valeurs_affichees;
    //  tabDef.lignes[i].valeurs_charnieres.valeurs_approchees=tabDef.lignes[tabDef.lignes.length-1].valeurs_charnieres.valeurs_approchees;
    // console.log("NB DE CHARNIERES:"+charnieresTheoriques.length);
    // console.log("PB :  tabDef.lignes["+i+"].valeurs_charnieres.valeurs_affichees="+ tabDef.lignes[i].valeurs_charnieres.valeurs_affichees);
    // console.log("PB :  tabDef.lignes["+i+"].valeurs_charnieres.valeurs_theoriques=charnieresTheoriques="+tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques)

    // objet_global.lignes[i].signes=["-","+"];
    tabDef.lignes[i].signes = []
    for (let j = 0; j < charnieresTheoriques.length - 1; j++) {
      tabDef.lignes[i].signes.push('?')
    }
    if (isDebug) console.debug('tabDef.lignes[i].signes.=' + tabDef.lignes[i].signes)
    // normalement pas besoin
    tabDef.lignes[i].signescorrection = ['']
    // zones de saisies pour les images :
    const nbCharnieres = charnieresTheoriques.length - 1
    // on n’en veut pas à gauche :
    tabDef.lignes[i].images.push([charnieresTheoriques[0], ''])
    // les autres on veut des zones de saisies :
    for (let j = 1; j < nbCharnieres; j++) {
      tabDef.lignes[i].images.push([charnieresTheoriques[j], '?'])
    }
    // on n’en veut pas à gauche :
    tabDef.lignes[i].images.push([charnieresTheoriques[nbCharnieres], ''])
    // tabDef.lignes[i].imagesapprochees=[];
    // on relance la création du tableau :
    if (isDebug) console.debug('mes lignes=', tabDef.lignes)
    setTimeout(() => {
      creationTableau1bis()
    }, 0)
  }

  function tabSignesNettoieLigne () {
    if (isDebug) console.debug('lignes', tabDef.lignes)
    const nbLignesTemp = tabDef.lignes.length
    const objTemp = tabDef.lignes[nbLignesTemp - 1]
    tabDef.lignes.pop()
    tabDef.lignes.pop()
    tabDef.lignes.push(objTemp)
    if (isDebug) console.debug(tabDef.lignes)
    setTimeout(() => {
      creationTableau1bis()
    }, 0)
  }

  function tabSignesAjouteLigne () {
    const nbLignesTemp = tabDef.lignes.length
    // je limite à 5 lignes, mais ça se dicute...(ça se paramètre ?)
    if (nbLignesTemp < 5) {
      nbLignesAjoutees++
      tabDef.lignes[nbLignesTemp] = {}
      tabDef.lignes[nbLignesTemp] = tabDef.lignes[nbLignesTemp - 1]
      // on recopie la sauvegarde des charnières par ligne :
      charnieresLignes[nbLignesTemp] = charnieresLignes[nbLignesTemp - 1]
      // on créé l’avant dernière ligne'
      tabSignesCreerLigne(nbLignesTemp - 1)
    }
  }

  function tabSignesSupprimeLigne () {
    if (nbLignesAjoutees > 0) {
      nbLignesAjoutees--
      tabSignesNettoieLigne()
    }
  }

  function tabSignesAjouteIntervalle () {
    // je récupère le nb d’intervalles voulus :
    const nbIntervalles = j3pValeurde('affiche_tabsignes_varinput1')
    // je change les propriétés de mon objet :

    // var nbIntervalles=charnieresTheoriques.length-1;
    // je limite à 6 intervalles, mais ça se dicute...(ça se paramètre ?)
    if (nbIntervalles < 6) {
      if (isDebug) console.debug('tabDef', tabDef)
      // A FAIRE : ADAPTER POUR QUE SI RANDOM CE SOIT POSITIONNE DE MANIERE ORDONNEE (tjs centré on ne tient pas comte de l’affichage ou non des valeurs extremes)
      //             tabDef.lignes[i].valeurs_charnieres.valeurs_theoriques.push("random");
      //             tabDef.lignes[i].valeurs_charnieres.valeurs_affichees.push("?");
      //             tabDef.lignes[i].valeurs_charnieres.valeurs_approchees.push("random");
      for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
        // on ajoute une valeur charnière en avant dernier position
        if (tabDef.lignes[i].type === 'signes') {
          tabDef.lignes[i].signes = []
          for (let j = 0; j < nbIntervalles; j++) {
            tabDef.lignes[i].signes.push('?')
          }
          console.debug(tabDef)
          // tabDef.lignes[i].signes.push("?");
          // images ? imagesapprochees ?
        }
        if (tabDef.lignes[i].type === 'variations') {
          if (isDebug) console.debug('tabDef.lignes[' + i + '].variations=' + tabDef.lignes[i].variations)
          tabDef.lignes[i].variations = []
          tabDef.lignes[i].cliquable = []
          for (let j = 0; j < nbIntervalles; j++) {
            tabDef.lignes[i].cliquable.push(true)
            tabDef.lignes[i].variations.push('const')
          }
          // intervalles ? images ? imagesapprochees ?
        }
        // }
      }
      // on relance la création du tableau :
      setTimeout(() => {
        creationTableau1ter()
      }, 0)
    }
  }

  // fonction qui, une fois l’affichage des "Signes de..." et "Variations de..." terminée gère la suite de l’affichage :
  // le x en haut à gauche, les valeurs de la première ligne
  //  function creation_tableau2(hauteurTemp){
  function creerSignes (i) {
    divList[i] = []

    let nbChangement = 0
    let posTemp = largeurSigneDe
    let nbSignesTemp = 1
    let nbSignesOld = 0
    hauteurTemp = tabDef.mef.h
    for (let indexI = 0; indexI <= i - 1; indexI++) {
      hauteurTemp += tabDef.lignes[indexI].hauteur
    }
    // il faut définir le tableau des signes, par exemple si on demande +,-,+ on peut avoir à afficher +,+,-,-,+ si l’on a rajouté deux valeurs intermédiaires...
    // var tab_signes=definir_signes(i);
    // objet_global.lignes[0].images=[["1","0"],["3","0"],["0","3"],["2","-1"]]
    // PLACEMENT DES IMAGES
    const styleImage = {}
    styleImage.style = {}
    styleImage.style.position = 'absolute'
    styleImage.style.top = '0px'
    styleImage.style.left = '0px'
    styleImage.style.color = tabDef.mef.macouleur
    for (let j = 0; j <= charnieresTheoriques.length - 1; j++) {
      if (isDebug) console.debug('--------------charnieres_theoriques[j]=' + charnieresTheoriques[j])
      for (let k = 0; k <= tabDef.lignes[i].images.length - 1; k++) {
        if (charnieresTheoriques[j] === tabDef.lignes[i].images[k][0]) {
          let valeuDeX = tabDef.lignes[i].images[k][0]
          let valeuDeY = tabDef.lignes[i].images[k][1]
          if (modeCorrection) {
            // console.log("tabDef.lignes["+i+"].imagesreponses="+j3p.stockage[40].lignes[i].imagesreponses)
            // si l’on demandait une zone de saisie, on remplace par la réponse élève
            if (valeuDeX.includes('?')) valeuDeX = tabDef.lignes[i].valeurs_charnieres.reponses[k]
            if (valeuDeY.includes('?')) valeuDeY = tabDef.lignes[i].imagesreponses[k]
          }
          if (isDebug) console.debug('valeuDeX=' + valeuDeX + ' et valeuDeY=' + valeuDeY)
          const pos2 = (largeurSigneDe + j * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1))
          // le I c’est pour le cas où l’élève a renseigné I comme interdite
          if (valeuDeY.includes('|') || valeuDeY === 'I') {
            let pos3
            if (j === 0) {
              // je suis à gauche du tableau
              pos3 = pos2
            } else {
              if (j === charnieresTheoriques.length - 1) {
                // je suis à droite du tableau
                pos3 = pos2 - 5
              } else {
                // cas général
                pos3 = pos2 - 2
              }
            }
            // c’est une valeur interdite ! on trace simplement une double barre
            j3pCreeSegment(svg, {
              id: divId + 's2' + j,
              x1: pos3,
              y1: hauteurTemp,
              x2: pos3,
              y2: hauteurTemp + tabDef.lignes[i].hauteur,
              couleur: tabDef.mef.macouleur,
              epaisseur: 1
            })
            j3pCreeSegment(svg, {
              id: divId + 's3' + j,
              x1: pos3 + 5,
              y1: hauteurTemp,
              x2: pos3 + 5,
              y2: hauteurTemp + tabDef.lignes[i].hauteur,
              couleur: tabDef.mef.macouleur,
              epaisseur: 1
            })
            divList[i].push(divId + 's2' + j)
            divList[i].push(divId + 's3' + j)
          } else {
            let imageFct
            if (valeuDeY === '?') {
              // la position en left de la racine, à décaler pour la dernière...
              imageFct = j3pAjouteDiv(zoneSvgCorr, divId + i + 'image_fct' + j, '', styleImage)
              const zoneValY = j3pAffiche(imageFct, 'tab' + k + divId + i + 'nomimage_fct' + j, '&1&',
                {
                  style: { color: '#654321' },
                  inputmq1: { texte: '', correction: tabDef.lignes[i].images[k][2] }
                })
              mqList.push(zoneValY.inputmqList[0])
              mqRestriction(zoneValY.inputmqList[0], restrict, {
                commandes: tabDef.liste_boutons
              })

              let pos3
              if (j === 0) {
                // je suis à gauche du tableau
                pos3 = pos2
              } else {
                if (j === charnieresTheoriques.length - 1) {
                  // je suis à droite du tableau
                  pos3 = pos2 - imageFct.getBoundingClientRect().width
                } else {
                  // cas général
                  pos3 = pos2 - imageFct.getBoundingClientRect().width / 2
                }
              }
              imageFct.style.left = pos3 + 'px'
              // on garde la mémoire du pos2 pour le repositionnement dans l’écouteur : (tester le j...)
              positionValeursSignes[j] = pos2
              imageFct.style.top = hauteurTemp + tabDef.lignes[i].hauteur / 2 - imageFct.getBoundingClientRect().height / 2 + 'px'
              // ecouteur pour positionner correctement le tout :
              const imageFctMQ = j3pElement('tab' + k + divId + i + 'nomimage_fct' + j + 'inputmq1')
              // une feinte de siou, blur permet d’enlever le focus, comme ça on est obligé de cliquer sur la zone, ça fait apparaitre la palette.
              $(imageFctMQ).blur()
              const lid = 'tab' + k + divId + i + 'nomimage_fct' + j + 'inputmq1'
              tabDef.lignes[i].zonesdesaisie.push(lid)
              // Modification du css pour ajouter un fond blanc à la zone de saisie :
              imageFctMQ.style.backgroundColor = '#FFFFFF'
              // Gestion de la palette :
              imageFctMQ.addEventListener('mouseup', affichePaletteMQ.bind(null, lid))
              imageFctMQ.addEventListener('keyup', positionneZoneSignes.bind(null, lid, hauteurTemp))
              // lorsque l’on n’utilise pas le clavier mais uniquement les boutons de la palette... (il faut aussi repositionner la zone et compléter le tableau des réponses
              imageFctMQ.addEventListener('focusin', positionneZoneSignes.bind(null, lid, hauteurTemp))
              // on ajoute un trait vertical
              j3pCreeSegment(svg, {
                id: divId + 's2' + j,
                x1: pos2,
                y1: hauteurTemp,
                x2: pos2,
                y2: hauteurTemp + tabDef.lignes[i].hauteur,
                couleur: tabDef.mef.macouleur,
                epaisseur: 1
              })
            } else {
              // la position en left de la racine à décaler pour la dernière...
              // var imageFct = j3pAjouteDiv(zoneSvgCorr, divId + i + 'image_fct' + j, '', { style: 'position:absolute;top:0px;left:0px;color:' + tabDef.mef.macouleur + ';' })
              imageFct = j3pAjouteDiv(zoneSvgCorr, divId + i + 'image_fct' + j, '', styleImage)
              j3pAffiche(imageFct, divId + i + 'l_image_fct' + j, '$' + valeuDeY + '$')
              let pos3
              if (j === 0) {
                // je suis à gauche du tableau
                pos3 = pos2
              } else {
                if (j === charnieresTheoriques.length - 1) {
                  // je suis à droite du tableau
                  pos3 = pos2 - imageFct.getBoundingClientRect().width
                } else {
                  // cas général
                  pos3 = pos2 - imageFct.getBoundingClientRect().width / 2
                }
              }
              imageFct.style.left = pos3 + 'px'
              imageFct.style.top = hauteurTemp + tabDef.lignes[i].hauteur / 2 - imageFct.getBoundingClientRect().height / 2 + 'px'
              // on ajoute un trait vertical
              j3pCreeSegment(svg, {
                id: divId + 's2' + j,
                x1: pos2,
                y1: hauteurTemp,
                x2: pos2,
                y2: hauteurTemp + tabDef.lignes[i].hauteur,
                couleur: tabDef.mef.macouleur,
                epaisseur: 1
              })
              divList[i].push(divId + 's2' + j)
            }
            divList[i].push(divId + i + 'image_fct' + j)
          }
          // on teste parmi les lignes précédentes si la racine en cours n’est pas une racine, alors ontrace un segment vertical
          for (let itemp = 0; itemp <= i - 1; itemp++) {
            // je teste si pas une racine
            if (!estDedans(charnieresLignes[itemp], charnieresTheoriques[j])) {
              // la charnière en cours n’est pas une charnière de la ligne
              let ydeb = tabDef.mef.h
              for (let ktemp = 0; ktemp <= itemp - 1; ktemp++) {
                ydeb += tabDef.lignes[ktemp].hauteur
              }
              const yfin = ydeb + tabDef.lignes[itemp].hauteur
              j3pCreeSegment(svg, {
                id: divId + 's200' + itemp,
                x1: pos2,
                y1: ydeb,
                x2: pos2,
                y2: yfin,
                couleur: tabDef.mef.macouleur,
                epaisseur: 1
              })
              divList[i].push(divId + 's200' + itemp)
            }
          }
        }
      }
    }
    if (!modeCorrection) {
      // pour stocker éventuellement les signes réponses
      tabDef.lignes[i].signesreponses = []
    }
    // PLACEMENT DES SIGNES
    if (isDebug) console.debug('HEREEE')
    if (isDebug) console.debug('charnieresApprochees.length=' + charnieresApprochees.length + ' et tabDef.lignes[i].valeurs_charnieres.valeurs_approchees.length-1=' + (tabDef.lignes[i].valeurs_charnieres.valeurs_approchees.length - 1))
    if (isDebug) console.debug('charnieresApprochees=' + charnieresApprochees)
    if (isDebug) console.debug('tabDef.lignes[' + i + '].valeurs_charnieres.valeurs_approchees=' + tabDef.lignes[i].valeurs_charnieres.valeurs_approchees)

    let estCharniere, lesigne, id, divSigneFct
    for (let k = 0; k <= charnieresApprochees.length - 1; k++) {
      // si c’est une charnière de la ligne on y regarde de plus près...
      // if (estDedans(charnieres_lignes_approchees[i],charnieres_approchees[k])
      estCharniere = false

      for (let j = 0; j <= tabDef.lignes[i].valeurs_charnieres.valeurs_approchees.length - 1; j++) {
        // console.log("k="+k+" et charnieres_approchees[k]="+charnieres_approchees[k]+" et tabDef.lignes[i].valeurs_charnieres.valeurs_approchees[j="+tabDef.lignes[i].valeurs_charnieres.valeurs_approchees[j])
        // on teste aussi si dans   (=["-inf",1,3,"+inf"]), on récupère la position pour placer le + ou -
        if (charnieresApprochees[k] === tabDef.lignes[i].valeurs_charnieres.valeurs_approchees[j]) {
          estCharniere = true
          // pas la première !
          if (nbChangement) {
            // je place le signe :
            lesigne = tabDef.lignes[i].signes[nbChangement - 1]
            // en cas de correction, j’affiche la réponse élève'
            if (modeCorrection && lesigne === '?') {
              lesigne = tabDef.lignes[i].signesreponses[nbChangement - 1]
              if (isDebug) console.debug('tabDef.lignes[' + i + '].signesreponses[' + (nbChangement - 1) + ']=' + tabDef.lignes[i].signesreponses[nbChangement - 1])
            }
            //  console.log("lesigne="+lesigne+" nb de fois à placer="+nbSignesTemp);
            // nbSignesTemp est le nb de fois où je dois placer le signe...
            if (isDebug) console.debug('nbSignesTemp=' + nbSignesTemp + ' et nbSignesOld=' + nbSignesOld)
            for (let index = 1; index <= nbSignesTemp; index++) {
              // console.log("&&&&&&&j="+j+" et index="+index)outils/j3poutils.js
              id = divId + i + 'signe_fct' + j + index
              // divSigneFct = j3pAjouteDiv(zoneSvgCorr, id, '', { style: 'position:absolute;top:0px;left:0px;color:' + tabDef.mef.macouleur + ';' })
              divSigneFct = j3pAjouteDiv(zoneSvgCorr, id, '', styleImage)
              divList[i].push(id)
              if (lesigne === '?') {
                const signeCorrige = tabDef.lignes[i].signescorrection[nbChangement - 1]
                // liste déroulante
                // var choix1 = j3pAjouteDiv(zoneSvgCorr, divId+"choix1","",
                //  {"style":"color:"+macouleur+";position:absolute;top:0px;left:0px;font-size:"+taille_font+"px;"});
                j3pAffiche(divSigneFct, divId + i + 'les_listes' + j + (index + nbSignesOld), '#1#', {
                  style: {
                    fontSize: '12px',
                    color: '#654321'
                  },
                  liste1: {
                    texte: [' ', '+', '-'],
                    correction: signeCorrige
                  }
                })
                // code reponse :  var reponse_index1 = j3pElement("tableau1les_listesliste1").selectedIndex;
                // ecouteur sur la liste pour renseigner la réponse :
                const signeFctListe = j3pElement(divId + i + 'les_listes' + j + (index + nbSignesOld) + 'liste1')
                tabDef.lignes[i].liste_deroulante.push(signeFctListe)
                signeFctListe.onchange = function () {
                  // on choisit + ou - :
                  const posi = this.id.indexOf('les_listes')
                  const posj = this.id.indexOf('liste1')
                  const moni = this.id.charAt(posi - 1)
                  const monj = this.id.charAt(posj - 1)
                  // console.log("MONI="+moni+" et MONJ="+monj)
                  tabDef.lignes[moni].signesreponses[monj - 1] = j3pElement(this.id).options[j3pElement(this.id).selectedIndex].value
                  // console.log(tabDef.lignes[moni].signesreponses)
                }
              } else {
                // var divSigneFct = j3pAjouteDiv(zoneSvgCorr, divId+i+"signe_fct"+j+index,"",{"style":"position:absolute;top:0px;left:0px;color:"+tabDef.mef.macouleur+";font-size:"+taille_font+"px;"});
                if (lesigne) {
                  j3pAffiche(divSigneFct, divId + i + 'le_signe_fct' + j + index, '$' + lesigne + '$')
                }
              }
              // scorie du code de Rémi, a priori inutile var signe_fct0=j3pElement(divId+i+"le_signe_fct"+j+"0");
              //  console.log("=====(nbRacines-1)="+(nbRacines-1)+" et nbSignesTemp="+nbSignesTemp+" et index="+index+" et j="+j)
              const pos1 = posTemp + ((tabDef.mef.L - largeurSigneDe) / (nbRacines - 1))
              const posSigne = (posTemp + pos1) / 2
              divSigneFct.style.left = posSigne + 'px'
              if (lesigne === '?') {
                // on décale si liste déroulante
                const largeurTemp = divSigneFct.getBoundingClientRect().width
                divSigneFct.style.left = posSigne + -largeurTemp / 2 + 'px'
              }
              posTemp = pos1
              divSigneFct.style.top = hauteurTemp + tabDef.lignes[i].hauteur / 2 - divSigneFct.getBoundingClientRect().height / 2 + 'px'
            }
            // je garde la mémoire de l’ancienne valeur :
            nbSignesOld += nbSignesTemp
            // je reinitialise le nb de signes :
            nbSignesTemp = 1

            // on ajoute un trait en pointillés si c’est pas une charnière et si c’est pas la premiere/dernière ?
          }
          nbChangement++
        }
      }
      // c’est donc une image ajoutée, donc on augmente d’un le nb de signes à placer
      if (!estCharniere) {
        // pas satisfaisant car il faut changer la façon de placer les signes au dessus...
        // if (estDedans(tabDef.lignes[i].imagesapprochees,charnieres_approchees[k])){
        nbSignesTemp++
        // }
        // console.log(charnieres_approchees[k]+" est juste une image et donc nbSignesTemp vaut"+nbSignesTemp)
      }
    }
  }

  // ETAPE 1 :on définit la largeur de la barre verticale et on affiche 'x' dans la première case
  function creationTableau2Etape1 (hauteurTemp) {
    largeurSigneDe = 0
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      const elt = j3pElement('Mep' + divId + 'txt2' + i)
      if (elt) {
        const largeurTemp = elt.getBoundingClientRect().width + 4
        if (largeurTemp > largeurSigneDe) {
          largeurSigneDe = largeurTemp
        }
      }
    }

    // placement du "x" de la première ligne
    const divLeX = j3pAjouteDiv(zoneSvgCorr, divId + 'le_x', '', {
      style: {
        color: tabDef.mef.macouleur,
        position: 'absolute',
        top: '0',
        left: '0'
      }
    })
    j3pAffiche(divLeX, 'Mep' + divId + 'txt1', '$x$')

    const monobj2 = j3pElement('Mep' + divId + 'txt1')
    // var pos_txt2 = tabDef.tab_signes.h/4-monobj2.getBoundingClientRect().height/2;

    divLeX.style.top = ((tabDef.mef.h) / 2 - divLeX.getBoundingClientRect().height / 2) + 'px'
    // je le garde en mémoire pour le point mobile (mais ça aurait pu servir à autre chose...)
    const posTxt3 = largeurSigneDe / 2 - monobj2.getBoundingClientRect().width / 2
    divLeX.style.left = posTxt3 + 'px'
    // j’ajoute la barre verticale :
    if (isDebug) console.debug('!!!!!!!!!!!!!!largeurSigneDe=' + largeurSigneDe + ' et hauteurTemp=' + hauteurTemp + ' et divId=' + divId)
    j3pCreeSegment(svg, {
      id: divId + 's0',
      x1: largeurSigneDe,
      y1: 0,
      x2: largeurSigneDe,
      y2: hauteurTemp,
      couleur: tabDef.mef.macouleur,
      epaisseur: 1
    })
    // fin ETAPE 1
    // ASYNC 3
    setTimeout(() => {
      creationTableau2Etape2(hauteurTemp)
    }, 0)
  }

  // ETAPE 2  : Affichage des valeurs de la première ligne
  function creationTableau2Etape2 (hauteurTemp) {
    // je récupère le nb de racines de l’équation en cours
    nbRacines = charnieres.length
    if (isDebug) console.debug('nbRacines=' + nbRacines)
    const mesZeroTxt = []
    const mesZonesNulles = []
    for (let i = 1; i <= nbRacines; i++) {
      mesZonesNulles[i] = j3pAddElt(zoneSvgCorr, 'div', '', { id: divId + 'zone_nulle' + i })
      j3pStyle(mesZonesNulles[i], {
        color: tabDef.mef.macouleur,
        position: 'absolute',
        top: '0',
        left: '0'
      })
      let monZeroTxt = ''
      if (charnieres[i - 1] !== '?') {
        // if (charnieres_affichage[i-1]){
        // attention le_zero doit être du type string (un entier ou un décimal ou un nb fractionnaire de la forme a/b
        monZeroTxt = (Number.isNaN(Number(charnieres[i - 1]))) ? charnieres[i - 1] : j3pVirgule(charnieres[i - 1])
        // conversion en chaine latex...
        if (monZeroTxt === '-inf') monZeroTxt = '-\\infty'
        if (monZeroTxt === '+inf') monZeroTxt = '+\\infty'
        mesZeroTxt.push(monZeroTxt)
        j3pAffiche(mesZonesNulles[i], divId + 'le_zero_txt' + i, '$' + monZeroTxt + '$')

        // console.log("tabDef.tab_signes.h/(nb_lignes_signes+1="+tabDef.tab_signes.h/(nb_lignes_signes+1))
      } else {
        mesZeroTxt.push('')
        // dans le cas où la valeur en laquelle s’annule la fonction est à donner
        // la correction est dans charnieres_theoriques, penser à corriger +-inf en code latex
        if (isDebug) console.debug(' on a une zone de saisie :' + charnieres[i - 1] + ' et la correction est :' + charnieresTheoriques[i - 1])
        const zoneValZoneNulle = j3pAffiche(mesZonesNulles[i], divId + 'nomzone_nulle' + i, '&1&',
          {
            style: { color: '#654321' },
            inputmq1: { texte: '', correction: charnieresTheoriques[i - 1] }
          })
        mqList.push(zoneValZoneNulle.inputmqList[0])
        mqRestriction(zoneValZoneNulle.inputmqList[0], restrict, {
          commandes: tabDef.liste_boutons
        })

        const zonenulleMQ = j3pElement(divId + 'nomzone_nulle' + i + 'inputmq1')

        $(zonenulleMQ).blur()
        // je vais également stocker la réponse à chaque frappe, dans tabDef.lignes[i].valeurs_charnieres.reponses (ou tabDef.lignes[i].imagesreponses_x) mais au même indice et à la première ligne i sur laquelle la charnière se trouve
        // comme les charnières ont été réordonnées, on utilise charnieres_memoire qui avait stocké le i de lignes[i] en mémoire ainsi que l’indice correspondant'
        const eltBulle = divId + 'nomzone_nulle' + i + 'inputmq1'
        tabDef.zonesdesaisie_charnieres.push(divId + 'nomzone_nulle' + i + 'inputmq1')
        // Modification du css pour ajouter un fond blanc à la zone de saisie :
        zonenulleMQ.style.backgroundColor = '#FFFFFF'
        // Gestion de la palette :
        zonenulleMQ.addEventListener('mouseup', affichePaletteMQ.bind(null, eltBulle))
        zonenulleMQ.addEventListener('keyup', positionneZoneCharnieres.bind(null, eltBulle))
        zonenulleMQ.addEventListener('focusin', positionneZoneCharnieres.bind(null, eltBulle))
        // lorsque l’on n’utilise pas le clavier mais uniquement les boutons de la palette... (il faut aussi repositionner la zone et compléter le tableau des réponses
      }
    }
    // pour garder la mémoire du div pour suppression ultérieure si besoin
    // ASYNC 4
    setTimeout(() => {
      for (let i = 1; i <= nbRacines; i++) {
        const { width, height } = mesZonesNulles[i].getBoundingClientRect()
        mesZonesNulles[i].style.left = (largeurSigneDe + (i - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1)) - width / 2 + 'px'
        if (i === nbRacines) {
          // pas beau... pour éviter que la dernière "racine" sorte du cadre
          mesZonesNulles[i].style.left = (largeurSigneDe + (i - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1) - width - 2) + 'px'
          if (mesZeroTxt[i] === '+\\infty') mesZonesNulles[i].style.left = (tabDef.mef.L - 47) + 'px'
        }
        if (i === 1) {
          // pas beau... pour éviter que la dernière "racine" sorte du cadre
          mesZonesNulles[i].style.left = (largeurSigneDe + (i - 1) * (tabDef.mef.L - largeurSigneDe) / (nbRacines - 1)) + 'px'
        }
        mesZonesNulles[i].style.top = ((tabDef.mef.h) / 2 - (height) / 2) + 'px'
      }
      // fin ETAPE 2
      // ASYNC 5
      setTimeout(() => {
        creationTableau2Etape3(hauteurTemp)
      }, 0)
    }, 0)
  }

  // ETAPE 3 : on complète les lignes signes ou variations
  function creationTableau2Etape3 (hauteurTemp) {
    for (let i = 0; i <= tabDef.lignes.length - 1; i++) {
      switch (tabDef.lignes[i].type) {
        case 'signes':
          creerSignes(i)
          break

        case 'variations':
          // la fonction creer_fleches est à l’exterieure de cette fonction car peut être appelée également au clic sur la flèche
          creerFleches(i)
          break
      }
    }
    if (tabDef.ajout_lignes_possible) {
      // on part du principe que l’on ajoutera que des lignes de signes (pas de variations)
      // Ajout d’un bouton en bas à droite : (le -40 est empirique...)
      j3pDiv(zoneSvgCorr, { id: 'conteneurbouton_ligne', contenu: '', coord: [tabDef.mef.L - 40, hauteurTemp + 5] })
      j3pAjouteBouton('conteneurbouton_ligne', 'bouton1', 'MepBoutons2', '+', tabSignesAjouteLigne)
      j3pAjouteBouton('conteneurbouton_ligne', 'bouton2', 'MepBoutons2', '-', tabSignesSupprimeLigne)
    }
    if (tabDef.ajout_intervalles_possible) {
      if (isDebug) console.debug('!!!!!!!!!!!!On peut ajouter des intervalles!!!!!!!!!!!!!!')
      const divTxtIntervealle = j3pDiv(zoneSvgCorr, { contenu: '', coord: [0, hauteurTemp + 5] })
      j3pAffiche(divTxtIntervealle, '', 'Nombre d’intervalles souhaités : @1@', {
        input1: {}
      })
      const divBtnIntervalle = j3pDiv(zoneSvgCorr, { contenu: '', coord: [0, hauteurTemp + 35] })
      j3pAjouteBouton(divBtnIntervalle, 'bouton3', 'MepBoutons2', 'Valider', tabSignesAjouteIntervalle)
    }
    if (typeof finishCallback === 'function') finishCallback()
  } // creationTableau2Etape3

  // préparation du contexte

  // divparent doit exister
  const parent = j3pEnsureHtmlElement(divparent)
  // divId facultatif (mais il en faut un, il sert de préfixe un peu partout
  if (!divId || typeof divId !== 'string') divId = 'tabvar'
  divId = j3pGetNewId(divId)
  // le div en question, conteneur du svg
  const zoneSvgCorr = j3pAddElt(parent, 'div', '', {
    id: divId,
    style: {
      color: tabDef.mef.macouleur,
      position: 'relative'
    }
  })

  // isDebug facultatif
  if (typeof isDebug === 'function') {
    finishCallback = isDebug
    isDebug = false
  }

  // des variables globales (internes à cette fct, destinées à devenir des propriétés d'une classe)
  let nbLignesAjoutees = 0
  const positionValeursSignes = []
  const restrict = /[\d.,eln/+-]/ // passé à mqRestriction
  const divList = []
  /** Contient les inputMq créés par cette fct d’après tabDef */
  const mqList = []
  const charnieres = []
  const charnieresApprochees = []
  // je garde en mémoire les valeurs théoriques
  const charnieresTheoriques = []

  // je garde aussi la mémoire des charnières theoriques ligne par ligne :
  const charnieresLignes = []
  const charnieresLignesApprochees = []
  // je garde aussi la mémoire des indices et des lignes afin de compléter tabDef.lignes[i].valeurs_charnieres.reponses (ou tabDef.lignes[i].imagesreponses_x) si zone de saisie il y a
  const charnieresMemoire = []

  let hauteurTemp = 0
  let largeurSigneDe = 0
  let nbRacines = 0

  // il faut ajouter certaines propriétés locales dans tabdef.perso car certaines sections les utilisent
  // variationsModifiees utilisé par tabVarLecture pour savoir si l'élève a répondu
  tabDef.perso = { mqList, variationsModifiees: false }

  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')

  // maintenant que les fonctions intermédiaires sont définies, on lance la première qui s’occupe de lancer les autres en cascade
  creationTableau1()
} // tableauSignesVariations