mesa-react / frontend /src /components /PlotFigure.jsx
Guilherme Silberfarb Costa
correcao de trabalhos tecnicos e indices
71cdfeb
Raw
History Blame Contribute Delete
5.66 kB
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