Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
0d8b6ec
1
Parent(s): a577d9a
alteracoes para grandes quantidades de pontos
Browse files- backend/app/api/elaboracao.py +20 -0
- backend/app/models/session.py +2 -0
- backend/app/services/elaboracao_service.py +261 -7
- backend/requirements.txt +1 -0
- frontend/src/api.js +8 -0
- frontend/src/components/ElaboracaoTab.jsx +649 -111
- frontend/src/styles.css +60 -0
backend/app/api/elaboracao.py
CHANGED
|
@@ -86,6 +86,14 @@ class DispersaoPayload(SessionPayload):
|
|
| 86 |
eixo_y_coluna: str | None = None
|
| 87 |
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
class TransformPreviewPayload(SessionPayload):
|
| 90 |
transformacao_y: str = "(x)"
|
| 91 |
transformacoes_x: dict[str, str] = Field(default_factory=dict)
|
|
@@ -288,6 +296,18 @@ def model_dispersao(payload: DispersaoPayload) -> dict[str, Any]:
|
|
| 288 |
)
|
| 289 |
|
| 290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
@router.post("/transform-preview")
|
| 292 |
def transform_preview(payload: TransformPreviewPayload) -> dict[str, Any]:
|
| 293 |
session = session_store.get(payload.session_id)
|
|
|
|
| 86 |
eixo_y_coluna: str | None = None
|
| 87 |
|
| 88 |
|
| 89 |
+
class DispersaoInterativoPayload(SessionPayload):
|
| 90 |
+
alvo: str = "secao10"
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class DiagnosticoInterativoPayload(SessionPayload):
|
| 94 |
+
grafico: str = "obs_calc"
|
| 95 |
+
|
| 96 |
+
|
| 97 |
class TransformPreviewPayload(SessionPayload):
|
| 98 |
transformacao_y: str = "(x)"
|
| 99 |
transformacoes_x: dict[str, str] = Field(default_factory=dict)
|
|
|
|
| 296 |
)
|
| 297 |
|
| 298 |
|
| 299 |
+
@router.post("/dispersao-interativo")
|
| 300 |
+
def dispersao_interativo(payload: DispersaoInterativoPayload) -> dict[str, Any]:
|
| 301 |
+
session = session_store.get(payload.session_id)
|
| 302 |
+
return elaboracao_service.obter_grafico_dispersao_interativo(session, payload.alvo)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
@router.post("/diagnostico-interativo")
|
| 306 |
+
def diagnostico_interativo(payload: DiagnosticoInterativoPayload) -> dict[str, Any]:
|
| 307 |
+
session = session_store.get(payload.session_id)
|
| 308 |
+
return elaboracao_service.obter_grafico_diagnostico_interativo(session, payload.grafico)
|
| 309 |
+
|
| 310 |
+
|
| 311 |
@router.post("/transform-preview")
|
| 312 |
def transform_preview(payload: TransformPreviewPayload) -> dict[str, Any]:
|
| 313 |
session = session_store.get(payload.session_id)
|
backend/app/models/session.py
CHANGED
|
@@ -51,6 +51,7 @@ class SessionState:
|
|
| 51 |
pacote_visualizacao: dict[str, Any] | None = None
|
| 52 |
dados_visualizacao: pd.DataFrame | None = None
|
| 53 |
avaliacoes_visualizacao: list[dict[str, Any]] = field(default_factory=list)
|
|
|
|
| 54 |
|
| 55 |
elaborador: dict[str, Any] | None = None
|
| 56 |
|
|
@@ -62,6 +63,7 @@ class SessionState:
|
|
| 62 |
self.avaliacoes_elaboracao = []
|
| 63 |
self.transformacao_y = "(x)"
|
| 64 |
self.transformacoes_x = {}
|
|
|
|
| 65 |
|
| 66 |
def reset_visualizacao(self) -> None:
|
| 67 |
self.pacote_visualizacao = None
|
|
|
|
| 51 |
pacote_visualizacao: dict[str, Any] | None = None
|
| 52 |
dados_visualizacao: pd.DataFrame | None = None
|
| 53 |
avaliacoes_visualizacao: list[dict[str, Any]] = field(default_factory=list)
|
| 54 |
+
graficos_dispersao_cache: dict[str, dict[str, Any]] = field(default_factory=dict)
|
| 55 |
|
| 56 |
elaborador: dict[str, Any] | None = None
|
| 57 |
|
|
|
|
| 63 |
self.avaliacoes_elaboracao = []
|
| 64 |
self.transformacao_y = "(x)"
|
| 65 |
self.transformacoes_x = {}
|
| 66 |
+
self.graficos_dispersao_cache = {}
|
| 67 |
|
| 68 |
def reset_visualizacao(self) -> None:
|
| 69 |
self.pacote_visualizacao = None
|
backend/app/services/elaboracao_service.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
import re
|
|
@@ -10,6 +11,8 @@ from typing import Any
|
|
| 10 |
|
| 11 |
import numpy as np
|
| 12 |
import pandas as pd
|
|
|
|
|
|
|
| 13 |
from fastapi import HTTPException
|
| 14 |
|
| 15 |
from app.core.elaboracao import charts, geocodificacao
|
|
@@ -48,6 +51,7 @@ from app.services.serializers import dataframe_to_payload, figure_to_payload, sa
|
|
| 48 |
|
| 49 |
_AVALIADORES_PATH = Path(__file__).resolve().parent.parent / "core" / "elaboracao" / "avaliadores.json"
|
| 50 |
_AVALIADORES_CACHE: list[dict[str, Any]] | None = None
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
def _is_rh_col(coluna: str) -> bool:
|
|
@@ -143,6 +147,161 @@ def list_avaliadores() -> list[dict[str, Any]]:
|
|
| 143 |
return _AVALIADORES_CACHE
|
| 144 |
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
def _clean_int_list(values: list[Any] | None) -> list[int]:
|
| 147 |
if not values:
|
| 148 |
return []
|
|
@@ -799,6 +958,12 @@ def apply_selection(
|
|
| 799 |
fig_dispersao = charts.criar_graficos_dispersao(df_filtrado[colunas_x_validas], df_filtrado[coluna_y])
|
| 800 |
except Exception:
|
| 801 |
fig_dispersao = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 802 |
|
| 803 |
busca_payload = search_transformacoes(session, grau_min_coef=grau_min_coef, grau_min_f=grau_min_f)
|
| 804 |
|
|
@@ -830,7 +995,11 @@ def apply_selection(
|
|
| 830 |
return {
|
| 831 |
"estatisticas": dataframe_to_payload(estatisticas, decimals=4),
|
| 832 |
"micronumerosidade_html": micro_html,
|
| 833 |
-
"grafico_dispersao":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
"busca": busca_payload,
|
| 835 |
"resumo_outliers": resumo_outliers,
|
| 836 |
"outliers_html": outliers_html,
|
|
@@ -1023,6 +1192,13 @@ def fit_model(
|
|
| 1023 |
)
|
| 1024 |
except Exception:
|
| 1025 |
fig_dispersao_transf = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1026 |
|
| 1027 |
fig_corr = None
|
| 1028 |
try:
|
|
@@ -1033,6 +1209,26 @@ def fit_model(
|
|
| 1033 |
fig_corr = None
|
| 1034 |
|
| 1035 |
graficos = charts.criar_painel_diagnostico(resultado)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1036 |
|
| 1037 |
tabela_metricas = resultado["tabela_obs_calc"].copy()
|
| 1038 |
tabela_metricas_estado = tabela_metricas.set_index("Índice")
|
|
@@ -1090,11 +1286,22 @@ def fit_model(
|
|
| 1090 |
"equacoes": sanitize_value(equacoes),
|
| 1091 |
"tabela_coef": dataframe_to_payload(resultado["tabela_coef"], decimals=4),
|
| 1092 |
"tabela_obs_calc": dataframe_to_payload(resultado["tabela_obs_calc"], decimals=4),
|
| 1093 |
-
"grafico_dispersao_modelo":
|
| 1094 |
-
"
|
| 1095 |
-
"
|
| 1096 |
-
"
|
| 1097 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1098 |
"grafico_correlacao": figure_to_payload(fig_corr),
|
| 1099 |
"tabela_metricas": dataframe_to_payload(tabela_metricas, decimals=4),
|
| 1100 |
"tabela_outliers_excluidos": tabela_outliers_excluidos,
|
|
@@ -1214,9 +1421,19 @@ def gerar_grafico_dispersao_modelo(
|
|
| 1214 |
fig = charts.criar_graficos_dispersao(x_base, y_base)
|
| 1215 |
except Exception:
|
| 1216 |
fig = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1217 |
|
| 1218 |
return {
|
| 1219 |
-
"grafico":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1220 |
"eixo_x_aplicado": "nao_transformado" if eixo_x_norm in {"nao_transformado", "não_transformado"} else "transformado",
|
| 1221 |
"eixo_y_tipo_aplicado": eixo_y_norm,
|
| 1222 |
"eixo_y_residuo_aplicado": eixo_residuo_norm if eixo_y_norm == "residuo" else None,
|
|
@@ -1225,6 +1442,43 @@ def gerar_grafico_dispersao_modelo(
|
|
| 1225 |
}
|
| 1226 |
|
| 1227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
def apply_outlier_filters(session: SessionState, filtros: list[dict[str, Any]]) -> dict[str, Any]:
|
| 1229 |
metricas = session.tabela_metricas_estado
|
| 1230 |
if metricas is None:
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import base64
|
| 4 |
import json
|
| 5 |
import os
|
| 6 |
import re
|
|
|
|
| 11 |
|
| 12 |
import numpy as np
|
| 13 |
import pandas as pd
|
| 14 |
+
import plotly.graph_objects as go
|
| 15 |
+
import plotly.io as pio
|
| 16 |
from fastapi import HTTPException
|
| 17 |
|
| 18 |
from app.core.elaboracao import charts, geocodificacao
|
|
|
|
| 51 |
|
| 52 |
_AVALIADORES_PATH = Path(__file__).resolve().parent.parent / "core" / "elaboracao" / "avaliadores.json"
|
| 53 |
_AVALIADORES_CACHE: list[dict[str, Any]] | None = None
|
| 54 |
+
LIMIAR_DISPERSAO_PNG = 1000
|
| 55 |
|
| 56 |
|
| 57 |
def _is_rh_col(coluna: str) -> bool:
|
|
|
|
| 147 |
return _AVALIADORES_CACHE
|
| 148 |
|
| 149 |
|
| 150 |
+
def _usar_png_dispersao(total_pontos: int) -> bool:
|
| 151 |
+
return int(total_pontos) > LIMIAR_DISPERSAO_PNG
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _contar_paineis_dispersao(fig: Any) -> int:
|
| 155 |
+
try:
|
| 156 |
+
data = list(getattr(fig, "data", []) or [])
|
| 157 |
+
eixos: set[str] = set()
|
| 158 |
+
for trace in data:
|
| 159 |
+
eixo = str(getattr(trace, "xaxis", "") or "").strip().lower()
|
| 160 |
+
if not eixo:
|
| 161 |
+
eixo = "x"
|
| 162 |
+
eixos.add(eixo)
|
| 163 |
+
return max(1, len(eixos))
|
| 164 |
+
except Exception:
|
| 165 |
+
return 1
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _estilizar_figura_dispersao_png(fig: Any) -> Any:
|
| 169 |
+
try:
|
| 170 |
+
fig_out = go.Figure(fig)
|
| 171 |
+
except Exception:
|
| 172 |
+
return fig
|
| 173 |
+
|
| 174 |
+
for trace in fig_out.data or []:
|
| 175 |
+
mode = str(getattr(trace, "mode", "") or "")
|
| 176 |
+
if "markers" in mode:
|
| 177 |
+
marker_atual = getattr(trace, "marker", None)
|
| 178 |
+
marker = dict(marker_atual.to_plotly_json() if hasattr(marker_atual, "to_plotly_json") else (marker_atual or {}))
|
| 179 |
+
marker["color"] = "#FF8C00"
|
| 180 |
+
marker["size"] = float(marker.get("size") or 8)
|
| 181 |
+
marker["opacity"] = float(marker.get("opacity") if marker.get("opacity") is not None else 0.84)
|
| 182 |
+
linha = dict(marker.get("line") or {})
|
| 183 |
+
linha["color"] = linha.get("color") or "#1f2933"
|
| 184 |
+
linha["width"] = float(linha.get("width") or 1)
|
| 185 |
+
marker["line"] = linha
|
| 186 |
+
trace.marker = marker
|
| 187 |
+
|
| 188 |
+
if "lines" in mode:
|
| 189 |
+
line_atual = getattr(trace, "line", None)
|
| 190 |
+
line = dict(line_atual.to_plotly_json() if hasattr(line_atual, "to_plotly_json") else (line_atual or {}))
|
| 191 |
+
line["color"] = line.get("color") or "#dc3545"
|
| 192 |
+
line["width"] = float(line.get("width") or 2)
|
| 193 |
+
trace.line = line
|
| 194 |
+
|
| 195 |
+
trace.showlegend = False
|
| 196 |
+
|
| 197 |
+
fig_out.update_layout(
|
| 198 |
+
showlegend=False,
|
| 199 |
+
plot_bgcolor="white",
|
| 200 |
+
paper_bgcolor="white",
|
| 201 |
+
margin=dict(t=78, r=20, b=48, l=54),
|
| 202 |
+
)
|
| 203 |
+
fig_out.update_xaxes(showgrid=True, gridcolor="#d7dde3", showline=True, linecolor="#1f2933", zeroline=False)
|
| 204 |
+
fig_out.update_yaxes(showgrid=True, gridcolor="#d7dde3", showline=True, linecolor="#1f2933", zeroline=False)
|
| 205 |
+
return fig_out
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _renderizar_png_grafico(fig: Any, *, estilo: str | None = None) -> dict[str, Any] | None:
|
| 209 |
+
if fig is None:
|
| 210 |
+
return None
|
| 211 |
+
try:
|
| 212 |
+
figura_export = _estilizar_figura_dispersao_png(fig) if str(estilo or "") == "dispersao" else fig
|
| 213 |
+
largura_layout = int(getattr(figura_export.layout, "width", 0) or 0)
|
| 214 |
+
altura_layout = int(getattr(figura_export.layout, "height", 0) or 0)
|
| 215 |
+
|
| 216 |
+
if str(estilo or "") == "dispersao":
|
| 217 |
+
paineis = _contar_paineis_dispersao(figura_export)
|
| 218 |
+
n_cols = min(3, max(1, paineis))
|
| 219 |
+
altura = altura_layout if altura_layout > 0 else 900
|
| 220 |
+
largura = largura_layout if largura_layout > 0 else (460 * n_cols)
|
| 221 |
+
largura = max(860, min(largura, 2200))
|
| 222 |
+
altura = max(520, min(altura, 1800))
|
| 223 |
+
else:
|
| 224 |
+
altura = altura_layout if altura_layout > 0 else 420
|
| 225 |
+
largura = largura_layout if largura_layout > 0 else int(round(altura * 1.75))
|
| 226 |
+
largura = max(640, min(largura, 2000))
|
| 227 |
+
altura = max(360, min(altura, 1600))
|
| 228 |
+
|
| 229 |
+
imagem = pio.to_image(figura_export, format="png", width=largura, height=altura, scale=1)
|
| 230 |
+
return {
|
| 231 |
+
"mime_type": "image/png",
|
| 232 |
+
"image_base64": base64.b64encode(imagem).decode("ascii"),
|
| 233 |
+
"width": largura,
|
| 234 |
+
"height": altura,
|
| 235 |
+
}
|
| 236 |
+
except Exception:
|
| 237 |
+
return None
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def _montar_payload_dispersao(
|
| 241 |
+
session: SessionState,
|
| 242 |
+
*,
|
| 243 |
+
cache_key: str,
|
| 244 |
+
fig: Any,
|
| 245 |
+
total_pontos: int,
|
| 246 |
+
estilo_png: str | None = "dispersao",
|
| 247 |
+
) -> dict[str, Any]:
|
| 248 |
+
key = str(cache_key or "").strip().lower()
|
| 249 |
+
if not key:
|
| 250 |
+
raise ValueError("cache_key invalido")
|
| 251 |
+
|
| 252 |
+
if fig is None:
|
| 253 |
+
session.graficos_dispersao_cache.pop(key, None)
|
| 254 |
+
return {
|
| 255 |
+
"modo": "interativo",
|
| 256 |
+
"grafico": None,
|
| 257 |
+
"grafico_png": None,
|
| 258 |
+
"total_pontos": int(total_pontos),
|
| 259 |
+
"limiar_png": LIMIAR_DISPERSAO_PNG,
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
payload_interativo = figure_to_payload(fig)
|
| 263 |
+
if not payload_interativo:
|
| 264 |
+
session.graficos_dispersao_cache.pop(key, None)
|
| 265 |
+
return {
|
| 266 |
+
"modo": "interativo",
|
| 267 |
+
"grafico": None,
|
| 268 |
+
"grafico_png": None,
|
| 269 |
+
"total_pontos": int(total_pontos),
|
| 270 |
+
"limiar_png": LIMIAR_DISPERSAO_PNG,
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
usar_png = _usar_png_dispersao(int(total_pontos))
|
| 274 |
+
if not usar_png:
|
| 275 |
+
session.graficos_dispersao_cache.pop(key, None)
|
| 276 |
+
return {
|
| 277 |
+
"modo": "interativo",
|
| 278 |
+
"grafico": payload_interativo,
|
| 279 |
+
"grafico_png": None,
|
| 280 |
+
"total_pontos": int(total_pontos),
|
| 281 |
+
"limiar_png": LIMIAR_DISPERSAO_PNG,
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
payload_png = _renderizar_png_grafico(fig, estilo=estilo_png)
|
| 285 |
+
if not payload_png:
|
| 286 |
+
session.graficos_dispersao_cache.pop(key, None)
|
| 287 |
+
return {
|
| 288 |
+
"modo": "interativo",
|
| 289 |
+
"grafico": payload_interativo,
|
| 290 |
+
"grafico_png": None,
|
| 291 |
+
"total_pontos": int(total_pontos),
|
| 292 |
+
"limiar_png": LIMIAR_DISPERSAO_PNG,
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
session.graficos_dispersao_cache[key] = payload_interativo
|
| 296 |
+
return {
|
| 297 |
+
"modo": "png",
|
| 298 |
+
"grafico": None,
|
| 299 |
+
"grafico_png": payload_png,
|
| 300 |
+
"total_pontos": int(total_pontos),
|
| 301 |
+
"limiar_png": LIMIAR_DISPERSAO_PNG,
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
def _clean_int_list(values: list[Any] | None) -> list[int]:
|
| 306 |
if not values:
|
| 307 |
return []
|
|
|
|
| 958 |
fig_dispersao = charts.criar_graficos_dispersao(df_filtrado[colunas_x_validas], df_filtrado[coluna_y])
|
| 959 |
except Exception:
|
| 960 |
fig_dispersao = None
|
| 961 |
+
dispersao_payload = _montar_payload_dispersao(
|
| 962 |
+
session,
|
| 963 |
+
cache_key="secao10",
|
| 964 |
+
fig=fig_dispersao,
|
| 965 |
+
total_pontos=len(df_filtrado.index),
|
| 966 |
+
)
|
| 967 |
|
| 968 |
busca_payload = search_transformacoes(session, grau_min_coef=grau_min_coef, grau_min_f=grau_min_f)
|
| 969 |
|
|
|
|
| 995 |
return {
|
| 996 |
"estatisticas": dataframe_to_payload(estatisticas, decimals=4),
|
| 997 |
"micronumerosidade_html": micro_html,
|
| 998 |
+
"grafico_dispersao": dispersao_payload.get("grafico"),
|
| 999 |
+
"grafico_dispersao_modo": dispersao_payload.get("modo"),
|
| 1000 |
+
"grafico_dispersao_png": dispersao_payload.get("grafico_png"),
|
| 1001 |
+
"grafico_dispersao_total_pontos": dispersao_payload.get("total_pontos"),
|
| 1002 |
+
"grafico_dispersao_limiar_png": dispersao_payload.get("limiar_png"),
|
| 1003 |
"busca": busca_payload,
|
| 1004 |
"resumo_outliers": resumo_outliers,
|
| 1005 |
"outliers_html": outliers_html,
|
|
|
|
| 1192 |
)
|
| 1193 |
except Exception:
|
| 1194 |
fig_dispersao_transf = None
|
| 1195 |
+
total_pontos_modelo = int(getattr(resultado.get("X_transformado"), "shape", [0])[0] or 0)
|
| 1196 |
+
dispersao_modelo_payload = _montar_payload_dispersao(
|
| 1197 |
+
session,
|
| 1198 |
+
cache_key="secao13",
|
| 1199 |
+
fig=fig_dispersao_transf,
|
| 1200 |
+
total_pontos=total_pontos_modelo,
|
| 1201 |
+
)
|
| 1202 |
|
| 1203 |
fig_corr = None
|
| 1204 |
try:
|
|
|
|
| 1209 |
fig_corr = None
|
| 1210 |
|
| 1211 |
graficos = charts.criar_painel_diagnostico(resultado)
|
| 1212 |
+
usar_png_diagnosticos = _usar_png_dispersao(total_pontos_modelo)
|
| 1213 |
+
obs_calc_png = _renderizar_png_grafico(graficos.get("obs_calc")) if usar_png_diagnosticos else None
|
| 1214 |
+
residuos_png = _renderizar_png_grafico(graficos.get("residuos")) if usar_png_diagnosticos else None
|
| 1215 |
+
histograma_png = _renderizar_png_grafico(graficos.get("histograma")) if usar_png_diagnosticos else None
|
| 1216 |
+
cook_png = _renderizar_png_grafico(graficos.get("cook")) if usar_png_diagnosticos else None
|
| 1217 |
+
diagnosticos_cache_map = {
|
| 1218 |
+
"secao15_obs_calc": graficos.get("obs_calc"),
|
| 1219 |
+
"secao15_residuos": graficos.get("residuos"),
|
| 1220 |
+
"secao15_histograma": graficos.get("histograma"),
|
| 1221 |
+
"secao15_cook": graficos.get("cook"),
|
| 1222 |
+
}
|
| 1223 |
+
for cache_key, fig_diag in diagnosticos_cache_map.items():
|
| 1224 |
+
if usar_png_diagnosticos and fig_diag is not None:
|
| 1225 |
+
payload_diag = figure_to_payload(fig_diag)
|
| 1226 |
+
if payload_diag:
|
| 1227 |
+
session.graficos_dispersao_cache[cache_key] = payload_diag
|
| 1228 |
+
else:
|
| 1229 |
+
session.graficos_dispersao_cache.pop(cache_key, None)
|
| 1230 |
+
else:
|
| 1231 |
+
session.graficos_dispersao_cache.pop(cache_key, None)
|
| 1232 |
|
| 1233 |
tabela_metricas = resultado["tabela_obs_calc"].copy()
|
| 1234 |
tabela_metricas_estado = tabela_metricas.set_index("Índice")
|
|
|
|
| 1286 |
"equacoes": sanitize_value(equacoes),
|
| 1287 |
"tabela_coef": dataframe_to_payload(resultado["tabela_coef"], decimals=4),
|
| 1288 |
"tabela_obs_calc": dataframe_to_payload(resultado["tabela_obs_calc"], decimals=4),
|
| 1289 |
+
"grafico_dispersao_modelo": dispersao_modelo_payload.get("grafico"),
|
| 1290 |
+
"grafico_dispersao_modelo_modo": dispersao_modelo_payload.get("modo"),
|
| 1291 |
+
"grafico_dispersao_modelo_png": dispersao_modelo_payload.get("grafico_png"),
|
| 1292 |
+
"grafico_dispersao_modelo_total_pontos": dispersao_modelo_payload.get("total_pontos"),
|
| 1293 |
+
"grafico_dispersao_modelo_limiar_png": dispersao_modelo_payload.get("limiar_png"),
|
| 1294 |
+
"graficos_diagnostico_modo": "png" if usar_png_diagnosticos else "interativo",
|
| 1295 |
+
"graficos_diagnostico_total_pontos": total_pontos_modelo,
|
| 1296 |
+
"graficos_diagnostico_limiar_png": LIMIAR_DISPERSAO_PNG,
|
| 1297 |
+
"grafico_obs_calc": None if usar_png_diagnosticos else figure_to_payload(graficos.get("obs_calc")),
|
| 1298 |
+
"grafico_residuos": None if usar_png_diagnosticos else figure_to_payload(graficos.get("residuos")),
|
| 1299 |
+
"grafico_histograma": None if usar_png_diagnosticos else figure_to_payload(graficos.get("histograma")),
|
| 1300 |
+
"grafico_cook": None if usar_png_diagnosticos else figure_to_payload(graficos.get("cook")),
|
| 1301 |
+
"grafico_obs_calc_png": obs_calc_png,
|
| 1302 |
+
"grafico_residuos_png": residuos_png,
|
| 1303 |
+
"grafico_histograma_png": histograma_png,
|
| 1304 |
+
"grafico_cook_png": cook_png,
|
| 1305 |
"grafico_correlacao": figure_to_payload(fig_corr),
|
| 1306 |
"tabela_metricas": dataframe_to_payload(tabela_metricas, decimals=4),
|
| 1307 |
"tabela_outliers_excluidos": tabela_outliers_excluidos,
|
|
|
|
| 1421 |
fig = charts.criar_graficos_dispersao(x_base, y_base)
|
| 1422 |
except Exception:
|
| 1423 |
fig = None
|
| 1424 |
+
dispersao_payload = _montar_payload_dispersao(
|
| 1425 |
+
session,
|
| 1426 |
+
cache_key="secao13",
|
| 1427 |
+
fig=fig,
|
| 1428 |
+
total_pontos=int(getattr(x_base, "shape", [0])[0] or 0),
|
| 1429 |
+
)
|
| 1430 |
|
| 1431 |
return {
|
| 1432 |
+
"grafico": dispersao_payload.get("grafico"),
|
| 1433 |
+
"modo": dispersao_payload.get("modo"),
|
| 1434 |
+
"grafico_png": dispersao_payload.get("grafico_png"),
|
| 1435 |
+
"total_pontos": dispersao_payload.get("total_pontos"),
|
| 1436 |
+
"limiar_png": dispersao_payload.get("limiar_png"),
|
| 1437 |
"eixo_x_aplicado": "nao_transformado" if eixo_x_norm in {"nao_transformado", "não_transformado"} else "transformado",
|
| 1438 |
"eixo_y_tipo_aplicado": eixo_y_norm,
|
| 1439 |
"eixo_y_residuo_aplicado": eixo_residuo_norm if eixo_y_norm == "residuo" else None,
|
|
|
|
| 1442 |
}
|
| 1443 |
|
| 1444 |
|
| 1445 |
+
def obter_grafico_dispersao_interativo(session: SessionState, alvo: str) -> dict[str, Any]:
|
| 1446 |
+
key = str(alvo or "").strip().lower()
|
| 1447 |
+
if key not in {"secao10", "secao13"}:
|
| 1448 |
+
raise HTTPException(status_code=400, detail="Alvo de grafico invalido")
|
| 1449 |
+
|
| 1450 |
+
grafico = session.graficos_dispersao_cache.get(key)
|
| 1451 |
+
if not grafico:
|
| 1452 |
+
raise HTTPException(status_code=404, detail="Grafico interativo indisponivel para este alvo")
|
| 1453 |
+
|
| 1454 |
+
return {
|
| 1455 |
+
"alvo": key,
|
| 1456 |
+
"grafico": sanitize_value(grafico),
|
| 1457 |
+
}
|
| 1458 |
+
|
| 1459 |
+
|
| 1460 |
+
def obter_grafico_diagnostico_interativo(session: SessionState, grafico: str) -> dict[str, Any]:
|
| 1461 |
+
alvo = str(grafico or "").strip().lower()
|
| 1462 |
+
mapa_alvos = {
|
| 1463 |
+
"obs_calc": "secao15_obs_calc",
|
| 1464 |
+
"residuos": "secao15_residuos",
|
| 1465 |
+
"histograma": "secao15_histograma",
|
| 1466 |
+
"cook": "secao15_cook",
|
| 1467 |
+
}
|
| 1468 |
+
key = mapa_alvos.get(alvo)
|
| 1469 |
+
if not key:
|
| 1470 |
+
raise HTTPException(status_code=400, detail="Grafico diagnostico invalido")
|
| 1471 |
+
|
| 1472 |
+
payload = session.graficos_dispersao_cache.get(key)
|
| 1473 |
+
if not payload:
|
| 1474 |
+
raise HTTPException(status_code=404, detail="Grafico diagnostico interativo indisponivel")
|
| 1475 |
+
|
| 1476 |
+
return {
|
| 1477 |
+
"grafico": alvo,
|
| 1478 |
+
"payload": sanitize_value(payload),
|
| 1479 |
+
}
|
| 1480 |
+
|
| 1481 |
+
|
| 1482 |
def apply_outlier_filters(session: SessionState, filtros: list[dict[str, Any]]) -> dict[str, Any]:
|
| 1483 |
metricas = session.tabela_metricas_estado
|
| 1484 |
if metricas is None:
|
backend/requirements.txt
CHANGED
|
@@ -7,6 +7,7 @@ numpy
|
|
| 7 |
statsmodels
|
| 8 |
scipy
|
| 9 |
plotly
|
|
|
|
| 10 |
folium
|
| 11 |
branca
|
| 12 |
joblib
|
|
|
|
| 7 |
statsmodels
|
| 8 |
scipy
|
| 9 |
plotly
|
| 10 |
+
kaleido
|
| 11 |
folium
|
| 12 |
branca
|
| 13 |
joblib
|
frontend/src/api.js
CHANGED
|
@@ -161,6 +161,14 @@ export const api = {
|
|
| 161 |
session_id: sessionId,
|
| 162 |
...(payload || {}),
|
| 163 |
}),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
previewTransformElab: (sessionId, transformacaoY, transformacoesX) => postJson('/api/elaboracao/transform-preview', {
|
| 165 |
session_id: sessionId,
|
| 166 |
transformacao_y: transformacaoY,
|
|
|
|
| 161 |
session_id: sessionId,
|
| 162 |
...(payload || {}),
|
| 163 |
}),
|
| 164 |
+
getDispersaoInterativo: (sessionId, alvo) => postJson('/api/elaboracao/dispersao-interativo', {
|
| 165 |
+
session_id: sessionId,
|
| 166 |
+
alvo,
|
| 167 |
+
}),
|
| 168 |
+
getDiagnosticoInterativo: (sessionId, grafico) => postJson('/api/elaboracao/diagnostico-interativo', {
|
| 169 |
+
session_id: sessionId,
|
| 170 |
+
grafico,
|
| 171 |
+
}),
|
| 172 |
previewTransformElab: (sessionId, transformacaoY, transformacoesX) => postJson('/api/elaboracao/transform-preview', {
|
| 173 |
session_id: sessionId,
|
| 174 |
transformacao_y: transformacaoY,
|
frontend/src/components/ElaboracaoTab.jsx
CHANGED
|
@@ -371,7 +371,8 @@ function stripPanelLayoutAxes(layout) {
|
|
| 371 |
return out
|
| 372 |
}
|
| 373 |
|
| 374 |
-
function stylePanelTrace(trace) {
|
|
|
|
| 375 |
const mode = String(trace?.mode || '')
|
| 376 |
const traceName = String(trace?.name || '').toLowerCase()
|
| 377 |
const hasMarkers = mode.includes('markers')
|
|
@@ -379,6 +380,9 @@ function stylePanelTrace(trace) {
|
|
| 379 |
const next = { ...trace, showlegend: false }
|
| 380 |
|
| 381 |
if (hasMarkers) {
|
|
|
|
|
|
|
|
|
|
| 382 |
next.marker = {
|
| 383 |
...(trace?.marker || {}),
|
| 384 |
color: '#FF8C00',
|
|
@@ -446,7 +450,7 @@ function buildScatterPanels(figure, options = {}) {
|
|
| 446 |
...item.trace,
|
| 447 |
xaxis: 'x',
|
| 448 |
yaxis: 'y',
|
| 449 |
-
}))
|
| 450 |
|
| 451 |
const panelFigure = {
|
| 452 |
data: panelTraces,
|
|
@@ -637,6 +641,27 @@ function obterLabelGrau(listaGraus, valor) {
|
|
| 637 |
return item?.label || 'Sem enquadramento'
|
| 638 |
}
|
| 639 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 640 |
export default function ElaboracaoTab({ sessionId }) {
|
| 641 |
const [loading, setLoading] = useState(false)
|
| 642 |
const [downloadingAssets, setDownloadingAssets] = useState(false)
|
|
@@ -720,6 +745,12 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 720 |
const [dispersaoEixoYTipo, setDispersaoEixoYTipo] = useState('y_transformado')
|
| 721 |
const [dispersaoEixoYResiduo, setDispersaoEixoYResiduo] = useState('residuo_pad')
|
| 722 |
const [dispersaoEixoYColuna, setDispersaoEixoYColuna] = useState('')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
|
| 724 |
const [filtros, setFiltros] = useState(defaultFiltros())
|
| 725 |
const [outliersTexto, setOutliersTexto] = useState('')
|
|
@@ -970,6 +1001,19 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 970 |
}
|
| 971 |
return ''
|
| 972 |
}, [origemTransformacoes])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 973 |
const graficosSecao9 = useMemo(
|
| 974 |
() => buildScatterPanels(selection?.grafico_dispersao, {
|
| 975 |
singleLabel: 'Dispersão',
|
|
@@ -978,6 +1022,25 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 978 |
}),
|
| 979 |
[selection?.grafico_dispersao, colunaY],
|
| 980 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 981 |
const colunasGraficosSecao9 = useMemo(
|
| 982 |
() => Math.max(1, Math.min(3, graficosSecao9.length || 1)),
|
| 983 |
[graficosSecao9.length],
|
|
@@ -1014,10 +1077,60 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1014 |
}),
|
| 1015 |
[fit?.grafico_dispersao_modelo, yLabelSecao13],
|
| 1016 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1017 |
const colunasGraficosSecao12 = useMemo(
|
| 1018 |
() => Math.max(1, Math.min(3, graficosSecao12.length || 1)),
|
| 1019 |
[graficosSecao12.length],
|
| 1020 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1021 |
const temAvaliacoes = useMemo(
|
| 1022 |
() => Array.isArray(baseChoices) && baseChoices.length > 0,
|
| 1023 |
[baseChoices],
|
|
@@ -1130,6 +1243,33 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1130 |
}
|
| 1131 |
}, [colunasOriginaisDispersao, dispersaoEixoYColuna])
|
| 1132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1133 |
useEffect(() => {
|
| 1134 |
if (!sessionId || tipoFonteDados === 'dai') return undefined
|
| 1135 |
|
|
@@ -1409,6 +1549,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1409 |
setSelection(null)
|
| 1410 |
setSection6EditOpen(true)
|
| 1411 |
setFit(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
setFiltros(defaultFiltros())
|
| 1413 |
setOutliersTexto('')
|
| 1414 |
setReincluirTexto('')
|
|
@@ -1444,6 +1588,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1444 |
|
| 1445 |
function applySelectionResponse(resp) {
|
| 1446 |
setSelection(resp)
|
|
|
|
|
|
|
| 1447 |
setSection6EditOpen(false)
|
| 1448 |
setSection11LocksOpen(false)
|
| 1449 |
setSection10ManualOpen(false)
|
|
@@ -1493,6 +1639,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1493 |
|
| 1494 |
function applyFitResponse(resp, origemMeta = null) {
|
| 1495 |
setFit(resp)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1496 |
setDispersaoEixoX('transformado')
|
| 1497 |
setDispersaoEixoYTipo('y_transformado')
|
| 1498 |
setDispersaoEixoYResiduo('residuo_pad')
|
|
@@ -1585,6 +1735,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1585 |
setGrauCoef(0)
|
| 1586 |
setGrauF(0)
|
| 1587 |
setFit(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1588 |
setSelectionAppliedSnapshot(buildSelectionSnapshot())
|
| 1589 |
setColunasX([])
|
| 1590 |
setDicotomicas([])
|
|
@@ -1819,6 +1973,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1819 |
setSelectionAppliedSnapshot(buildSelectionSnapshot({ coluna_y: proximaColunaY }))
|
| 1820 |
setSelection(null)
|
| 1821 |
setFit(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1822 |
setTransformacaoY('(x)')
|
| 1823 |
setTransformacoesX({})
|
| 1824 |
setTransformacaoYFixaBusca('Livre')
|
|
@@ -1980,6 +2138,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1980 |
setSelectionAppliedSnapshot(buildSelectionSnapshot(payload))
|
| 1981 |
setSection6EditOpen(false)
|
| 1982 |
setFit(null)
|
|
|
|
|
|
|
| 1983 |
setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
|
| 1984 |
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 1985 |
setCamposAvaliacao([])
|
|
@@ -2082,7 +2242,16 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2082 |
eixo_y_residuo: eixoYTipo === 'residuo' ? eixoYResiduo : null,
|
| 2083 |
eixo_y_coluna: eixoYTipo === 'coluna_original' ? eixoYColuna : null,
|
| 2084 |
})
|
| 2085 |
-
setFit((prev) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2086 |
})
|
| 2087 |
}
|
| 2088 |
|
|
@@ -2204,6 +2373,8 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2204 |
setReincluirTexto('')
|
| 2205 |
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 2206 |
setFit(null)
|
|
|
|
|
|
|
| 2207 |
setTransformacoesAplicadas(null)
|
| 2208 |
setOrigemTransformacoes(null)
|
| 2209 |
setSection10ManualOpen(true)
|
|
@@ -2230,6 +2401,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2230 |
setSelection(null)
|
| 2231 |
setSection6EditOpen(true)
|
| 2232 |
setFit(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2233 |
setTransformacoesAplicadas(null)
|
| 2234 |
setOrigemTransformacoes(null)
|
| 2235 |
setCamposAvaliacao([])
|
|
@@ -2567,6 +2742,97 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 2567 |
}
|
| 2568 |
}
|
| 2569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2570 |
function toggleSelection(setter, value) {
|
| 2571 |
setter((prev) => {
|
| 2572 |
if (prev.includes(value)) return prev.filter((item) => item !== value)
|
|
@@ -3457,56 +3723,135 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3457 |
</SectionBlock>
|
| 3458 |
|
| 3459 |
<SectionBlock step="10" title="Gráficos de Dispersão das Variáveis Independentes" subtitle="Leitura visual entre X e Y no conjunto filtrado.">
|
| 3460 |
-
|
| 3461 |
-
|
| 3462 |
-
|
| 3463 |
-
|
| 3464 |
-
|
|
|
|
|
|
|
|
|
|
| 3465 |
<button
|
| 3466 |
-
key={`s9-dl-${item.id}`}
|
| 3467 |
type="button"
|
| 3468 |
className="btn-download-subtle"
|
| 3469 |
-
|
| 3470 |
-
|
| 3471 |
-
disabled={loading || downloadingAssets || !item.figure}
|
| 3472 |
>
|
| 3473 |
-
|
| 3474 |
</button>
|
| 3475 |
-
|
| 3476 |
-
|
| 3477 |
-
|
| 3478 |
-
|
| 3479 |
-
|
| 3480 |
-
|
| 3481 |
-
onClick={() => onDownloadFiguresPngBatch(
|
| 3482 |
-
graficosSecao9.map((item, idx) => ({
|
| 3483 |
-
figure: item.figure,
|
| 3484 |
-
fileNameBase: `secao10_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`,
|
| 3485 |
-
forceHideLegend: true,
|
| 3486 |
-
})),
|
| 3487 |
-
)}
|
| 3488 |
-
disabled={loading || downloadingAssets || graficosSecao9.length === 0}
|
| 3489 |
-
>
|
| 3490 |
-
Todos
|
| 3491 |
-
</button>
|
| 3492 |
-
) : null}
|
| 3493 |
-
</div>
|
| 3494 |
-
{graficosSecao9.length > 0 ? (
|
| 3495 |
-
<div className="plot-grid-scatter" style={{ '--plot-cols': colunasGraficosSecao9 }}>
|
| 3496 |
-
{graficosSecao9.map((item) => (
|
| 3497 |
-
<PlotFigure
|
| 3498 |
-
key={`s9-plot-${item.id}`}
|
| 3499 |
-
figure={item.figure}
|
| 3500 |
-
title={item.title}
|
| 3501 |
-
subtitle={item.subtitle}
|
| 3502 |
-
forceHideLegend
|
| 3503 |
-
className="plot-stretch"
|
| 3504 |
-
lazy
|
| 3505 |
/>
|
| 3506 |
-
|
| 3507 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3508 |
) : (
|
| 3509 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3510 |
)}
|
| 3511 |
</SectionBlock>
|
| 3512 |
|
|
@@ -3740,6 +4085,11 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3740 |
{fit ? (
|
| 3741 |
<>
|
| 3742 |
<SectionBlock step="13" title="Visualizar Mapa dos Dados de Mercado" subtitle="Escolha livre dos eixos para análise gráfica do modelo.">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3743 |
<div className="row dispersao-config-row">
|
| 3744 |
<div className="dispersao-config-field">
|
| 3745 |
<label>Eixo X</label>
|
|
@@ -3810,56 +4160,148 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3810 |
</div>
|
| 3811 |
) : null}
|
| 3812 |
</div>
|
| 3813 |
-
|
| 3814 |
-
|
| 3815 |
-
|
| 3816 |
-
const fileBase = `secao13_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`
|
| 3817 |
-
return (
|
| 3818 |
<button
|
| 3819 |
-
key={`s12-dl-${item.id}`}
|
| 3820 |
type="button"
|
| 3821 |
className="btn-download-subtle"
|
| 3822 |
-
|
| 3823 |
-
|
| 3824 |
-
disabled={loading || downloadingAssets || !item.figure}
|
| 3825 |
>
|
| 3826 |
-
|
| 3827 |
</button>
|
| 3828 |
-
|
| 3829 |
-
|
| 3830 |
-
|
| 3831 |
-
|
| 3832 |
-
|
| 3833 |
-
|
| 3834 |
-
onClick={() => onDownloadFiguresPngBatch(
|
| 3835 |
-
graficosSecao12.map((item, idx) => ({
|
| 3836 |
-
figure: item.figure,
|
| 3837 |
-
fileNameBase: `secao13_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`,
|
| 3838 |
-
forceHideLegend: true,
|
| 3839 |
-
})),
|
| 3840 |
-
)}
|
| 3841 |
-
disabled={loading || downloadingAssets || graficosSecao12.length === 0}
|
| 3842 |
-
>
|
| 3843 |
-
Todos
|
| 3844 |
-
</button>
|
| 3845 |
-
) : null}
|
| 3846 |
-
</div>
|
| 3847 |
-
{graficosSecao12.length > 0 ? (
|
| 3848 |
-
<div className="plot-grid-scatter" style={{ '--plot-cols': colunasGraficosSecao12 }}>
|
| 3849 |
-
{graficosSecao12.map((item) => (
|
| 3850 |
-
<PlotFigure
|
| 3851 |
-
key={`s12-plot-${item.id}`}
|
| 3852 |
-
figure={item.figure}
|
| 3853 |
-
title={item.title}
|
| 3854 |
-
subtitle={item.subtitle}
|
| 3855 |
-
forceHideLegend
|
| 3856 |
-
className="plot-stretch"
|
| 3857 |
-
lazy
|
| 3858 |
/>
|
| 3859 |
-
|
| 3860 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3861 |
) : (
|
| 3862 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3863 |
)}
|
| 3864 |
</SectionBlock>
|
| 3865 |
|
|
@@ -3916,37 +4358,58 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3916 |
</SectionBlock>
|
| 3917 |
|
| 3918 |
<SectionBlock step="15" title="Gráficos de Diagnóstico do Modelo" subtitle="Obs x calc, resíduos, histograma, Cook e correlação.">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3919 |
<div className="download-actions-bar">
|
| 3920 |
<span className="download-actions-label">Fazer download:</span>
|
| 3921 |
<button
|
| 3922 |
type="button"
|
| 3923 |
className="btn-download-subtle"
|
| 3924 |
-
onClick={() =>
|
| 3925 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3926 |
>
|
| 3927 |
Obs x calc
|
| 3928 |
</button>
|
| 3929 |
<button
|
| 3930 |
type="button"
|
| 3931 |
className="btn-download-subtle"
|
| 3932 |
-
onClick={() =>
|
| 3933 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3934 |
>
|
| 3935 |
Resíduos
|
| 3936 |
</button>
|
| 3937 |
<button
|
| 3938 |
type="button"
|
| 3939 |
className="btn-download-subtle"
|
| 3940 |
-
onClick={() =>
|
| 3941 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3942 |
>
|
| 3943 |
Histograma
|
| 3944 |
</button>
|
| 3945 |
<button
|
| 3946 |
type="button"
|
| 3947 |
className="btn-download-subtle"
|
| 3948 |
-
onClick={() =>
|
| 3949 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3950 |
>
|
| 3951 |
Cook
|
| 3952 |
</button>
|
|
@@ -3961,27 +4424,102 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 3961 |
<button
|
| 3962 |
type="button"
|
| 3963 |
className="btn-download-subtle"
|
| 3964 |
-
onClick={() =>
|
| 3965 |
-
|
| 3966 |
-
|
| 3967 |
-
|
| 3968 |
-
|
| 3969 |
-
|
| 3970 |
-
|
| 3971 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3972 |
>
|
| 3973 |
Todos
|
| 3974 |
</button>
|
| 3975 |
</div>
|
| 3976 |
-
|
| 3977 |
-
<
|
| 3978 |
-
|
| 3979 |
-
|
| 3980 |
-
|
| 3981 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3982 |
<div className="plot-full-width">
|
| 3983 |
<PlotFigure figure={fit.grafico_correlacao} title="Matriz de correlação" className="plot-correlation-card" />
|
| 3984 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3985 |
</SectionBlock>
|
| 3986 |
|
| 3987 |
<SectionBlock step="16" title="Analisar Resíduos" subtitle="Métricas para identificação de observações influentes.">
|
|
|
|
| 371 |
return out
|
| 372 |
}
|
| 373 |
|
| 374 |
+
function stylePanelTrace(trace, options = {}) {
|
| 375 |
+
const useScatterGlMarkers = Boolean(options.useScatterGlMarkers)
|
| 376 |
const mode = String(trace?.mode || '')
|
| 377 |
const traceName = String(trace?.name || '').toLowerCase()
|
| 378 |
const hasMarkers = mode.includes('markers')
|
|
|
|
| 380 |
const next = { ...trace, showlegend: false }
|
| 381 |
|
| 382 |
if (hasMarkers) {
|
| 383 |
+
if (useScatterGlMarkers) {
|
| 384 |
+
next.type = 'scattergl'
|
| 385 |
+
}
|
| 386 |
next.marker = {
|
| 387 |
...(trace?.marker || {}),
|
| 388 |
color: '#FF8C00',
|
|
|
|
| 450 |
...item.trace,
|
| 451 |
xaxis: 'x',
|
| 452 |
yaxis: 'y',
|
| 453 |
+
}, { useScatterGlMarkers: Boolean(options.useScatterGlMarkers) }))
|
| 454 |
|
| 455 |
const panelFigure = {
|
| 456 |
data: panelTraces,
|
|
|
|
| 641 |
return item?.label || 'Sem enquadramento'
|
| 642 |
}
|
| 643 |
|
| 644 |
+
function DiagnosticPngCard({ title, pngPayload, alt }) {
|
| 645 |
+
if (!pngPayload?.image_base64) {
|
| 646 |
+
return (
|
| 647 |
+
<div className="empty-box">Grafico indisponivel.</div>
|
| 648 |
+
)
|
| 649 |
+
}
|
| 650 |
+
const mime = String(pngPayload.mime_type || 'image/png').trim() || 'image/png'
|
| 651 |
+
return (
|
| 652 |
+
<div className="plot-card plot-png-card">
|
| 653 |
+
<div className="plot-card-head">
|
| 654 |
+
<h4 className="plot-card-title">{title}</h4>
|
| 655 |
+
</div>
|
| 656 |
+
<img
|
| 657 |
+
src={`data:${mime};base64,${pngPayload.image_base64}`}
|
| 658 |
+
alt={alt || title}
|
| 659 |
+
className="plot-png-image"
|
| 660 |
+
/>
|
| 661 |
+
</div>
|
| 662 |
+
)
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
export default function ElaboracaoTab({ sessionId }) {
|
| 666 |
const [loading, setLoading] = useState(false)
|
| 667 |
const [downloadingAssets, setDownloadingAssets] = useState(false)
|
|
|
|
| 745 |
const [dispersaoEixoYTipo, setDispersaoEixoYTipo] = useState('y_transformado')
|
| 746 |
const [dispersaoEixoYResiduo, setDispersaoEixoYResiduo] = useState('residuo_pad')
|
| 747 |
const [dispersaoEixoYColuna, setDispersaoEixoYColuna] = useState('')
|
| 748 |
+
const [secao10InterativoFigura, setSecao10InterativoFigura] = useState(null)
|
| 749 |
+
const [secao10InterativoSelecionado, setSecao10InterativoSelecionado] = useState('none')
|
| 750 |
+
const [secao13InterativoFigura, setSecao13InterativoFigura] = useState(null)
|
| 751 |
+
const [secao13InterativoSelecionado, setSecao13InterativoSelecionado] = useState('none')
|
| 752 |
+
const [secao15InterativoFigura, setSecao15InterativoFigura] = useState(null)
|
| 753 |
+
const [secao15InterativoSelecionado, setSecao15InterativoSelecionado] = useState('none')
|
| 754 |
|
| 755 |
const [filtros, setFiltros] = useState(defaultFiltros())
|
| 756 |
const [outliersTexto, setOutliersTexto] = useState('')
|
|
|
|
| 1001 |
}
|
| 1002 |
return ''
|
| 1003 |
}, [origemTransformacoes])
|
| 1004 |
+
const secao10PngPayload = useMemo(() => {
|
| 1005 |
+
if (String(selection?.grafico_dispersao_modo || '') !== 'png') return null
|
| 1006 |
+
const payload = selection?.grafico_dispersao_png
|
| 1007 |
+
if (!payload || typeof payload !== 'object') return null
|
| 1008 |
+
const imageBase64 = String(payload.image_base64 || '').trim()
|
| 1009 |
+
if (!imageBase64) return null
|
| 1010 |
+
return {
|
| 1011 |
+
imageBase64,
|
| 1012 |
+
mimeType: String(payload.mime_type || 'image/png').trim() || 'image/png',
|
| 1013 |
+
totalPontos: Number(payload.total_pontos ?? selection?.grafico_dispersao_total_pontos ?? 0) || 0,
|
| 1014 |
+
limiar: Number(selection?.grafico_dispersao_limiar_png ?? 1000) || 1000,
|
| 1015 |
+
}
|
| 1016 |
+
}, [selection])
|
| 1017 |
const graficosSecao9 = useMemo(
|
| 1018 |
() => buildScatterPanels(selection?.grafico_dispersao, {
|
| 1019 |
singleLabel: 'Dispersão',
|
|
|
|
| 1022 |
}),
|
| 1023 |
[selection?.grafico_dispersao, colunaY],
|
| 1024 |
)
|
| 1025 |
+
const graficosSecao9Interativo = useMemo(
|
| 1026 |
+
() => buildScatterPanels(secao10InterativoFigura, {
|
| 1027 |
+
singleLabel: 'Dispersão',
|
| 1028 |
+
height: 360,
|
| 1029 |
+
yLabel: colunaY || 'Y',
|
| 1030 |
+
useScatterGlMarkers: true,
|
| 1031 |
+
}),
|
| 1032 |
+
[secao10InterativoFigura, colunaY],
|
| 1033 |
+
)
|
| 1034 |
+
const secao10InterativoOpcoes = useMemo(() => {
|
| 1035 |
+
const labels = graficosSecao9Interativo.length > 0
|
| 1036 |
+
? graficosSecao9Interativo.map((item) => String(item.label || '').trim()).filter(Boolean)
|
| 1037 |
+
: (selection?.contexto?.colunas_x || colunasX).map((item) => String(item || '').trim()).filter(Boolean)
|
| 1038 |
+
return Array.from(new Set(labels))
|
| 1039 |
+
}, [graficosSecao9Interativo, selection?.contexto?.colunas_x, colunasX])
|
| 1040 |
+
const secao10InterativoAtual = useMemo(() => {
|
| 1041 |
+
if (secao10InterativoSelecionado === 'none' || graficosSecao9Interativo.length === 0) return null
|
| 1042 |
+
return graficosSecao9Interativo.find((item) => String(item.label || '') === secao10InterativoSelecionado) || graficosSecao9Interativo[0]
|
| 1043 |
+
}, [graficosSecao9Interativo, secao10InterativoSelecionado])
|
| 1044 |
const colunasGraficosSecao9 = useMemo(
|
| 1045 |
() => Math.max(1, Math.min(3, graficosSecao9.length || 1)),
|
| 1046 |
[graficosSecao9.length],
|
|
|
|
| 1077 |
}),
|
| 1078 |
[fit?.grafico_dispersao_modelo, yLabelSecao13],
|
| 1079 |
)
|
| 1080 |
+
const secao13PngPayload = useMemo(() => {
|
| 1081 |
+
if (String(fit?.grafico_dispersao_modelo_modo || '') !== 'png') return null
|
| 1082 |
+
const payload = fit?.grafico_dispersao_modelo_png
|
| 1083 |
+
if (!payload || typeof payload !== 'object') return null
|
| 1084 |
+
const imageBase64 = String(payload.image_base64 || '').trim()
|
| 1085 |
+
if (!imageBase64) return null
|
| 1086 |
+
return {
|
| 1087 |
+
imageBase64,
|
| 1088 |
+
mimeType: String(payload.mime_type || 'image/png').trim() || 'image/png',
|
| 1089 |
+
totalPontos: Number(payload.total_pontos ?? fit?.grafico_dispersao_modelo_total_pontos ?? 0) || 0,
|
| 1090 |
+
limiar: Number(fit?.grafico_dispersao_modelo_limiar_png ?? 1000) || 1000,
|
| 1091 |
+
}
|
| 1092 |
+
}, [fit])
|
| 1093 |
+
const graficosSecao12Interativo = useMemo(
|
| 1094 |
+
() => buildScatterPanels(secao13InterativoFigura, {
|
| 1095 |
+
singleLabel: 'Dispersão do modelo',
|
| 1096 |
+
height: 360,
|
| 1097 |
+
yLabel: yLabelSecao13,
|
| 1098 |
+
useScatterGlMarkers: true,
|
| 1099 |
+
}),
|
| 1100 |
+
[secao13InterativoFigura, yLabelSecao13],
|
| 1101 |
+
)
|
| 1102 |
+
const secao13InterativoOpcoes = useMemo(() => {
|
| 1103 |
+
const labels = graficosSecao12Interativo.length > 0
|
| 1104 |
+
? graficosSecao12Interativo.map((item) => String(item.label || '').trim()).filter(Boolean)
|
| 1105 |
+
: (fit?.contexto?.colunas_x || colunasX).map((item) => String(item || '').trim()).filter(Boolean)
|
| 1106 |
+
return Array.from(new Set(labels))
|
| 1107 |
+
}, [graficosSecao12Interativo, fit?.contexto?.colunas_x, colunasX])
|
| 1108 |
+
const secao13InterativoAtual = useMemo(() => {
|
| 1109 |
+
if (secao13InterativoSelecionado === 'none' || graficosSecao12Interativo.length === 0) return null
|
| 1110 |
+
return graficosSecao12Interativo.find((item) => String(item.label || '') === secao13InterativoSelecionado) || graficosSecao12Interativo[0]
|
| 1111 |
+
}, [graficosSecao12Interativo, secao13InterativoSelecionado])
|
| 1112 |
const colunasGraficosSecao12 = useMemo(
|
| 1113 |
() => Math.max(1, Math.min(3, graficosSecao12.length || 1)),
|
| 1114 |
[graficosSecao12.length],
|
| 1115 |
)
|
| 1116 |
+
const secao15DiagnosticoPng = useMemo(
|
| 1117 |
+
() => String(fit?.graficos_diagnostico_modo || '') === 'png',
|
| 1118 |
+
[fit?.graficos_diagnostico_modo],
|
| 1119 |
+
)
|
| 1120 |
+
const secao15InterativoOpcoes = useMemo(
|
| 1121 |
+
() => [
|
| 1122 |
+
{ value: 'none', label: 'Sem gráfico interativo' },
|
| 1123 |
+
{ value: 'obs_calc', label: 'Obs x Calc' },
|
| 1124 |
+
{ value: 'residuos', label: 'Resíduos' },
|
| 1125 |
+
{ value: 'histograma', label: 'Histograma' },
|
| 1126 |
+
{ value: 'cook', label: 'Cook' },
|
| 1127 |
+
],
|
| 1128 |
+
[],
|
| 1129 |
+
)
|
| 1130 |
+
const secao15InterativoLabel = useMemo(() => {
|
| 1131 |
+
const item = secao15InterativoOpcoes.find((opt) => opt.value === secao15InterativoSelecionado)
|
| 1132 |
+
return item?.label || 'Gráfico interativo'
|
| 1133 |
+
}, [secao15InterativoOpcoes, secao15InterativoSelecionado])
|
| 1134 |
const temAvaliacoes = useMemo(
|
| 1135 |
() => Array.isArray(baseChoices) && baseChoices.length > 0,
|
| 1136 |
[baseChoices],
|
|
|
|
| 1243 |
}
|
| 1244 |
}, [colunasOriginaisDispersao, dispersaoEixoYColuna])
|
| 1245 |
|
| 1246 |
+
useEffect(() => {
|
| 1247 |
+
if (graficosSecao9Interativo.length === 0) {
|
| 1248 |
+
if (secao10InterativoSelecionado !== 'none') setSecao10InterativoSelecionado('none')
|
| 1249 |
+
return
|
| 1250 |
+
}
|
| 1251 |
+
if (secao10InterativoSelecionado !== 'none' && !graficosSecao9Interativo.some((item) => String(item.label || '') === secao10InterativoSelecionado)) {
|
| 1252 |
+
setSecao10InterativoSelecionado(String(graficosSecao9Interativo[0].label || 'none'))
|
| 1253 |
+
}
|
| 1254 |
+
}, [graficosSecao9Interativo, secao10InterativoSelecionado])
|
| 1255 |
+
|
| 1256 |
+
useEffect(() => {
|
| 1257 |
+
if (graficosSecao12Interativo.length === 0) {
|
| 1258 |
+
if (secao13InterativoSelecionado !== 'none') setSecao13InterativoSelecionado('none')
|
| 1259 |
+
return
|
| 1260 |
+
}
|
| 1261 |
+
if (secao13InterativoSelecionado !== 'none' && !graficosSecao12Interativo.some((item) => String(item.label || '') === secao13InterativoSelecionado)) {
|
| 1262 |
+
setSecao13InterativoSelecionado(String(graficosSecao12Interativo[0].label || 'none'))
|
| 1263 |
+
}
|
| 1264 |
+
}, [graficosSecao12Interativo, secao13InterativoSelecionado])
|
| 1265 |
+
|
| 1266 |
+
useEffect(() => {
|
| 1267 |
+
if (!fit || !secao15DiagnosticoPng) {
|
| 1268 |
+
if (secao15InterativoSelecionado !== 'none') setSecao15InterativoSelecionado('none')
|
| 1269 |
+
if (secao15InterativoFigura) setSecao15InterativoFigura(null)
|
| 1270 |
+
}
|
| 1271 |
+
}, [fit, secao15DiagnosticoPng, secao15InterativoSelecionado, secao15InterativoFigura])
|
| 1272 |
+
|
| 1273 |
useEffect(() => {
|
| 1274 |
if (!sessionId || tipoFonteDados === 'dai') return undefined
|
| 1275 |
|
|
|
|
| 1549 |
setSelection(null)
|
| 1550 |
setSection6EditOpen(true)
|
| 1551 |
setFit(null)
|
| 1552 |
+
setSecao10InterativoFigura(null)
|
| 1553 |
+
setSecao10InterativoSelecionado('none')
|
| 1554 |
+
setSecao13InterativoFigura(null)
|
| 1555 |
+
setSecao13InterativoSelecionado('none')
|
| 1556 |
setFiltros(defaultFiltros())
|
| 1557 |
setOutliersTexto('')
|
| 1558 |
setReincluirTexto('')
|
|
|
|
| 1588 |
|
| 1589 |
function applySelectionResponse(resp) {
|
| 1590 |
setSelection(resp)
|
| 1591 |
+
setSecao10InterativoFigura(null)
|
| 1592 |
+
setSecao10InterativoSelecionado('none')
|
| 1593 |
setSection6EditOpen(false)
|
| 1594 |
setSection11LocksOpen(false)
|
| 1595 |
setSection10ManualOpen(false)
|
|
|
|
| 1639 |
|
| 1640 |
function applyFitResponse(resp, origemMeta = null) {
|
| 1641 |
setFit(resp)
|
| 1642 |
+
setSecao13InterativoFigura(null)
|
| 1643 |
+
setSecao13InterativoSelecionado('none')
|
| 1644 |
+
setSecao15InterativoFigura(null)
|
| 1645 |
+
setSecao15InterativoSelecionado('none')
|
| 1646 |
setDispersaoEixoX('transformado')
|
| 1647 |
setDispersaoEixoYTipo('y_transformado')
|
| 1648 |
setDispersaoEixoYResiduo('residuo_pad')
|
|
|
|
| 1735 |
setGrauCoef(0)
|
| 1736 |
setGrauF(0)
|
| 1737 |
setFit(null)
|
| 1738 |
+
setSecao10InterativoFigura(null)
|
| 1739 |
+
setSecao10InterativoSelecionado('none')
|
| 1740 |
+
setSecao13InterativoFigura(null)
|
| 1741 |
+
setSecao13InterativoSelecionado('none')
|
| 1742 |
setSelectionAppliedSnapshot(buildSelectionSnapshot())
|
| 1743 |
setColunasX([])
|
| 1744 |
setDicotomicas([])
|
|
|
|
| 1973 |
setSelectionAppliedSnapshot(buildSelectionSnapshot({ coluna_y: proximaColunaY }))
|
| 1974 |
setSelection(null)
|
| 1975 |
setFit(null)
|
| 1976 |
+
setSecao10InterativoFigura(null)
|
| 1977 |
+
setSecao10InterativoSelecionado('none')
|
| 1978 |
+
setSecao13InterativoFigura(null)
|
| 1979 |
+
setSecao13InterativoSelecionado('none')
|
| 1980 |
setTransformacaoY('(x)')
|
| 1981 |
setTransformacoesX({})
|
| 1982 |
setTransformacaoYFixaBusca('Livre')
|
|
|
|
| 2138 |
setSelectionAppliedSnapshot(buildSelectionSnapshot(payload))
|
| 2139 |
setSection6EditOpen(false)
|
| 2140 |
setFit(null)
|
| 2141 |
+
setSecao13InterativoFigura(null)
|
| 2142 |
+
setSecao13InterativoSelecionado('none')
|
| 2143 |
setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
|
| 2144 |
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 2145 |
setCamposAvaliacao([])
|
|
|
|
| 2242 |
eixo_y_residuo: eixoYTipo === 'residuo' ? eixoYResiduo : null,
|
| 2243 |
eixo_y_coluna: eixoYTipo === 'coluna_original' ? eixoYColuna : null,
|
| 2244 |
})
|
| 2245 |
+
setFit((prev) => ({
|
| 2246 |
+
...prev,
|
| 2247 |
+
grafico_dispersao_modelo: resp.grafico,
|
| 2248 |
+
grafico_dispersao_modelo_modo: resp.modo || (resp.grafico ? 'interativo' : ''),
|
| 2249 |
+
grafico_dispersao_modelo_png: resp.grafico_png || null,
|
| 2250 |
+
grafico_dispersao_modelo_total_pontos: resp.total_pontos,
|
| 2251 |
+
grafico_dispersao_modelo_limiar_png: resp.limiar_png,
|
| 2252 |
+
}))
|
| 2253 |
+
setSecao13InterativoFigura(null)
|
| 2254 |
+
setSecao13InterativoSelecionado('none')
|
| 2255 |
})
|
| 2256 |
}
|
| 2257 |
|
|
|
|
| 2373 |
setReincluirTexto('')
|
| 2374 |
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 2375 |
setFit(null)
|
| 2376 |
+
setSecao13InterativoFigura(null)
|
| 2377 |
+
setSecao13InterativoSelecionado('none')
|
| 2378 |
setTransformacoesAplicadas(null)
|
| 2379 |
setOrigemTransformacoes(null)
|
| 2380 |
setSection10ManualOpen(true)
|
|
|
|
| 2401 |
setSelection(null)
|
| 2402 |
setSection6EditOpen(true)
|
| 2403 |
setFit(null)
|
| 2404 |
+
setSecao10InterativoFigura(null)
|
| 2405 |
+
setSecao10InterativoSelecionado('none')
|
| 2406 |
+
setSecao13InterativoFigura(null)
|
| 2407 |
+
setSecao13InterativoSelecionado('none')
|
| 2408 |
setTransformacoesAplicadas(null)
|
| 2409 |
setOrigemTransformacoes(null)
|
| 2410 |
setCamposAvaliacao([])
|
|
|
|
| 2742 |
}
|
| 2743 |
}
|
| 2744 |
|
| 2745 |
+
async function onDownloadPngBase64(imageBase64, mimeType, fileNameBase) {
|
| 2746 |
+
const payload = String(imageBase64 || '').trim()
|
| 2747 |
+
if (!payload) {
|
| 2748 |
+
setError('Imagem indisponível para download.')
|
| 2749 |
+
return
|
| 2750 |
+
}
|
| 2751 |
+
setDownloadingAssets(true)
|
| 2752 |
+
setError('')
|
| 2753 |
+
try {
|
| 2754 |
+
const safeMime = String(mimeType || 'image/png').trim() || 'image/png'
|
| 2755 |
+
const dataUrl = `data:${safeMime};base64,${payload}`
|
| 2756 |
+
const response = await fetch(dataUrl)
|
| 2757 |
+
const blob = await response.blob()
|
| 2758 |
+
downloadBlob(blob, `${sanitizeFileName(fileNameBase, 'grafico')}.png`)
|
| 2759 |
+
} catch (err) {
|
| 2760 |
+
setError(err.message || 'Falha ao baixar imagem do gráfico.')
|
| 2761 |
+
} finally {
|
| 2762 |
+
setDownloadingAssets(false)
|
| 2763 |
+
}
|
| 2764 |
+
}
|
| 2765 |
+
|
| 2766 |
+
async function onDownloadPngBase64Batch(items) {
|
| 2767 |
+
const validItems = (items || []).filter((item) => String(item?.imageBase64 || '').trim())
|
| 2768 |
+
if (validItems.length === 0) {
|
| 2769 |
+
setError('Não há imagens disponíveis para download.')
|
| 2770 |
+
return
|
| 2771 |
+
}
|
| 2772 |
+
setDownloadingAssets(true)
|
| 2773 |
+
setError('')
|
| 2774 |
+
try {
|
| 2775 |
+
for (let i = 0; i < validItems.length; i += 1) {
|
| 2776 |
+
const item = validItems[i]
|
| 2777 |
+
const safeMime = String(item.mimeType || 'image/png').trim() || 'image/png'
|
| 2778 |
+
const dataUrl = `data:${safeMime};base64,${String(item.imageBase64).trim()}`
|
| 2779 |
+
const response = await fetch(dataUrl)
|
| 2780 |
+
const blob = await response.blob()
|
| 2781 |
+
downloadBlob(blob, `${sanitizeFileName(item.fileNameBase, `grafico_${i + 1}`)}.png`)
|
| 2782 |
+
if (i < validItems.length - 1) {
|
| 2783 |
+
await sleep(180)
|
| 2784 |
+
}
|
| 2785 |
+
}
|
| 2786 |
+
} catch (err) {
|
| 2787 |
+
setError(err.message || 'Falha ao baixar imagens.')
|
| 2788 |
+
} finally {
|
| 2789 |
+
setDownloadingAssets(false)
|
| 2790 |
+
}
|
| 2791 |
+
}
|
| 2792 |
+
|
| 2793 |
+
async function onChangeSecao10InterativoSelecionado(value) {
|
| 2794 |
+
const nextValue = String(value || 'none').trim() || 'none'
|
| 2795 |
+
setSecao10InterativoSelecionado(nextValue)
|
| 2796 |
+
if (nextValue === 'none') {
|
| 2797 |
+
return
|
| 2798 |
+
}
|
| 2799 |
+
if (secao10InterativoFigura || !sessionId) return
|
| 2800 |
+
|
| 2801 |
+
await withBusy(async () => {
|
| 2802 |
+
const resp = await api.getDispersaoInterativo(sessionId, 'secao10')
|
| 2803 |
+
setSecao10InterativoFigura(resp?.grafico || null)
|
| 2804 |
+
})
|
| 2805 |
+
}
|
| 2806 |
+
|
| 2807 |
+
async function onChangeSecao13InterativoSelecionado(value) {
|
| 2808 |
+
const nextValue = String(value || 'none').trim() || 'none'
|
| 2809 |
+
setSecao13InterativoSelecionado(nextValue)
|
| 2810 |
+
if (nextValue === 'none') {
|
| 2811 |
+
return
|
| 2812 |
+
}
|
| 2813 |
+
if (secao13InterativoFigura || !sessionId) return
|
| 2814 |
+
|
| 2815 |
+
await withBusy(async () => {
|
| 2816 |
+
const resp = await api.getDispersaoInterativo(sessionId, 'secao13')
|
| 2817 |
+
setSecao13InterativoFigura(resp?.grafico || null)
|
| 2818 |
+
})
|
| 2819 |
+
}
|
| 2820 |
+
|
| 2821 |
+
async function onChangeSecao15InterativoSelecionado(value) {
|
| 2822 |
+
const nextValue = String(value || 'none').trim() || 'none'
|
| 2823 |
+
setSecao15InterativoSelecionado(nextValue)
|
| 2824 |
+
if (nextValue === 'none') {
|
| 2825 |
+
setSecao15InterativoFigura(null)
|
| 2826 |
+
return
|
| 2827 |
+
}
|
| 2828 |
+
if (!sessionId) return
|
| 2829 |
+
|
| 2830 |
+
await withBusy(async () => {
|
| 2831 |
+
const resp = await api.getDiagnosticoInterativo(sessionId, nextValue)
|
| 2832 |
+
setSecao15InterativoFigura(resp?.payload || null)
|
| 2833 |
+
})
|
| 2834 |
+
}
|
| 2835 |
+
|
| 2836 |
function toggleSelection(setter, value) {
|
| 2837 |
setter((prev) => {
|
| 2838 |
if (prev.includes(value)) return prev.filter((item) => item !== value)
|
|
|
|
| 3723 |
</SectionBlock>
|
| 3724 |
|
| 3725 |
<SectionBlock step="10" title="Gráficos de Dispersão das Variáveis Independentes" subtitle="Leitura visual entre X e Y no conjunto filtrado.">
|
| 3726 |
+
{secao10PngPayload ? (
|
| 3727 |
+
<div className="section-disclaimer-warning">
|
| 3728 |
+
Modo PNG automático para mais de {secao10PngPayload.limiar} pontos. Ao final da seção, podem ser gerados individualmente os gráficos interativos.
|
| 3729 |
+
</div>
|
| 3730 |
+
) : null}
|
| 3731 |
+
{secao10PngPayload ? (
|
| 3732 |
+
<>
|
| 3733 |
+
<div className="download-actions-bar">
|
| 3734 |
<button
|
|
|
|
| 3735 |
type="button"
|
| 3736 |
className="btn-download-subtle"
|
| 3737 |
+
onClick={() => onDownloadPngBase64(secao10PngPayload.imageBase64, secao10PngPayload.mimeType, 'secao10_dispersao_png')}
|
| 3738 |
+
disabled={loading || downloadingAssets}
|
|
|
|
| 3739 |
>
|
| 3740 |
+
Fazer download
|
| 3741 |
</button>
|
| 3742 |
+
</div>
|
| 3743 |
+
<div className="scatter-png-card">
|
| 3744 |
+
<img
|
| 3745 |
+
src={`data:${secao10PngPayload.mimeType};base64,${secao10PngPayload.imageBase64}`}
|
| 3746 |
+
alt="Gráficos de dispersão em PNG"
|
| 3747 |
+
className="scatter-png-image"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3748 |
/>
|
| 3749 |
+
</div>
|
| 3750 |
+
<div className="scatter-interactive-control">
|
| 3751 |
+
<label>Gráfico interativo (opcional)</label>
|
| 3752 |
+
<select
|
| 3753 |
+
value={secao10InterativoSelecionado}
|
| 3754 |
+
onChange={(e) => {
|
| 3755 |
+
void onChangeSecao10InterativoSelecionado(e.target.value)
|
| 3756 |
+
}}
|
| 3757 |
+
disabled={loading}
|
| 3758 |
+
>
|
| 3759 |
+
<option value="none">Sem gráfico</option>
|
| 3760 |
+
{secao10InterativoOpcoes.map((item) => (
|
| 3761 |
+
<option key={`s10-int-opt-${item}`} value={item}>{item}</option>
|
| 3762 |
+
))}
|
| 3763 |
+
</select>
|
| 3764 |
+
</div>
|
| 3765 |
+
{secao10InterativoSelecionado !== 'none' ? (
|
| 3766 |
+
<>
|
| 3767 |
+
<div className="download-actions-bar">
|
| 3768 |
+
<button
|
| 3769 |
+
type="button"
|
| 3770 |
+
className="btn-download-subtle"
|
| 3771 |
+
title={secao10InterativoAtual?.legenda || ''}
|
| 3772 |
+
onClick={() => {
|
| 3773 |
+
if (!secao10InterativoAtual) return
|
| 3774 |
+
void onDownloadFigurePng(
|
| 3775 |
+
secao10InterativoAtual.figure,
|
| 3776 |
+
`secao10_${sanitizeFileName(secao10InterativoAtual.label, 'dispersao_interativo')}`,
|
| 3777 |
+
{ forceHideLegend: true },
|
| 3778 |
+
)
|
| 3779 |
+
}}
|
| 3780 |
+
disabled={loading || downloadingAssets || !secao10InterativoAtual?.figure}
|
| 3781 |
+
>
|
| 3782 |
+
Fazer download
|
| 3783 |
+
</button>
|
| 3784 |
+
</div>
|
| 3785 |
+
{secao10InterativoAtual?.figure ? (
|
| 3786 |
+
<PlotFigure
|
| 3787 |
+
key={`s9-interativo-plot-${secao10InterativoAtual.id}`}
|
| 3788 |
+
figure={secao10InterativoAtual.figure}
|
| 3789 |
+
title={secao10InterativoAtual.title}
|
| 3790 |
+
subtitle={secao10InterativoAtual.subtitle}
|
| 3791 |
+
forceHideLegend
|
| 3792 |
+
className="plot-stretch"
|
| 3793 |
+
lazy
|
| 3794 |
+
/>
|
| 3795 |
+
) : (
|
| 3796 |
+
<div className="empty-box">Grafico indisponivel.</div>
|
| 3797 |
+
)}
|
| 3798 |
+
</>
|
| 3799 |
+
) : null}
|
| 3800 |
+
</>
|
| 3801 |
) : (
|
| 3802 |
+
<>
|
| 3803 |
+
<div className="download-actions-bar">
|
| 3804 |
+
{graficosSecao9.length > 1 ? <span className="download-actions-label">Fazer download:</span> : null}
|
| 3805 |
+
{graficosSecao9.map((item, idx) => {
|
| 3806 |
+
const fileBase = `secao10_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`
|
| 3807 |
+
return (
|
| 3808 |
+
<button
|
| 3809 |
+
key={`s9-dl-${item.id}`}
|
| 3810 |
+
type="button"
|
| 3811 |
+
className="btn-download-subtle"
|
| 3812 |
+
title={item.legenda}
|
| 3813 |
+
onClick={() => onDownloadFigurePng(item.figure, fileBase, { forceHideLegend: true })}
|
| 3814 |
+
disabled={loading || downloadingAssets || !item.figure}
|
| 3815 |
+
>
|
| 3816 |
+
{graficosSecao9.length > 1 ? item.label : 'Fazer download'}
|
| 3817 |
+
</button>
|
| 3818 |
+
)
|
| 3819 |
+
})}
|
| 3820 |
+
{graficosSecao9.length > 1 ? (
|
| 3821 |
+
<button
|
| 3822 |
+
type="button"
|
| 3823 |
+
className="btn-download-subtle"
|
| 3824 |
+
onClick={() => onDownloadFiguresPngBatch(
|
| 3825 |
+
graficosSecao9.map((item, idx) => ({
|
| 3826 |
+
figure: item.figure,
|
| 3827 |
+
fileNameBase: `secao10_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`,
|
| 3828 |
+
forceHideLegend: true,
|
| 3829 |
+
})),
|
| 3830 |
+
)}
|
| 3831 |
+
disabled={loading || downloadingAssets || graficosSecao9.length === 0}
|
| 3832 |
+
>
|
| 3833 |
+
Todos
|
| 3834 |
+
</button>
|
| 3835 |
+
) : null}
|
| 3836 |
+
</div>
|
| 3837 |
+
{graficosSecao9.length > 0 ? (
|
| 3838 |
+
<div className="plot-grid-scatter" style={{ '--plot-cols': colunasGraficosSecao9 }}>
|
| 3839 |
+
{graficosSecao9.map((item) => (
|
| 3840 |
+
<PlotFigure
|
| 3841 |
+
key={`s9-plot-${item.id}`}
|
| 3842 |
+
figure={item.figure}
|
| 3843 |
+
title={item.title}
|
| 3844 |
+
subtitle={item.subtitle}
|
| 3845 |
+
forceHideLegend
|
| 3846 |
+
className="plot-stretch"
|
| 3847 |
+
lazy
|
| 3848 |
+
/>
|
| 3849 |
+
))}
|
| 3850 |
+
</div>
|
| 3851 |
+
) : (
|
| 3852 |
+
<div className="empty-box">Grafico indisponivel.</div>
|
| 3853 |
+
)}
|
| 3854 |
+
</>
|
| 3855 |
)}
|
| 3856 |
</SectionBlock>
|
| 3857 |
|
|
|
|
| 4085 |
{fit ? (
|
| 4086 |
<>
|
| 4087 |
<SectionBlock step="13" title="Visualizar Mapa dos Dados de Mercado" subtitle="Escolha livre dos eixos para análise gráfica do modelo.">
|
| 4088 |
+
{secao13PngPayload ? (
|
| 4089 |
+
<div className="section-disclaimer-warning">
|
| 4090 |
+
Modo PNG automático para mais de {secao13PngPayload.limiar} pontos. Ao final da seção, podem ser gerados individualmente os gráficos interativos.
|
| 4091 |
+
</div>
|
| 4092 |
+
) : null}
|
| 4093 |
<div className="row dispersao-config-row">
|
| 4094 |
<div className="dispersao-config-field">
|
| 4095 |
<label>Eixo X</label>
|
|
|
|
| 4160 |
</div>
|
| 4161 |
) : null}
|
| 4162 |
</div>
|
| 4163 |
+
{secao13PngPayload ? (
|
| 4164 |
+
<>
|
| 4165 |
+
<div className="download-actions-bar">
|
|
|
|
|
|
|
| 4166 |
<button
|
|
|
|
| 4167 |
type="button"
|
| 4168 |
className="btn-download-subtle"
|
| 4169 |
+
onClick={() => onDownloadPngBase64(secao13PngPayload.imageBase64, secao13PngPayload.mimeType, 'secao13_dispersao_png')}
|
| 4170 |
+
disabled={loading || downloadingAssets}
|
|
|
|
| 4171 |
>
|
| 4172 |
+
Fazer download
|
| 4173 |
</button>
|
| 4174 |
+
</div>
|
| 4175 |
+
<div className="scatter-png-card">
|
| 4176 |
+
<img
|
| 4177 |
+
src={`data:${secao13PngPayload.mimeType};base64,${secao13PngPayload.imageBase64}`}
|
| 4178 |
+
alt="Dispersão do modelo em PNG"
|
| 4179 |
+
className="scatter-png-image"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4180 |
/>
|
| 4181 |
+
</div>
|
| 4182 |
+
<div className="scatter-interactive-control">
|
| 4183 |
+
<label>Renderização interativa</label>
|
| 4184 |
+
<select
|
| 4185 |
+
value={secao13InterativoSelecionado === 'none' ? 'off' : 'on'}
|
| 4186 |
+
onChange={(e) => {
|
| 4187 |
+
const next = String(e.target.value || 'off')
|
| 4188 |
+
if (next === 'off') {
|
| 4189 |
+
void onChangeSecao13InterativoSelecionado('none')
|
| 4190 |
+
return
|
| 4191 |
+
}
|
| 4192 |
+
const firstOption = secao13InterativoOpcoes[0] || 'none'
|
| 4193 |
+
void onChangeSecao13InterativoSelecionado(firstOption)
|
| 4194 |
+
}}
|
| 4195 |
+
disabled={loading}
|
| 4196 |
+
>
|
| 4197 |
+
<option value="off">Sem gráfico interativo</option>
|
| 4198 |
+
<option value="on">Com gráfico interativo</option>
|
| 4199 |
+
</select>
|
| 4200 |
+
</div>
|
| 4201 |
+
{secao13InterativoSelecionado !== 'none' ? (
|
| 4202 |
+
<>
|
| 4203 |
+
<div className="scatter-interactive-control">
|
| 4204 |
+
<label>Variável do gráfico interativo</label>
|
| 4205 |
+
<select
|
| 4206 |
+
value={secao13InterativoSelecionado}
|
| 4207 |
+
onChange={(e) => {
|
| 4208 |
+
void onChangeSecao13InterativoSelecionado(e.target.value)
|
| 4209 |
+
}}
|
| 4210 |
+
disabled={loading}
|
| 4211 |
+
>
|
| 4212 |
+
{secao13InterativoOpcoes.map((item) => (
|
| 4213 |
+
<option key={`s13-int-opt-bottom-${item}`} value={item}>{item}</option>
|
| 4214 |
+
))}
|
| 4215 |
+
</select>
|
| 4216 |
+
</div>
|
| 4217 |
+
<div className="download-actions-bar">
|
| 4218 |
+
<button
|
| 4219 |
+
type="button"
|
| 4220 |
+
className="btn-download-subtle"
|
| 4221 |
+
title={secao13InterativoAtual?.legenda || ''}
|
| 4222 |
+
onClick={() => {
|
| 4223 |
+
if (!secao13InterativoAtual) return
|
| 4224 |
+
void onDownloadFigurePng(
|
| 4225 |
+
secao13InterativoAtual.figure,
|
| 4226 |
+
`secao13_${sanitizeFileName(secao13InterativoAtual.label, 'dispersao_interativo')}`,
|
| 4227 |
+
{ forceHideLegend: true },
|
| 4228 |
+
)
|
| 4229 |
+
}}
|
| 4230 |
+
disabled={loading || downloadingAssets || !secao13InterativoAtual?.figure}
|
| 4231 |
+
>
|
| 4232 |
+
Fazer download
|
| 4233 |
+
</button>
|
| 4234 |
+
</div>
|
| 4235 |
+
{secao13InterativoAtual?.figure ? (
|
| 4236 |
+
<PlotFigure
|
| 4237 |
+
key={`s12-interativo-plot-${secao13InterativoAtual.id}`}
|
| 4238 |
+
figure={secao13InterativoAtual.figure}
|
| 4239 |
+
title={secao13InterativoAtual.title}
|
| 4240 |
+
subtitle={secao13InterativoAtual.subtitle}
|
| 4241 |
+
forceHideLegend
|
| 4242 |
+
className="plot-stretch"
|
| 4243 |
+
lazy
|
| 4244 |
+
/>
|
| 4245 |
+
) : (
|
| 4246 |
+
<div className="empty-box">Grafico indisponivel.</div>
|
| 4247 |
+
)}
|
| 4248 |
+
</>
|
| 4249 |
+
) : null}
|
| 4250 |
+
</>
|
| 4251 |
) : (
|
| 4252 |
+
<>
|
| 4253 |
+
<div className="download-actions-bar">
|
| 4254 |
+
{graficosSecao12.length > 1 ? <span className="download-actions-label">Fazer download:</span> : null}
|
| 4255 |
+
{graficosSecao12.map((item, idx) => {
|
| 4256 |
+
const fileBase = `secao13_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`
|
| 4257 |
+
return (
|
| 4258 |
+
<button
|
| 4259 |
+
key={`s12-dl-${item.id}`}
|
| 4260 |
+
type="button"
|
| 4261 |
+
className="btn-download-subtle"
|
| 4262 |
+
title={item.legenda}
|
| 4263 |
+
onClick={() => onDownloadFigurePng(item.figure, fileBase, { forceHideLegend: true })}
|
| 4264 |
+
disabled={loading || downloadingAssets || !item.figure}
|
| 4265 |
+
>
|
| 4266 |
+
{graficosSecao12.length > 1 ? item.label : 'Fazer download'}
|
| 4267 |
+
</button>
|
| 4268 |
+
)
|
| 4269 |
+
})}
|
| 4270 |
+
{graficosSecao12.length > 1 ? (
|
| 4271 |
+
<button
|
| 4272 |
+
type="button"
|
| 4273 |
+
className="btn-download-subtle"
|
| 4274 |
+
onClick={() => onDownloadFiguresPngBatch(
|
| 4275 |
+
graficosSecao12.map((item, idx) => ({
|
| 4276 |
+
figure: item.figure,
|
| 4277 |
+
fileNameBase: `secao13_${sanitizeFileName(item.label, `dispersao_${idx + 1}`)}`,
|
| 4278 |
+
forceHideLegend: true,
|
| 4279 |
+
})),
|
| 4280 |
+
)}
|
| 4281 |
+
disabled={loading || downloadingAssets || graficosSecao12.length === 0}
|
| 4282 |
+
>
|
| 4283 |
+
Todos
|
| 4284 |
+
</button>
|
| 4285 |
+
) : null}
|
| 4286 |
+
</div>
|
| 4287 |
+
{graficosSecao12.length > 0 ? (
|
| 4288 |
+
<div className="plot-grid-scatter" style={{ '--plot-cols': colunasGraficosSecao12 }}>
|
| 4289 |
+
{graficosSecao12.map((item) => (
|
| 4290 |
+
<PlotFigure
|
| 4291 |
+
key={`s12-plot-${item.id}`}
|
| 4292 |
+
figure={item.figure}
|
| 4293 |
+
title={item.title}
|
| 4294 |
+
subtitle={item.subtitle}
|
| 4295 |
+
forceHideLegend
|
| 4296 |
+
className="plot-stretch"
|
| 4297 |
+
lazy
|
| 4298 |
+
/>
|
| 4299 |
+
))}
|
| 4300 |
+
</div>
|
| 4301 |
+
) : (
|
| 4302 |
+
<div className="empty-box">Grafico indisponivel.</div>
|
| 4303 |
+
)}
|
| 4304 |
+
</>
|
| 4305 |
)}
|
| 4306 |
</SectionBlock>
|
| 4307 |
|
|
|
|
| 4358 |
</SectionBlock>
|
| 4359 |
|
| 4360 |
<SectionBlock step="15" title="Gráficos de Diagnóstico do Modelo" subtitle="Obs x calc, resíduos, histograma, Cook e correlação.">
|
| 4361 |
+
{secao15DiagnosticoPng ? (
|
| 4362 |
+
<div className="section-disclaimer-warning">
|
| 4363 |
+
Modo PNG automático para mais de {fit.graficos_diagnostico_limiar_png || 1000} pontos. Ao final da seção, podem ser gerados individualmente os gráficos interativos.
|
| 4364 |
+
</div>
|
| 4365 |
+
) : null}
|
| 4366 |
<div className="download-actions-bar">
|
| 4367 |
<span className="download-actions-label">Fazer download:</span>
|
| 4368 |
<button
|
| 4369 |
type="button"
|
| 4370 |
className="btn-download-subtle"
|
| 4371 |
+
onClick={() => (
|
| 4372 |
+
secao15DiagnosticoPng
|
| 4373 |
+
? onDownloadPngBase64(fit.grafico_obs_calc_png?.image_base64, fit.grafico_obs_calc_png?.mime_type, 'secao15_obs_calc')
|
| 4374 |
+
: onDownloadFigurePng(fit.grafico_obs_calc, 'secao15_obs_calc')
|
| 4375 |
+
)}
|
| 4376 |
+
disabled={loading || downloadingAssets || (secao15DiagnosticoPng ? !fit.grafico_obs_calc_png?.image_base64 : !fit.grafico_obs_calc)}
|
| 4377 |
>
|
| 4378 |
Obs x calc
|
| 4379 |
</button>
|
| 4380 |
<button
|
| 4381 |
type="button"
|
| 4382 |
className="btn-download-subtle"
|
| 4383 |
+
onClick={() => (
|
| 4384 |
+
secao15DiagnosticoPng
|
| 4385 |
+
? onDownloadPngBase64(fit.grafico_residuos_png?.image_base64, fit.grafico_residuos_png?.mime_type, 'secao15_residuos')
|
| 4386 |
+
: onDownloadFigurePng(fit.grafico_residuos, 'secao15_residuos')
|
| 4387 |
+
)}
|
| 4388 |
+
disabled={loading || downloadingAssets || (secao15DiagnosticoPng ? !fit.grafico_residuos_png?.image_base64 : !fit.grafico_residuos)}
|
| 4389 |
>
|
| 4390 |
Resíduos
|
| 4391 |
</button>
|
| 4392 |
<button
|
| 4393 |
type="button"
|
| 4394 |
className="btn-download-subtle"
|
| 4395 |
+
onClick={() => (
|
| 4396 |
+
secao15DiagnosticoPng
|
| 4397 |
+
? onDownloadPngBase64(fit.grafico_histograma_png?.image_base64, fit.grafico_histograma_png?.mime_type, 'secao15_histograma')
|
| 4398 |
+
: onDownloadFigurePng(fit.grafico_histograma, 'secao15_histograma')
|
| 4399 |
+
)}
|
| 4400 |
+
disabled={loading || downloadingAssets || (secao15DiagnosticoPng ? !fit.grafico_histograma_png?.image_base64 : !fit.grafico_histograma)}
|
| 4401 |
>
|
| 4402 |
Histograma
|
| 4403 |
</button>
|
| 4404 |
<button
|
| 4405 |
type="button"
|
| 4406 |
className="btn-download-subtle"
|
| 4407 |
+
onClick={() => (
|
| 4408 |
+
secao15DiagnosticoPng
|
| 4409 |
+
? onDownloadPngBase64(fit.grafico_cook_png?.image_base64, fit.grafico_cook_png?.mime_type, 'secao15_cook')
|
| 4410 |
+
: onDownloadFigurePng(fit.grafico_cook, 'secao15_cook', { forceHideLegend: true })
|
| 4411 |
+
)}
|
| 4412 |
+
disabled={loading || downloadingAssets || (secao15DiagnosticoPng ? !fit.grafico_cook_png?.image_base64 : !fit.grafico_cook)}
|
| 4413 |
>
|
| 4414 |
Cook
|
| 4415 |
</button>
|
|
|
|
| 4424 |
<button
|
| 4425 |
type="button"
|
| 4426 |
className="btn-download-subtle"
|
| 4427 |
+
onClick={() => {
|
| 4428 |
+
if (secao15DiagnosticoPng) {
|
| 4429 |
+
void onDownloadPngBase64Batch([
|
| 4430 |
+
{ imageBase64: fit.grafico_obs_calc_png?.image_base64, mimeType: fit.grafico_obs_calc_png?.mime_type, fileNameBase: 'secao15_obs_calc' },
|
| 4431 |
+
{ imageBase64: fit.grafico_residuos_png?.image_base64, mimeType: fit.grafico_residuos_png?.mime_type, fileNameBase: 'secao15_residuos' },
|
| 4432 |
+
{ imageBase64: fit.grafico_histograma_png?.image_base64, mimeType: fit.grafico_histograma_png?.mime_type, fileNameBase: 'secao15_histograma' },
|
| 4433 |
+
{ imageBase64: fit.grafico_cook_png?.image_base64, mimeType: fit.grafico_cook_png?.mime_type, fileNameBase: 'secao15_cook' },
|
| 4434 |
+
])
|
| 4435 |
+
return
|
| 4436 |
+
}
|
| 4437 |
+
void onDownloadFiguresPngBatch([
|
| 4438 |
+
{ figure: fit.grafico_obs_calc, fileNameBase: 'secao15_obs_calc' },
|
| 4439 |
+
{ figure: fit.grafico_residuos, fileNameBase: 'secao15_residuos' },
|
| 4440 |
+
{ figure: fit.grafico_histograma, fileNameBase: 'secao15_histograma' },
|
| 4441 |
+
{ figure: fit.grafico_cook, fileNameBase: 'secao15_cook', forceHideLegend: true },
|
| 4442 |
+
{ figure: fit.grafico_correlacao, fileNameBase: 'secao15_correlacao' },
|
| 4443 |
+
])
|
| 4444 |
+
}}
|
| 4445 |
+
disabled={loading || downloadingAssets || (
|
| 4446 |
+
secao15DiagnosticoPng
|
| 4447 |
+
? (!fit.grafico_obs_calc_png?.image_base64 && !fit.grafico_residuos_png?.image_base64 && !fit.grafico_histograma_png?.image_base64 && !fit.grafico_cook_png?.image_base64)
|
| 4448 |
+
: (!fit.grafico_obs_calc && !fit.grafico_residuos && !fit.grafico_histograma && !fit.grafico_cook && !fit.grafico_correlacao)
|
| 4449 |
+
)}
|
| 4450 |
>
|
| 4451 |
Todos
|
| 4452 |
</button>
|
| 4453 |
</div>
|
| 4454 |
+
{secao15DiagnosticoPng ? (
|
| 4455 |
+
<div className="plot-grid-2-fixed">
|
| 4456 |
+
<DiagnosticPngCard title="Obs x Calc" pngPayload={fit.grafico_obs_calc_png} alt="Obs x Calc em PNG" />
|
| 4457 |
+
<DiagnosticPngCard title="Resíduos" pngPayload={fit.grafico_residuos_png} alt="Resíduos em PNG" />
|
| 4458 |
+
<DiagnosticPngCard title="Histograma" pngPayload={fit.grafico_histograma_png} alt="Histograma em PNG" />
|
| 4459 |
+
<DiagnosticPngCard title="Cook" pngPayload={fit.grafico_cook_png} alt="Cook em PNG" />
|
| 4460 |
+
</div>
|
| 4461 |
+
) : (
|
| 4462 |
+
<div className="plot-grid-2-fixed">
|
| 4463 |
+
<PlotFigure figure={fit.grafico_obs_calc} title="Obs x Calc" />
|
| 4464 |
+
<PlotFigure figure={fit.grafico_residuos} title="Resíduos" />
|
| 4465 |
+
<PlotFigure figure={fit.grafico_histograma} title="Histograma" />
|
| 4466 |
+
<PlotFigure figure={fit.grafico_cook} title="Cook" forceHideLegend />
|
| 4467 |
+
</div>
|
| 4468 |
+
)}
|
| 4469 |
<div className="plot-full-width">
|
| 4470 |
<PlotFigure figure={fit.grafico_correlacao} title="Matriz de correlação" className="plot-correlation-card" />
|
| 4471 |
</div>
|
| 4472 |
+
{secao15DiagnosticoPng ? (
|
| 4473 |
+
<>
|
| 4474 |
+
<div className="scatter-interactive-control">
|
| 4475 |
+
<label>Gráfico interativo (opcional)</label>
|
| 4476 |
+
<select
|
| 4477 |
+
value={secao15InterativoSelecionado}
|
| 4478 |
+
onChange={(e) => {
|
| 4479 |
+
void onChangeSecao15InterativoSelecionado(e.target.value)
|
| 4480 |
+
}}
|
| 4481 |
+
disabled={loading}
|
| 4482 |
+
>
|
| 4483 |
+
{secao15InterativoOpcoes.map((item) => (
|
| 4484 |
+
<option key={`s15-int-opt-${item.value}`} value={item.value}>{item.label}</option>
|
| 4485 |
+
))}
|
| 4486 |
+
</select>
|
| 4487 |
+
</div>
|
| 4488 |
+
{secao15InterativoSelecionado !== 'none' ? (
|
| 4489 |
+
<>
|
| 4490 |
+
<div className="download-actions-bar">
|
| 4491 |
+
<button
|
| 4492 |
+
type="button"
|
| 4493 |
+
className="btn-download-subtle"
|
| 4494 |
+
onClick={() => {
|
| 4495 |
+
if (!secao15InterativoFigura) return
|
| 4496 |
+
void onDownloadFigurePng(
|
| 4497 |
+
secao15InterativoFigura,
|
| 4498 |
+
`secao15_${sanitizeFileName(secao15InterativoLabel, 'diagnostico_interativo')}`,
|
| 4499 |
+
{ forceHideLegend: secao15InterativoSelecionado === 'cook' },
|
| 4500 |
+
)
|
| 4501 |
+
}}
|
| 4502 |
+
disabled={loading || downloadingAssets || !secao15InterativoFigura}
|
| 4503 |
+
>
|
| 4504 |
+
Fazer download
|
| 4505 |
+
</button>
|
| 4506 |
+
</div>
|
| 4507 |
+
{secao15InterativoFigura ? (
|
| 4508 |
+
<PlotFigure
|
| 4509 |
+
key={`s15-interativo-${secao15InterativoSelecionado}`}
|
| 4510 |
+
figure={secao15InterativoFigura}
|
| 4511 |
+
title={secao15InterativoLabel}
|
| 4512 |
+
forceHideLegend={secao15InterativoSelecionado === 'cook'}
|
| 4513 |
+
className="plot-stretch"
|
| 4514 |
+
lazy
|
| 4515 |
+
/>
|
| 4516 |
+
) : (
|
| 4517 |
+
<div className="empty-box">Grafico indisponivel.</div>
|
| 4518 |
+
)}
|
| 4519 |
+
</>
|
| 4520 |
+
) : null}
|
| 4521 |
+
</>
|
| 4522 |
+
) : null}
|
| 4523 |
</SectionBlock>
|
| 4524 |
|
| 4525 |
<SectionBlock step="16" title="Analisar Resíduos" subtitle="Métricas para identificação de observações influentes.">
|
frontend/src/styles.css
CHANGED
|
@@ -2969,6 +2969,66 @@ button.btn-upload-select {
|
|
| 2969 |
gap: 12px;
|
| 2970 |
}
|
| 2971 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2972 |
.plot-card {
|
| 2973 |
border: 1px solid #dbe5ef;
|
| 2974 |
border-radius: 12px;
|
|
|
|
| 2969 |
gap: 12px;
|
| 2970 |
}
|
| 2971 |
|
| 2972 |
+
.section-disclaimer-warning {
|
| 2973 |
+
margin: 0 0 10px;
|
| 2974 |
+
padding: 9px 12px;
|
| 2975 |
+
border-radius: 10px;
|
| 2976 |
+
border: 1px solid #efcf75;
|
| 2977 |
+
background: linear-gradient(180deg, #fff8dd 0%, #ffefb8 100%);
|
| 2978 |
+
color: #6f4d00;
|
| 2979 |
+
font-size: 0.86rem;
|
| 2980 |
+
font-weight: 700;
|
| 2981 |
+
}
|
| 2982 |
+
|
| 2983 |
+
.scatter-png-card {
|
| 2984 |
+
border: 1px solid #dbe5ef;
|
| 2985 |
+
border-radius: 12px;
|
| 2986 |
+
background: #fff;
|
| 2987 |
+
padding: 10px;
|
| 2988 |
+
}
|
| 2989 |
+
|
| 2990 |
+
.scatter-png-image {
|
| 2991 |
+
display: block;
|
| 2992 |
+
width: 100%;
|
| 2993 |
+
height: auto;
|
| 2994 |
+
border-radius: 8px;
|
| 2995 |
+
}
|
| 2996 |
+
|
| 2997 |
+
.scatter-interactive-control {
|
| 2998 |
+
display: flex;
|
| 2999 |
+
align-items: center;
|
| 3000 |
+
gap: 10px;
|
| 3001 |
+
flex-wrap: wrap;
|
| 3002 |
+
margin: 12px 0;
|
| 3003 |
+
}
|
| 3004 |
+
|
| 3005 |
+
.scatter-interactive-control label {
|
| 3006 |
+
font-weight: 700;
|
| 3007 |
+
color: #334b62;
|
| 3008 |
+
}
|
| 3009 |
+
|
| 3010 |
+
.scatter-interactive-control select {
|
| 3011 |
+
min-width: 220px;
|
| 3012 |
+
max-width: 340px;
|
| 3013 |
+
}
|
| 3014 |
+
|
| 3015 |
+
.scatter-interactive-hint {
|
| 3016 |
+
color: #5f7387;
|
| 3017 |
+
font-size: 0.84rem;
|
| 3018 |
+
}
|
| 3019 |
+
|
| 3020 |
+
.plot-png-card {
|
| 3021 |
+
padding: 8px;
|
| 3022 |
+
}
|
| 3023 |
+
|
| 3024 |
+
.plot-png-image {
|
| 3025 |
+
display: block;
|
| 3026 |
+
width: 100%;
|
| 3027 |
+
height: auto;
|
| 3028 |
+
border-radius: 8px;
|
| 3029 |
+
border: 1px solid #dbe5ef;
|
| 3030 |
+
}
|
| 3031 |
+
|
| 3032 |
.plot-card {
|
| 3033 |
border: 1px solid #dbe5ef;
|
| 3034 |
border-radius: 12px;
|