diff --git a/frontend/src/components/editor/Step3FinalGraph.jsx b/frontend/src/components/editor/Step3FinalGraph.jsx index 8e344f9..4a8fcbd 100644 --- a/frontend/src/components/editor/Step3FinalGraph.jsx +++ b/frontend/src/components/editor/Step3FinalGraph.jsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, memo } from 'react'; import { ComposedChart, Area, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { useGraphData } from './finalGraph/useGraphData'; import { GraphTooltip } from './finalGraph/GraphTooltip'; +import { MembershipEvaluator } from './finalGraph/MembershipEvaluator'; const Step3FinalGraph = memo(({ data, criterionName }) => { const { sortedResults, denseData } = useGraphData(data); @@ -19,15 +20,15 @@ const Step3FinalGraph = memo(({ data, criterionName }) => { } return ( -
+
- -

+ +

{criterionName ? `Criterio: ${criterionName}` : 'Espectro Difuso Final'}

- -
- + +
+ {!isReady && (
@@ -35,28 +36,25 @@ const Step3FinalGraph = memo(({ data, criterionName }) => {
)} - {/* Gráfica */}
{isReady && ( - - Number(val.toFixed(2))} - tick={{ fill: '#475569', fontSize: 14 }} + Number(val.toFixed(2))} + tick={{ fill: '#475569', fontSize: 14 }} /> - - } - cursor={{ stroke: '#cbd5e1', strokeWidth: 1, strokeDasharray: '5 5' }} + } + cursor={{ stroke: '#cbd5e1', strokeWidth: 1, strokeDasharray: '5 5' }} isAnimationActive={false} /> - {sortedResults.map((item) => ( {item.isType2 ? ( @@ -76,8 +74,8 @@ const Step3FinalGraph = memo(({ data, criterionName }) => {
- {/* Leyenda */} -
+ {/* Legend */} +
{sortedResults.map((item) => (
@@ -85,6 +83,13 @@ const Step3FinalGraph = memo(({ data, criterionName }) => {
))}
+ + {/* Evaluador Manual */} + {isReady && sortedResults.length > 0 && ( +
+ +
+ )}
); }); diff --git a/frontend/src/components/editor/finalGraph/MembershipEvaluator.jsx b/frontend/src/components/editor/finalGraph/MembershipEvaluator.jsx new file mode 100644 index 0000000..c41f75e --- /dev/null +++ b/frontend/src/components/editor/finalGraph/MembershipEvaluator.jsx @@ -0,0 +1,115 @@ +import { useState } from 'react'; +import { interpolateY } from './useGraphData'; + +const evaluateMembership = (x, sortedResults) => { + if (!sortedResults || sortedResults.length === 0) return []; + return sortedResults.map(item => { + if (item.isType2) { + const lower = interpolateY(x, item.lowerNodes) ?? 0; + const upper = interpolateY(x, item.upperNodes) ?? 0; + return { term: item.term, color: item.color, isType2: true, lower, upper }; + } + const y = interpolateY(x, item.nodes) ?? 0; + return { term: item.term, color: item.color, isType2: false, y }; + }); +}; + +const filterActive = (results) => + results.filter(r => r.isType2 ? r.upper > 0 : r.y > 0); + +const TermResult = ({ result }) => { + const uncertainty = result.isType2 ? Math.abs(result.upper - result.lower) : 0; + const isSimple = !result.isType2 || uncertainty <= 0.001; + const displayY = result.isType2 ? result.upper : result.y; + + return ( +
+ + {result.term} + + {isSimple ? ( + + Pertenencia: {displayY.toFixed(3)} + + ) : ( + <> + + Mínimo: {result.lower.toFixed(3)} + + + Máximo: {result.upper.toFixed(3)} + + + Incertidumbre: {uncertainty.toFixed(3)} + + + )} +
+ ); +}; + +export const MembershipEvaluator = ({ sortedResults }) => { + const [inputValue, setInputValue] = useState(''); + + const handleChange = (e) => { + const raw = e.target.value; + if (raw === '') { setInputValue(''); return; } + const dotIdx = raw.indexOf('.'); + if (dotIdx !== -1 && raw.length - dotIdx - 1 > 4) { + setInputValue(raw.slice(0, dotIdx + 5)); + } else { + setInputValue(raw); + } + }; + + const trimmed = inputValue.trim(); + const xNum = trimmed === '' ? null : parseFloat(trimmed); + const isValidNumber = xNum !== null && !isNaN(xNum) && isFinite(xNum); + + const activeResults = isValidNumber + ? filterActive(evaluateMembership(xNum, sortedResults)) + : null; + + return ( +
+ {/* Input group */} +
+ + +
+ + {/* Results */} +
+ {trimmed === '' && ( + + Introduce un valor para obtener el grado de pertenencia. + + )} + {trimmed !== '' && !isValidNumber && ( + Valor no válido. + )} + {isValidNumber && activeResults.length === 0 && ( + + Ningún término activo en X = {xNum.toFixed(4)}. + + )} + {isValidNumber && activeResults.map(r => ( + + ))} +
+
+ ); +}; diff --git a/frontend/src/components/editor/finalGraph/useGraphData.js b/frontend/src/components/editor/finalGraph/useGraphData.js index 408cdfc..965c133 100644 --- a/frontend/src/components/editor/finalGraph/useGraphData.js +++ b/frontend/src/components/editor/finalGraph/useGraphData.js @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { CHART_COLORS } from '../../../config'; -const interpolateY = (x, nodes) => { +export const interpolateY = (x, nodes) => { if (!nodes || nodes.length === 0) return null; const EPSILON = 1e-5; const MICRO_STEP = 0.0001;