legacy/outils/fonctions/tableauSignes.js

import $ from 'jquery'
import { j3pAddElt, j3pChaine, j3pVirgule, j3pEmpty, j3pFocus, j3pPaletteMathquill, j3pMin } from 'src/legacy/core/functions'
import { fctsEtudeEcrireNbLatex, fctsEtudeEstDansDomaine, fctsEtudeOrdonneTabSansDoublon } from 'src/legacy/outils/fonctions/etude'
import { j3pAffiche, mqRestriction } from 'src/lib/mathquill/functions'
import { j3pCreeRectangle, j3pCreeSegment } from 'src/legacy/core/functionsSvg'
import { j3pCalculValeur } from 'src/legacy/core/functionsTarbre'

/**
 * Construit le tableau de signes utilisé dans les sections signe_courbe1 et signe_courbe2
 * @param {HTMLElement} obj conteneur du tableau
 * @param {Object} objParam
 * @param {object} [objParam.tab_dimension] tab_dimension[0] est la largeur, tab_dimension[1] la hauteur
 *    tab_dimension[2] est le nombre de valeurs à ajouter sur l’axe des abscisses (en plus des bornes)
 *    tab_dimension[3] est un tableau de deux éléments : les bornes les plus extrêmes du domaine de def de la fonction (écrire infini pour \\infty)'
 *    tab_dimension[4] est un tableau optionnel. S’il n’est pas undefined, c’est le domaine réel de définition sous la forme par exemple ["-infini",a,"]","[",a,"+infini","]","["] pour une valeur interdite
 *    tab_dimension[5] est un argument optionnel. S’il vaut true, alors on donne la possibilité dans les menus déroulants de mettre une double barre (pour la présence éventuelle de valeurs interdites)
 * @param {object} [objParam.zero_tab] tableau de booléens : le 1er vaut true si la 1ère ligne est complétée, le 2ème, vaut true si la 2ème ligne est complétée, le 3ème (optionnel) vaut false si on veut des listes déroulantes
 * @param {boolean} [objParam.afficheBornes] qui vaut true si on affiche les bornes au lieu d’y mettre des zones de saisie (utile lorsque zero_tab[0] vaut false)
 * @param {object} [objParam.val_zero_tab] tableau de valeurs en lesquelles s’annule la fonction
 * @param {object} [objParam.signe_tab_corr] tableau contenant les signes de la fonction
 * @param {object} [objParam.obj_style_couleur] peut contenir 4 éléments (les 2 derniers étant obligatoires) : un style de texte, couleur si on ne définit par le style, texte qui sera "signe de ", nom_f
 * @param {object} [objParam.palette] argument optionnel qui est un tableau contenant les outils de la palette qu’on souhaite donner à l’élève
 * @return {Object} objet {zone, signe, liste, palette} pour récupérer les zones de saisie : zone pour les inputs de la 1ère ligne, signe pour les inputs destinés aux signes et liste pour les listes déroulante (zéro ou barre) et le conteneur de la palette
 */
export function tableauSignesFct (obj, objParam) {
  function ecoute (zone, palette) {
    if (zone.className.includes('mq-editable-field')) {
      j3pEmpty(laPaletteDiv)
      j3pPaletteMathquill(laPaletteDiv, zone, { liste: palette })
    }
  }

  // on crée un tableau de signes dans obj
  /* objParam contient différentes propriétés destinées à construire ce tableau :
      tab_dimension,zero_tab,val_zero_tab,signe_tab_corr,obj_style_couleur,palette */
  // ici il ne s’agit que du tableau du signe d’une fonction, pas d’un produit ou quotient
  // gestions des choses à afficher (valeurs ou zones de saisie) :
  if (typeof (objParam.zero_tab) !== 'object') return console.error('zero_tab doit être un array de booléen')
  // Double négation pour convertir en booléen si ce ne l’est pas
  const afficheLigneX = !!(objParam.zero_tab[0])
  const afficheSignes = !!(objParam.zero_tab[1])
  const affiche0Ligne2 = !!(objParam.zero_tab[2])
  const laPalette = (objParam.palette === undefined) ? [] : objParam.palette
  const nomdiv = obj
  const macouleur = (objParam.obj_style_couleur.couleur === undefined) ? objParam.obj_style_couleur.style.color : objParam.obj_style_couleur.couleur
  // macouleur = "#000000",
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  const L = objParam.tab_dimension[0]
  const h = objParam.tab_dimension[1]
  svg.setAttribute('width', L + 1)
  svg.setAttribute('height', h + 1)
  nomdiv.appendChild(svg)
  j3pCreeRectangle(svg, { x: 1, y: 1, width: L - 1, height: h - 1, couleur: macouleur, epaisseur: 1 })
  j3pCreeSegment(svg, { x1: 1, y1: 0, x2: 0, y2: h, couleur: macouleur, epaisseur: 1 })
  j3pCreeSegment(svg, { x1: 1, y1: h / 2, x2: L, y2: h / 2, couleur: macouleur, epaisseur: 1 })
  // placement de la ligne "signe de f"
  const styleText = Object.assign({}, objParam.obj_style_couleur.style)
  styleText.position = 'absolute'
  const signeDe = j3pAddElt(nomdiv, 'div', '', { style: styleText })
  // dans la section, il faut que le nom de la fonction apparaisse sous la forme £f
  j3pAffiche(signeDe, '', objParam.obj_style_couleur.texte, { f: objParam.obj_style_couleur.nom_f })
  // pour placer le segment vertical, je cherche la largeur de la phrase "signe de ..."
  const largeurSignede = signeDe.getBoundingClientRect().width + 4
  const posTxt = h / 2 + h / 4 - signeDe.getBoundingClientRect().height / 2
  j3pCreeSegment(svg, { x1: largeurSignede, y1: 0, x2: largeurSignede, y2: h, couleur: macouleur, epaisseur: 1 })
  signeDe.style.top = posTxt + 'px'
  signeDe.style.left = '2px'
  // placement du "x"
  const leX = j3pAddElt(nomdiv, 'div', '', { style: styleText })
  j3pAffiche(leX, '', '$x$')
  const posTxt2 = h / 4 - leX.getBoundingClientRect().height / 2
  const posTxt3 = largeurSignede / 2 - leX.getBoundingClientRect().width / 2
  leX.style.top = posTxt2 + 'px'
  leX.style.left = posTxt3 + 'px'
  let borneInfTxt, borneSupTxt
  if (objParam.tab_dimension[3] === undefined) {
    borneInfTxt = '$-\\infty$'
    borneSupTxt = '$+\\infty$'
  } else {
    if ((objParam.tab_dimension[3][0] === undefined) || (objParam.tab_dimension[3][1] === undefined)) {
      borneInfTxt = '$-\\infty$'
      borneSupTxt = '$+\\infty$'
    } else {
      if (objParam.tab_dimension[3][0] === '-infini') {
        borneInfTxt = '$-\\infty$'
      } else {
        borneInfTxt = '$' + fctsEtudeEcrireNbLatex(String(objParam.tab_dimension[3][0])) + '$'
      }
      if (objParam.tab_dimension[3][1] === '+infini') {
        borneSupTxt = '$+\\infty$'
      } else {
        borneSupTxt = '$' + fctsEtudeEcrireNbLatex(String(objParam.tab_dimension[3][1])) + '$'
      }
    }
  }
  const zoneInput = []
  const signeInput = []
  const listeInput = []
  if (afficheLigneX || objParam.afficheBornes) {
    // on remplit entièrement la première ligne (sans zone de saisie)
    // plus et moins l’infini'
    const moinsInf = j3pAddElt(nomdiv, 'div', '', { style: styleText })
    const plusInf = j3pAddElt(nomdiv, 'div', '', { style: styleText })
    // Je n’affiche les bornes que si on affiche toute la première ligne'
    j3pAffiche(moinsInf, '', borneInfTxt)
    moinsInf.style.left = (largeurSignede + 1) + 'px'
    moinsInf.style.top = (h / 4 - moinsInf.getBoundingClientRect().height / 2) + 'px'
    j3pAffiche(plusInf, '', borneSupTxt)
    plusInf.style.left = (L - plusInf.getBoundingClientRect().width - 2 + 1) + 'px'
    plusInf.style.top = (h / 4 - plusInf.getBoundingClientRect().height / 2) + 'px'
  }
  const tabZeroOrdonne = fctsEtudeOrdonneTabSansDoublon(objParam.val_zero_tab, objParam.tab_dimension[3])
  const nbValx = objParam.tab_dimension[2]
  // pour gérer les bornes qui peuvent être des zéros ou des valeurs interdites
  // on y met soit un zéro, soit une double barre
  let newTab4
  if (objParam.tab_dimension[4] === undefined) {
    newTab4 = objParam.tab_dimension[3]
    newTab4.push(']', '[')
  } else {
    newTab4 = objParam.tab_dimension[4]
  }
  // dans le cas où on a changé le domaine de def, il faut qu’on regarde si les bornes sont des zéros de la fonction
  let tabValZeros = []
  let i, j
  for (i = 0; i < objParam.val_zero_tab.length; i++) {
    tabValZeros = tabValZeros.concat(String(objParam.val_zero_tab[i]).split('|'))
  }
  if (newTab4[0] !== '-infini') {
    for (j = 0; j < tabValZeros.length; j++) {
      if (Math.abs(j3pCalculValeur(tabValZeros[j]) - j3pCalculValeur(newTab4[0])) < Math.pow(10, -12)) {
        // c’est que la borne inf du domaine est un zéro de la fonction ou une valeur interdite
        if (newTab4[newTab4.length - 2] === '[') {
          // si c’est un zéro :
          const zeroFct = j3pAddElt(nomdiv, 'div', '0', { style: styleText })
          zeroFct.style.left = (largeurSignede) + 'px'
          zeroFct.style.top = (3 * h / 4 - zeroFct.getBoundingClientRect().height / 2) + 'px'
        } else {
          // et pour une valeur interdite
          j3pCreeSegment(svg, { x1: largeurSignede + 5, y1: h / 2, x2: largeurSignede + 5, y2: h, couleur: macouleur, epaisseur: 1 })
        }
      }
    }
  }
  if (newTab4[newTab4.length - 3] !== '+infini') {
    for (j = 0; j < tabValZeros.length; j++) {
      if (Math.abs(j3pCalculValeur(tabValZeros[j]) - j3pCalculValeur(newTab4[newTab4.length - 3])) < Math.pow(10, -12)) {
        // c’est que la borne inf du domaine est un zéro de la fonction ou une valeur interdite
        if (newTab4[newTab4.length - 1] === ']') {
          // si c’est un zéro
          const zeroFct = j3pAddElt(nomdiv, 'div', '0', { style: styleText })
          zeroFct.style.left = (L - zeroFct.getBoundingClientRect().width - 2 + 1) + 'px'
          zeroFct.style.top = (3 * h / 4 - zeroFct.getBoundingClientRect().height / 2) + 'px'
        } else {
          // et pour une valeur interdite
          j3pCreeSegment(svg, { x1: L - 5, y1: h / 2, x2: L - 5, y2: h, couleur: macouleur, epaisseur: 1 })
        }
      }
    }
  }
  let imin = 0
  let imax = nbValx + 1
  // Fin de la gestion du zéro aux bornes ou de la valeur interdite
  if (afficheLigneX) {
    // les endroits où s’annulent la fonction sont donnés
    // attention les_zeros doivent être du type string (un entier ou un décimal ou un nb fractionnaire de la forme a/b
    // dans un premier temps, on ordonne les nombre du tableau val_zero_tab (qui sont ls endroits où s’annulent notre produit)
    // Dans le cas où sont présents la fonction exp, la fonction ln ou la fonction racine, il va falloir gérer l’affichage'
    for (const [i, nb] of tabZeroOrdonne.entries()) {
      const leZero = /frac/.test(nb) ? nb : j3pVirgule(nb) // nb est une string ou un nombre
      const monZeroTxt = fctsEtudeEcrireNbLatex(leZero)
      const zoneNulle = j3pAddElt(nomdiv, 'div', '', { style: styleText })
      j3pAffiche(zoneNulle, '', '$' + monZeroTxt + '$')
      zoneNulle.style.left = (largeurSignede + (i + 1) * (L - largeurSignede) / (nbValx + 1) - zoneNulle.getBoundingClientRect().width / 2) + 'px'
      zoneNulle.style.top = (h / 4 - zoneNulle.getBoundingClientRect().height / 2) + 'px'
    }
  } else {
    // dans le cas où les valeur en lesquelles s’annule le produit sont à donner
    if (objParam.afficheBornes) imin = 1
    if (objParam.afficheBornes) imax = nbValx
    for (i = imin; i <= imax; i++) {
      // i==0 pour la borne inf du domaine et i==val_x+1 pour la borne_sup du domaine
      const zoneNulle = j3pAddElt(nomdiv, 'div', '', { style: styleText })
      const elt = j3pAffiche(zoneNulle, '', '&1&', { inputmq1: { texte: '' } })
      zoneInput.push(elt.inputmqList[0])
      elt.inputmqList[0].leSpan = elt.parent.parentNode
      elt.inputmqList[0].num = i
      // suivant la palette d’outils, on peut autoriser plus ou moins de touches du clavier
      let toucheRestriction = '\\d,.+-'
      if (laPalette.length > 0) {
        // on peut en ajouter dans ce cas
        if (laPalette.includes('fraction')) { toucheRestriction += '/' }
        if (laPalette.includes('puissance')) { toucheRestriction += '\\^' }
      }
      mqRestriction(elt.inputmqList[0], toucheRestriction, { commandes: laPalette })
      if (i === 0) {
        // placement de la borne inf
        zoneNulle.style.left = (largeurSignede + 2) + 'px'
      } else if (i === (nbValx + 1)) {
        // placement de la borne sup
        zoneNulle.style.left = (L - 5 - zoneNulle.getBoundingClientRect().width) + 'px'
      } else {
        zoneNulle.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - zoneNulle.getBoundingClientRect().width / 2) + 'px'
      }
      zoneNulle.style.top = (h / 4 - zoneNulle.getBoundingClientRect().height / 2) + 'px'
    }
    for (i = 0; i < zoneInput.length; i++) {
      zoneInput[i].addEventListener('input', function () {
        if (this.num === 0) {
          this.leSpan.style.left = (largeurSignede + 2) + 'px'
        } else if (this.num === nbValx + 1) {
          this.leSpan.style.left = (L - 10 - this.getBoundingClientRect().width) + 'px'
        } else {
          this.leSpan.style.left = (largeurSignede + this.num * (L - largeurSignede) / (nbValx + 1) - this.getBoundingClientRect().width / 2) + 'px'
        }
        this.leSpan.style.top = (h / 4 - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
  }
  // on crée tout d’abord les segments verticaux
  const posValZero = []
  for (i = 1; i <= nbValx; i++) {
    posValZero[i] = largeurSignede + i * (L - largeurSignede) / (nbValx + 1)
    j3pCreeSegment(svg, { x1: posValZero[i], y1: h / 2, x2: posValZero[i], y2: h, couleur: macouleur, epaisseur: 1 })
  }
  if (afficheSignes) {
    // on donne le signe de la fonction
    // les signes
    for (i = 1; i <= nbValx + 1; i++) {
      const leSigneFct = j3pAddElt(nomdiv, 'div', '', { style: styleText })
      j3pAffiche(leSigneFct, '', '$' + objParam.signe_tab_corr[i - 1] + '$')
      leSigneFct.style.left = largeurSignede + (i - 1 / 2) * (L - largeurSignede) / (nbValx + 1) - leSigneFct.getBoundingClientRect().width / 2 + 'px'
      leSigneFct.style.top = 3 * h / 4 - leSigneFct.getBoundingClientRect().height / 2 + 'px'
    }
  } else {
    // le signe de la fonction est à compléter
    // emplacement pour le premier signe
    for (i = 1; i <= nbValx + 1; i++) {
      const leSigneFct = j3pAddElt(nomdiv, 'div', '', { style: styleText })
      const elt = j3pAffiche(leSigneFct, '', '&1&', { inputmq1: { texte: '' } })
      signeInput.push(elt.inputmqList[0])
      elt.inputmqList[0].leSpan = elt.parent.parentNode
      elt.inputmqList[0].num = i
      leSigneFct.leContenu = ''// cette variable va me permettre d’identifier le contenu pour l’emp^cher d’avoir plus d’un caractère
      mqRestriction(elt.inputmqList[0], '+-')
      elt.inputmqList[0].addEventListener('keypress', function () {
        this.leContenu = $(this).mathquill('latex')
      })
      elt.inputmqList[0].addEventListener('keyup', function () {
        const newContenu = $(this).mathquill('latex')
        this.leContenu = newContenu[0]
        if ((this.leContenu !== '+') && (this.leContenu !== '-')) {
          this.leContenu = ''
        }
        $(this).mathquill('latex', this.leContenu)
        $(this).focus() // pour passer sans encombre à sesaparcours, j’ai dû le mettre sur 2 lignes
      })
      leSigneFct.style.left = (largeurSignede + (2 * i - 1) * (L - largeurSignede) / (2 * (nbValx + 1)) - elt.inputmqList[0].getBoundingClientRect().width / 2) + 'px'
      leSigneFct.style.top = (3 * h / 4 - elt.inputmqList[0].getBoundingClientRect().height / 2) + 'px'
    }
    // ajustement de la position de chaque signe
    for (i = 1; i <= nbValx + 1; i++) {
      signeInput[i - 1].addEventListener('input', function () {
        this.leSpan.style.left = (largeurSignede + (2 * this.num - 1) * (L - largeurSignede) / (2 * nbValx + 2) - this.getBoundingClientRect().width / 2) + 'px'
        this.leSpan.style.top = (3 * h / 4 - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
    if (imin > imax) j3pFocus(signeInput[0])
  }
  if (affiche0Ligne2) {
    // les zéros à la place de la liste déroulante
    for (i = 1; i <= nbValx; i++) {
      const afficheZero = !tabZeroOrdonne[i - 1] || fctsEtudeEstDansDomaine(tabZeroOrdonne[i - 1], newTab4)
      // !tabZeroOrdonne[i - 1] est utile car cela se produit quand on a des zones de saisie (qui ne correspond plus nécessairement à un zéro de la fonction)
      // du coup tabZeroOrdonne[i - 1] peut être undefined. Dans ce cas, on lui met un 0 dessous
      if (afficheZero) {
        const leZero = j3pAddElt(nomdiv, 'div', '0', { style: styleText })
        leZero.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - leZero.getBoundingClientRect().width / 2) + 'px'
        leZero.style.top = (3 * h / 4 - leZero.getBoundingClientRect().height / 2) + 'px'
      } else {
        j3pCreeSegment(svg, { x1: largeurSignede + i * (L - largeurSignede) / (nbValx + 1) + 3, y1: h / 2, x2: largeurSignede + i * (L - largeurSignede) / (nbValx + 1) + 3, y2: h, couleur: objParam.obj_style_couleur.couleur, epaisseur: 1 })
        j3pCreeSegment(svg, { x1: largeurSignede + i * (L - largeurSignede) / (nbValx + 1), y1: h / 2, x2: largeurSignede + i * (L - largeurSignede) / (nbValx + 1), y2: h, couleur: objParam.obj_style_couleur.couleur, epaisseur: 1 })
      }
    }
  } else {
    // liste déroulante
    for (i = 1; i <= nbValx; i++) {
      const laListe = j3pAddElt(nomdiv, 'div', '', { style: styleText })

      const tabTextListe = (objParam.tab_dimension[5]) ? ['|', '||', '0'] : ['|', '0']
      const elt = j3pAffiche(laListe, '', '#1#', { liste1: { texte: tabTextListe }, className: 'liste1' })
      listeInput.push(elt.selectList[0])
      laListe.style.left = (largeurSignede + i * (L - largeurSignede) / (nbValx + 1) - 8) + 'px'
      laListe.style.top = (3 * h / 4 - laListe.getBoundingClientRect().height / 2) + 'px'
    }
  }
  // affichage d’une palette pour compléter les zones de la première ligne
  let laPaletteDiv
  if (!afficheLigneX && (laPalette.length > 0)) {
    // ceci ne se fait que si la première ligne n’est pas complétée
    laPaletteDiv = j3pAddElt(nomdiv, 'div')
    // palette MQ :
    j3pPaletteMathquill(laPaletteDiv, zoneInput[0], { liste: laPalette, position: { top: h, left: 5 } })
    for (i = 0; i <= nbValx + 1; i++) {
      $(zoneInput[i]).focusin(function () {
        ecoute(this, objParam.palette)
      })
    }
    for (i = 1; i <= (nbValx + 1) * (tabZeroOrdonne.length + 1); i++) {
      $(signeInput[i - 1]).focusin(function () {
        j3pEmpty(laPaletteDiv)
      })
    }
    for (i = 1; i <= (nbValx) * (tabZeroOrdonne.length + 1); i++) {
      $(listeInput[i - 1]).focusin(function () {
        j3pEmpty(laPaletteDiv)
      })
    }
  }

  if (afficheLigneX) {
    if (!afficheSignes) j3pFocus(signeInput[0])
  } else {
    if (imin <= imax) j3pFocus(zoneInput[0])
    else if (!afficheSignes) j3pFocus(signeInput[0])
  }
  return { zone: zoneInput, signe: signeInput, liste: listeInput, palette: laPaletteDiv }
}

/**
 * Construit le tableau de signes utilisé dans les sections signe_courbe1 et signe_courbe2
 * @param {HTMLElement} obj conteneur du tableau
 * @param {Object} objParam
 * @param {string} [objParam.nomFct] pour le nom de la fonction qui sera écrit dans "signe de f(x)" - 'f' par défaut
 * @param {number} [objParam.L] largeur du tableau
 * @param {number} [objParam.h] hauteur du tableau
 * @param {object} [objParam.tabZero] tableau des zéros de la fonction
 * @param {object} [objParam.tabValInterdite] tableau des valeurs interdites de la fonction
 * @param {object} [objParam.tabSignes] tableau contenant les signes de la fonction
 * @param {object} [objParam.eltsAffiches] tableau de 3 booléens : le 1er pour savoir si on écrit les abscisses, le 2ème, pour les zéros et/ou double barre et le 3ème pour les signes
 * @param {Object} [objParam.styleTxt] style du texte
 * @return {Object} objet {zone, signe, liste} pour récupérer les zones de saisie : zone pour les inputs de la 1ère ligne, signe pour les inputs destinés aux signes et liste pour les listes déroulante (zéro ou barre)
 */
export function construireTableauSignes (obj, objParam) {
  // on crée un tableau de signes dans obj
  // dans le tableau, on écrira 'signe de nomFct(x)'
  // tabZero est un tableau contenant la liste des valeurs en la(les)quelle(s) s’annule la fonction
  // ce tableau est vide si la fonction ne s’annule pas'
  // ce tableau contient aussi les éventuelles valeurs interdites
  // objParam.tabValInterdite est un tableau de booleens objParam.tabValInterdite[i] vaut true si tabZero[i] est une valeur interdite
  // zerosAffiches est un booléen : il permet de faire en sorte que la première ligne soit complète ou avec des zones de saisie
  // tabSignes est le tableau contenant le signe de la fonction (utile pour une utlisation comme correction)
  // styleTxt est le style du texte. On en extrait la couleur pour la couleur du tableau
  // eltsAffiches est un tableau contenant 3 booléens :
  const tabZero = objParam.tabZero
  const zerosAffiches = objParam.eltsAffiches[0]// il permet de faire en sorte que la première ligne soit complète ou avec des zones de saisie
  const zerosBarresAffiches = objParam.eltsAffiches[1]// il permet de savoior si on affiche le zéro ou la double barre das la deuxième ligne du tableau
  const signesAffiches = objParam.eltsAffiches[2]// il vaut true pour afficher les signes (pour la correction)
  const styleText = Object.assign({}, objParam.styleTxt)
  const debug = objParam.debug
  const signeDeTxt = j3pChaine(objParam.signeDeTxt, { f: ((objParam.nomFct) ? objParam.nomFct : 'f') + '(x)' })
  styleText.position = 'absolute'
  const nomdiv = obj
  const h = (objParam.h) ? objParam.h : 100
  const L = (objParam.L) ? objParam.L : 420
  const txt2Txt = '<p>' + signeDeTxt + '</p>'
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  svg.setAttribute('width', L + 2)
  svg.setAttribute('height', h + 1)
  nomdiv.appendChild(svg)
  j3pCreeRectangle(svg, { x: 1, y: 0, width: L - 1, height: h, couleur: '#000000', epaisseur: 1 })
  j3pCreeSegment(svg, { x1: 1, y1: 0, x2: 0, y2: h, couleur: '#000000', epaisseur: 1 })
  j3pCreeSegment(svg, { x1: 1, y1: h / 2.5, x2: L, y2: h / 2.5, couleur: '#000000', epaisseur: 1 })
  // placement de la ligne "signe de ax+b"
  const signeDe = j3pAddElt(nomdiv, 'div', '', { style: styleText })
  j3pAffiche(signeDe, '', txt2Txt)
  // pour placer le segment vertical, je cherche la largeur de la phrase "signe de ..."
  const largeurSigneDe = signeDe.getBoundingClientRect().width + 4
  j3pCreeSegment(svg, { x1: largeurSigneDe, y1: 0, x2: largeurSigneDe, y2: h, couleur: '#000000', epaisseur: 1 })
  const posTxt = h / 2.5 + 3 * h / 10 - signeDe.getBoundingClientRect().height / 2
  signeDe.style.top = posTxt + 'px'
  signeDe.style.left = '2px'

  // placement du "x"
  const leX = j3pAddElt(nomdiv, 'div', '', { style: styleText })
  j3pAffiche(leX, '', '$x$')
  const posTxt2 = h / 5 - leX.getBoundingClientRect().height / 2
  leX.style.top = posTxt2 + 'px'
  const posTxt3 = largeurSigneDe / 2 - leX.getBoundingClientRect().width / 2
  leX.style.left = posTxt3 + 'px'

  // plus et moins l’infini'
  const moinsInf = j3pAddElt(nomdiv, 'div', '', { style: styleText })
  const plusInf = j3pAddElt(nomdiv, 'div', '', { style: styleText })
  j3pAffiche(moinsInf, '', '$-\\infty$')
  moinsInf.style.left = (largeurSigneDe + 1) + 'px'
  moinsInf.style.top = (h / 5 - moinsInf.getBoundingClientRect().height / 2) + 'px'
  j3pAffiche(plusInf, '', '$+\\infty$')
  plusInf.style.left = (L - plusInf.getBoundingClientRect().width - 2 + 1) + 'px'
  plusInf.style.top = (h / 5 - plusInf.getBoundingClientRect().height / 2) + 'px'

  const tabAbscisses = tabZero.concat(objParam.tabValInterdite)

  if (debug) console.debug('tabAbscisses : ' + tabAbscisses)
  const tabAbscissesApproche = []
  // ce tableau contient les valeurs de tabAbscisses pour lesquelles on remplace une fraction par une valeur arrondie
  for (let i = 0; i < tabAbscisses.length; i++) {
    const leZero = j3pVirgule(tabAbscisses[i])
    if (leZero.includes('/')) {
      tabAbscissesApproche[i] = Number(leZero.split('/')[0]) / Number(leZero.split('/')[1])
    } else {
      tabAbscissesApproche[i] = Number(leZero)
    }
  }
  let zeroInit = []
  zeroInit = zeroInit.concat(tabAbscissesApproche)
  const tabAbscissesOrdonne1 = []
  for (let i = 0; i < tabAbscissesApproche.length; i++) {
    const objMin = j3pMin(zeroInit)
    tabAbscissesOrdonne1[i] = objMin.min
    zeroInit.splice(objMin.indice, 1)
  }
  const tabAbscissesOrdonne2 = []
  const tabAbscissesOrdonneLatex = []
  for (let i = 0; i < tabAbscisses.length; i++) {
    const index = tabAbscissesApproche.indexOf(tabAbscissesOrdonne1[i])
    tabAbscissesOrdonne2[i] = tabAbscisses[index]
    const leZero = j3pVirgule(tabAbscissesOrdonne2[i])
    if (leZero.includes('/')) {
      tabAbscissesOrdonneLatex[i] = '\\frac{' + leZero.split('/')[0] + '}{' + leZero.split('/')[1] + '}'
    } else {
      tabAbscissesOrdonneLatex[i] = leZero
    }
  }
  if (debug) console.debug('tabAbscissesOrdonne2:' + tabAbscissesOrdonne2 + '  tabAbscissesOrdonneLatex:' + tabAbscissesOrdonneLatex)
  const zoneInput = []
  const signeInput = []
  const listeInput = []
  for (let i = 0; i < tabAbscissesOrdonne2.length; i++) {
    const abscisse = j3pAddElt(nomdiv, 'div', '', { style: styleText })
    if (zerosAffiches) {
      // on écrit les abscisses
      j3pAffiche(abscisse, '', '$' + tabAbscissesOrdonneLatex[i] + '$')
      abscisse.style.left = (largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) - abscisse.getBoundingClientRect().width / 2) + 'px'
      if (tabAbscissesOrdonneLatex[i].includes('frac')) {
        abscisse.style.top = (h / 6.5 - abscisse.getBoundingClientRect().height / 2) + 'px'
      } else {
        abscisse.style.top = (h / 5 - abscisse.getBoundingClientRect().height / 2) + 'px'
      }
    } else {
      // on met des zones de saisie à la place des abscisses
      const elt = j3pAffiche(abscisse, '', '&1&', { inputmq1: { texte: '' } })
      zoneInput.push(elt.inputmqList[0])
      elt.inputmqList[0].leSpan = elt.parent.parentNode
      elt.inputmqList[0].num = i
      abscisse.style.left = (largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) - elt.inputmqList[0].getBoundingClientRect().width / 2) + 'px'
      abscisse.style.top = (h / 5 - elt.inputmqList[0].getBoundingClientRect().height / 2) + 'px'
      elt.inputmqList[0].addEventListener('input', function () {
        this.leSpan.style.left = (largeurSigneDe + (this.num + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) - this.getBoundingClientRect().width / 2) + 'px'
        this.leSpan.style.top = (h / 5 - this.getBoundingClientRect().height / 2) + 'px'
      })
      mqRestriction(elt.inputmqList[0], '\\d.,/\\-')
    }
    // pour les doubles barres ou les zéros
    j3pCreeSegment(svg, { x1: largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1), y1: h / 2.5, x2: largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1), y2: h, couleur: styleText.color, epaisseur: 1 })
    if (zerosBarresAffiches) {
      // on les affiche
      if (tabZero.includes(tabAbscissesOrdonne2[i])) {
        // on écrit un zéro et non une double barre
        const zeroBarre = j3pAddElt(nomdiv, 'div', '', { style: styleText })
        j3pAffiche(zeroBarre, '', '0')
        zeroBarre.style.left = largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) - zeroBarre.getBoundingClientRect().width / 2 + 'px'
        zeroBarre.style.top = h / 2.5 + 3 * h / 10 - zeroBarre.getBoundingClientRect().height / 2 + 'px'
      } else {
        // on écrit une double barre
        j3pCreeSegment(svg, { x1: largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) + 3, y1: h / 2.5, x2: largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) + 3, y2: h, couleur: styleText.color, epaisseur: 1 })
      }
    } else {
      const laListe = j3pAddElt(nomdiv, 'div', '', { style: styleText })
      const tabTextListe = ['', '|', '||', '0']
      const elt = j3pAffiche(laListe, '', '#1#', { liste1: { texte: tabTextListe } })
      listeInput.push(elt.selectList[0])
      laListe.style.left = (largeurSigneDe + (i + 1) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) - 8) + 'px'
      laListe.style.top = (h / 2.5 + 3 * h / 10 - laListe.getBoundingClientRect().height / 2) + 'px'
    }
  }
  for (let i = 0; i < tabAbscissesOrdonne2.length + 1; i++) {
    const leSigne = j3pAddElt(nomdiv, 'div', '', { style: styleText })
    if (signesAffiches) {
      j3pAffiche(leSigne, '', '$' + objParam.tabSignes[i] + '$')
      leSigne.style.left = (largeurSigneDe + (i + 0.5) * (L - largeurSigneDe) / (tabAbscissesOrdonne2.length + 1) - leSigne.getBoundingClientRect().width / 2) + 'px'
      leSigne.style.top = (h / 2.5 + 3 * h / 10 - leSigne.getBoundingClientRect().height / 2) + 'px'
    } else {
      const elt = j3pAffiche(leSigne, '', '&1&', { inputmq1: { texte: '' } })
      signeInput.push(elt.inputmqList[0])
      const lengthTab = tabAbscissesOrdonne2.length
      leSigne.style.left = (largeurSigneDe + (i + 0.5) * (L - largeurSigneDe) / (lengthTab + 1) - leSigne.getBoundingClientRect().width / 2) + 'px'
      leSigne.style.top = (h / 2.5 + 3 * h / 10 - leSigne.getBoundingClientRect().height / 2) + 'px'
      mqRestriction(elt.inputmqList[0], '+\\-')
      elt.inputmqList[0].leSpan = elt.parent.parentNode
      elt.inputmqList[0].num = i
      elt.inputmqList[0].addEventListener('input', function () {
        this.leSpan.style.left = (largeurSigneDe + (this.num + 0.5) * (L - largeurSigneDe) / (lengthTab + 1) - this.getBoundingClientRect().width / 2) + 'px'
        this.leSpan.style.top = (h / 2.5 + 3 * h / 10 - this.getBoundingClientRect().height / 2) + 'px'
      })
    }
  }
  if (!zerosAffiches) {
    j3pFocus(zoneInput[0])
  } else if (!signesAffiches) {
    j3pFocus(signeInput[0])
  }
  return { zone: zoneInput, signe: signeInput, liste: listeInput }
}