File size: 5,662 Bytes
0a8b8ba
d6c9678
 
 
9e7c650
 
 
 
 
 
 
 
 
 
d953b73
25aa274
 
9e7c650
0a8b8ba
 
9e7c650
 
b2359f9
0a8b8ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e7c650
 
 
b2359f9
9e7c650
d6c9678
 
 
 
9e7c650
 
 
 
 
 
d6c9678
 
 
 
 
 
 
9e7c650
 
 
 
d6c9678
 
 
9e7c650
d6c9678
 
 
 
 
9e7c650
d6c9678
25aa274
d6c9678
 
9e7c650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71cdfeb
9e7c650
71cdfeb
9e7c650
71cdfeb
 
9e7c650
d6c9678
0a8b8ba
25aa274
0a8b8ba
d953b73
 
 
 
 
25aa274
d953b73
 
 
 
 
 
 
 
 
71cdfeb
d953b73
 
25aa274
d953b73
0a8b8ba
 
 
9e7c650
 
 
 
25aa274
9e7c650
 
 
 
 
 
0a8b8ba
 
 
d6c9678
 
 
f7fe834
9e7c650
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import React, { useEffect, useRef, useState } from 'react'
import Plot from 'react-plotly.js'
import Plotly from 'plotly.js-dist-min'

function PlotFigure({
  figure,
  indexedFigure = null,
  onRequestIndexedFigure = null,
  title,
  subtitle = '',
  forceHideLegend = false,
  className = '',
  lazy = false,
  showPointIndexToggle = false,
  headerActions = null,
  trailingHeaderActions = null,
  revisionKey = '',
}) {
  const containerRef = useRef(null)
  const [shouldRenderPlot, setShouldRenderPlot] = useState(() => !lazy)
  const [showPointIndices, setShowPointIndices] = useState(false)
  const [loadingIndexedFigure, setLoadingIndexedFigure] = useState(false)
  const figureResetKey = revisionKey ? String(revisionKey) : figure

  useEffect(() => {
    if (!lazy) {
      setShouldRenderPlot(true)
      return undefined
    }
    if (shouldRenderPlot) return undefined
    if (typeof window === 'undefined' || !('IntersectionObserver' in window)) {
      setShouldRenderPlot(true)
      return undefined
    }
    const target = containerRef.current
    if (!target) return undefined
    const observer = new IntersectionObserver(
      (entries) => {
        const isVisible = entries.some((entry) => entry.isIntersecting || entry.intersectionRatio > 0)
        if (isVisible) {
          setShouldRenderPlot(true)
          observer.disconnect()
        }
      },
      { rootMargin: '240px 0px', threshold: 0.01 },
    )
    observer.observe(target)
    return () => observer.disconnect()
  }, [lazy, shouldRenderPlot])

  useEffect(() => {
    setShowPointIndices(false)
    setLoadingIndexedFigure(false)
  }, [figureResetKey])

  if (!figure) {
    return <div className="empty-box">Grafico indisponivel.</div>
  }

  const hasIndexedFigure = Boolean(indexedFigure)
  const canRequestIndexedFigure = typeof onRequestIndexedFigure === 'function'
  const canToggleIndices = hasIndexedFigure || canRequestIndexedFigure
  const activeFigure = showPointIndices && hasIndexedFigure ? indexedFigure : figure
  const safeFigure = activeFigure || { data: [], layout: {} }
  const baseLayout = safeFigure.layout || {}
  const { width: _ignoreWidth, ...layoutNoWidth } = baseLayout
  const safeAnnotations = Array.isArray(baseLayout.annotations)
    ? baseLayout.annotations.map((annotation) => {
        const { ax, ay, axref, ayref, arrowhead, arrowsize, arrowwidth, arrowcolor, standoff, ...clean } = annotation || {}
        return { ...clean, showarrow: false }
      })
    : baseLayout.annotations

  const data = (safeFigure.data || []).map((trace) => (
    forceHideLegend ? { ...trace, showlegend: false } : { ...trace }
  ))
  const layout = {
    ...layoutNoWidth,
    autosize: true,
    annotations: Array.isArray(safeAnnotations) ? safeAnnotations : [],
    margin: baseLayout.margin || { t: 40, r: 20, b: 50, l: 50 },
  }
  if (forceHideLegend) {
    layout.showlegend = false
  }

  const plotHeight = layout.height ? `${layout.height}px` : '100%'
  const plotRevision = `${showPointIndices && hasIndexedFigure ? 'indices' : 'base'}:${String(revisionKey || '')}`
  const cardClassName = `plot-card ${className}`.trim()

  async function handleToggleChange(event) {
    const checked = Boolean(event.target.checked)
    if (!checked) {
      setShowPointIndices(false)
      return
    }
    if (hasIndexedFigure) {
      setShowPointIndices(true)
      return
    }
    if (!canRequestIndexedFigure || loadingIndexedFigure) return
    setLoadingIndexedFigure(true)
    try {
      const loadedFigure = await onRequestIndexedFigure()
      setShowPointIndices(Boolean(loadedFigure))
    } catch {
      setShowPointIndices(false)
    } finally {
      setLoadingIndexedFigure(false)
    }
  }

  const toggleTitle = loadingIndexedFigure
    ? 'Carregando INDICES dos pontos...'
    : hasIndexedFigure
      ? 'Exibir INDICES dos pontos'
      : canRequestIndexedFigure
        ? 'Carregar INDICES dos pontos sob demanda'
        : 'Este gráfico não trouxe uma versão com INDICES.'

  return (
    <div ref={containerRef} className={cardClassName}>
      {title || subtitle || showPointIndexToggle || headerActions || trailingHeaderActions ? (
        <div className="plot-card-head">
          <div className="plot-card-head-main">
            {title ? <h4 className="plot-card-title">{title}</h4> : null}
            {subtitle ? <div className="plot-card-subtitle">{subtitle}</div> : null}
          </div>
          <div className="plot-card-head-actions">
            {headerActions}
            {showPointIndexToggle ? (
              <label className={`plot-card-toggle${canToggleIndices ? '' : ' is-disabled'}`}>
                <input
                  type="checkbox"
                  checked={showPointIndices}
                  disabled={!canToggleIndices || loadingIndexedFigure}
                  onChange={handleToggleChange}
                  title={toggleTitle}
                />
                {loadingIndexedFigure ? 'Carregando...' : 'INDICES'}
              </label>
            ) : null}
            {trailingHeaderActions}
          </div>
        </div>
      ) : null}
      {shouldRenderPlot ? (
        <div className="plot-card-body">
          <Plot
            data={data}
            layout={layout}
            revision={plotRevision}
            config={{ responsive: true, displaylogo: false }}
            style={{ width: '100%', height: plotHeight, minHeight: '320px' }}
            useResizeHandler
            plotly={Plotly}
          />
        </div>
      ) : (
        <div className="plot-lazy-placeholder">Carregando gráfico...</div>
      )}
    </div>
  )
}

export default PlotFigure