Spaces:
Running
Running
| import React, { useEffect, useRef, useState } from 'react' | |
| import Plot from 'react-plotly.js' | |
| import Plotly from 'plotly.js-dist-min' | |
| function PlotFigure({ figure, title, subtitle = '', forceHideLegend = false, className = '', lazy = false }) { | |
| const containerRef = useRef(null) | |
| const [shouldRenderPlot, setShouldRenderPlot] = useState(() => !lazy) | |
| 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]) | |
| if (!figure) { | |
| return <div className="empty-box">Grafico indisponivel.</div> | |
| } | |
| const data = (figure.data || []).map((trace) => { | |
| if (!forceHideLegend) return trace | |
| return { ...trace, showlegend: false } | |
| }) | |
| const baseLayout = figure.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 layout = { | |
| ...layoutNoWidth, | |
| autosize: true, | |
| annotations: 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 cardClassName = `plot-card ${className}`.trim() | |
| return ( | |
| <div ref={containerRef} className={cardClassName}> | |
| {title || subtitle ? ( | |
| <div className="plot-card-head"> | |
| {title ? <h4 className="plot-card-title">{title}</h4> : null} | |
| {subtitle ? <div className="plot-card-subtitle">{subtitle}</div> : null} | |
| </div> | |
| ) : null} | |
| {shouldRenderPlot ? ( | |
| <Plot | |
| data={data} | |
| layout={layout} | |
| config={{ responsive: true, displaylogo: false }} | |
| style={{ width: '100%', height: plotHeight, minHeight: '320px' }} | |
| useResizeHandler | |
| plotly={Plotly} | |
| /> | |
| ) : ( | |
| <div className="plot-lazy-placeholder">Carregando gráfico...</div> | |
| )} | |
| </div> | |
| ) | |
| } | |
| export default React.memo(PlotFigure) | |