cisstech / platon

Platform for Learning and Teaching Online
Other
13 stars 3 forks source link

Problème de chargement de donnée dans un widget #38

Closed Ofghanirre closed 1 year ago

Ofghanirre commented 1 year ago

Hello ! J'espère que tu vas bien, j'ai récemment réaliser des widgets permettant l'affichage de charte graphique avec a librairie Echart que tu m'avais recommandé !

Je pense avoir fais tout à peu prêt correctement, et sur la page de documentation / bac à sable, tout semble fonctionner comme je le veux. (Même si je dois entièrement copier profondément certains objets)

Seulement voilà, une fois dans un exercice, le composant s'affiche, mais vide, il n'est jamais remplis, que ça soit en type script ou en python.

J'observe grâce à une balise script dans le DOM que mes données ont bien été saisies, mais c'est comme si le composant ne les recevait jamais.

image

Possible de m'aider à mieux comprendre / maitriser tout ça?

C'est encore WIP mais l'objectif serait de fournir une API plus ou moins complètes pour que les utilisateurs affichent des graphes avec quelques options.

Liens:

Et globalement j'en reviens aux questions suivantes:

Cela m'intéresserait d'avoir ton avis / ta version de ce que j'ai fais. Et ça m'aidera pas mal à comprendre comment bien utiliser des widgets / composants...

Voilà voilà, je reste à ta disposition si tu as la moindre question

Ofghanirre commented 1 year ago

J'oubliais, voici le code de l'exercice en question:

sandbox="python"

pieTest = :wc-chart-viewer-pies
pieTest.mode = "donut"
pieTest.data = [
  { name: "test", value: 42}, 
  { name: "truc", value: 30}
]
pieTest.title.text=  "ahhhh"

builder==#!lang=python

==

grader==#!lang=python

==

title==
Test Charts
==

statement ==
==

form==
{{pieTest}}
==
mciissee commented 1 year ago

@Ofghanirre Pour que ton code fonctionne, il manque un truc dans le template de ton composant, à savoir l'intégration du composant <wc-base [(state)]="state" /> qui s'occupe de la synchronisation du state avec les différentes manières possibles (via l'interpolation de l'input [state], via la définition explicite des props en tant qu'attribut HTML ou via une balise script). Tu n'a pas l'erreur sur la doc des composants car sur cette dernière le state d'un composant est défini via l'interpolation donc toujours détecter par angular.

<div *ngIf="commonOption" echarts [options]="commonOption" [merge]="mergedOption" class="demo-chart"></div>
<!--  IL TE MANQUAIT CETTE LIGNE -->
<wc-base [(state)]="state" /> 

Tu peux aussi remarquer que j'ai ajouté la prop [merge] sur la directive echarts. La raison est expliquée ici : https://github.com/xieziyu/ngx-echarts/issues/299#issuecomment-962883736 et sur la documentation https://xieziyu.github.io/ngx-echarts/api-doc/#api.

Je t'ai aussi simplifié le code de ton composant, je te laisse faire une diff des modifications pour comparer mais tu peux simplifier encore plus le composant:

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, Input, inject } from '@angular/core'
import { deepCopy } from '@platon/core/common'
import { EChartsOption } from 'echarts'
import { WebComponent, WebComponentHooks } from '../../web-component'
import {
  ChartViewerPiesComponentDefinition,
  ChartViewerPiesState,
  donutChartViewerPiesState,
  halfdonutChartViewerPiesState,
  nightingaleChartViewerPiesState,
  simpleChartViewerPiesState,
} from './chart-viewer-pies'

@Component({
  selector: 'wc-chart-viewer-pies, wc-cv-pies',
  templateUrl: './chart-viewer-pies.component.html',
  styleUrls: ['chart-viewer-pies.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
@WebComponent(ChartViewerPiesComponentDefinition)
export class ChartViewerPiesComponent implements WebComponentHooks<ChartViewerPiesState> {
  readonly injector = inject(Injector)
  readonly changeDetectorRef = inject(ChangeDetectorRef)

  protected commonOption = simpleChartViewerPiesState
  protected mergedOption: EChartsOption = {}

  @Input() state!: ChartViewerPiesState

  onChangeState() {
    this.state.mode = this.state.mode ?? 'simple'

    this.mergedOption = {
      donut: deepCopy(donutChartViewerPiesState),
      simple: deepCopy(simpleChartViewerPiesState),
      nightingale: deepCopy(nightingaleChartViewerPiesState),
      'half-donut': deepCopy(halfdonutChartViewerPiesState),
    }[this.state.mode]

    if (Array.isArray(this.mergedOption.series)) {
      this.mergedOption.series[0].data = deepCopy(this.state.data)
      if (this.state.mode == 'half-donut') {
        if (Array.isArray(this.mergedOption.series) && Array.isArray(this.mergedOption.series[0]?.data)) {
          this.mergedOption.series[0].data = [
            ...this.mergedOption.series[0].data,
            // The adding piece to hide half of the chart
            {
              value: this.state.data.map((element) => element.value).reduce((sum, value) => sum + value, 0),
              itemStyle: {
                // stop the chart from rendering this piece
                color: 'none',
                decal: {
                  symbol: 'none',
                },
              },
              label: {
                show: false,
              },
            },
          ]
          // make an record to fill the bottom 50%
        } else {
          console.log('error')
        }
      }
      this.mergedOption.series[0].name = this.state.dataTitle
    }

    this.mergedOption = {
      ...this.mergedOption,
      title: this.state.title,
      legend: this.state.legend,
      tooltip: this.state.tooltip,
    }
  }
}
mciissee commented 1 year ago

Et pour répondre à tes questions

Le state est l'objet qui est modifiable par les utilisateurs du WebComposants / widget? Oui l'input state est l'objet qui est modifiable par les utilisateurs sur le navigateur et les enseignants dans les graders/builders. En ajoutant le décorateur @WebComponent sur ta classe, un getter et et setter sont crées au runtime pour détecter les changements profonds sur ton objet afin d'appeler les hooks onChangeState et autres...

Est-ce que ma façon d'ajouter des champs / properties à mes objets et correcte ? (passer d'un State à un EChartsOption...) Oui c'est la bonne manière de faire dans la méthode onChangeState mais tu pourrais mieux faire en faisant ça dans un pipe angular afin de rendre cette logique réutilisable si jamais tu en a besoin pour d'autres web compo.

Est-ce que les widgets et les WebComponents ont un comportement différents ? Aucune différence, ils fonctionnent de la même manière, un widget est un web composant. La séparation widgets et forms est là uniquement pour différencier les composants qui permettent uniquement d'afficher une donnée et ceux qui permettent de récupérer des inputs.

mciissee commented 1 year ago

Par ailleurs la lib echarts n'est pas entièrement chargée dans le navigateur, il y a un three shaking pour intégrer uniquement les fonctionnalités nécessaires dans le fichier suivant https://github.com/cisstech/platon/blob/main/libs/core/browser/src/lib/vendors/ngx-echarts/config.ts, mais je vois que tu utilise la fonctionnalité de toolbox, tu dois donc modifier la config de la manière suivante :

Capture d’écran 2023-08-31 à 21 18 54