fix: arreglar que el núcleo de una etiqueta no pueda entrar en el soporte de sus etiquetas adyacentes
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ComposedChart, Line, XAxis, YAxis, CartesianGrid, ReferenceArea, ReferenceLine, ResponsiveContainer, Tooltip } from 'recharts';
|
import { ComposedChart, Line, XAxis, YAxis, CartesianGrid, ReferenceArea, ReferenceLine, ResponsiveContainer, Tooltip } from 'recharts';
|
||||||
|
|
||||||
export default function MembershipFunctionChart({ baseScale, mfDefinitions, selectedTerm, colors }) {
|
export default function Chart({ baseScale, mfDefinitions, selectedTerm, colors }) {
|
||||||
const scaleKeys = Object.keys(baseScale);
|
const scaleKeys = Object.keys(baseScale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
import React from 'react';
|
export default function Controls({ selectedTerm, currentMf, selectedColor, baseScale, mfDefinitions, updateCurrentMf }) {
|
||||||
|
|
||||||
export default function MembershipFunctionControls({ selectedTerm, currentMf, selectedColor, baseScale, updateCurrentMf }) {
|
|
||||||
if (!selectedTerm || !currentMf) return null;
|
if (!selectedTerm || !currentMf) return null;
|
||||||
|
|
||||||
const scaleKeys = Object.keys(baseScale);
|
const scaleKeys = Object.keys(baseScale);
|
||||||
const selectedIndex = scaleKeys.indexOf(selectedTerm);
|
const selectedIndex = scaleKeys.indexOf(selectedTerm);
|
||||||
|
|
||||||
let minBound = 0;
|
let prevCoreEnd = 0;
|
||||||
let maxBound = 1;
|
let prevSupportEnd = 0;
|
||||||
|
let nextCoreStart = 1;
|
||||||
|
let nextSupportStart = 1;
|
||||||
|
|
||||||
if (selectedIndex > 0) {
|
if (selectedIndex > 0) {
|
||||||
minBound = baseScale[scaleKeys[selectedIndex - 1]];
|
const prevTerm = scaleKeys[selectedIndex - 1];
|
||||||
|
prevCoreEnd = mfDefinitions[prevTerm].coreEnd;
|
||||||
|
prevSupportEnd = mfDefinitions[prevTerm].supportEnd;
|
||||||
}
|
}
|
||||||
if (selectedIndex >= 0 && selectedIndex < scaleKeys.length - 1) {
|
if (selectedIndex < scaleKeys.length - 1) {
|
||||||
maxBound = baseScale[scaleKeys[selectedIndex + 1]];
|
const nextTerm = scaleKeys[selectedIndex + 1];
|
||||||
|
nextCoreStart = mfDefinitions[nextTerm].coreStart;
|
||||||
|
nextSupportStart = mfDefinitions[nextTerm].supportStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -25,35 +29,41 @@ export default function MembershipFunctionControls({ selectedTerm, currentMf, se
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
|
|
||||||
|
{/* Columna izquierda: Inicios */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
||||||
<span>Inicio del Núcleo</span><span style={{ color: selectedColor }}>{currentMf.coreStart.toFixed(3)}</span>
|
<span>Inicio del Núcleo (Límite: {prevSupportEnd.toFixed(2)})</span>
|
||||||
|
<span style={{ color: selectedColor }}>{currentMf.coreStart.toFixed(3)}</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" min={minBound} max={maxBound} step="0.001" value={currentMf.coreStart} onChange={(e) => updateCurrentMf('coreStart', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor }} />
|
<input type="range" min={prevSupportEnd} max={nextCoreStart} step="0.001" value={currentMf.coreStart} onChange={(e) => updateCurrentMf('coreStart', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
||||||
<span>Inicio del Soporte (Límite: {minBound.toFixed(2)})</span><span style={{ color: selectedColor }}>{currentMf.supportStart.toFixed(3)}</span>
|
<span>Inicio del Soporte (Límite: {prevCoreEnd.toFixed(2)})</span>
|
||||||
|
<span style={{ color: selectedColor }}>{currentMf.supportStart.toFixed(3)}</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" min={minBound} max={maxBound} step="0.001" value={currentMf.supportStart} onChange={(e) => updateCurrentMf('supportStart', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor, opacity: 0.7 }} />
|
<input type="range" min={prevCoreEnd} max={nextCoreStart} step="0.001" value={currentMf.supportStart} onChange={(e) => updateCurrentMf('supportStart', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor, opacity: 0.7 }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Columna derecha: Fines */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
||||||
<span>Fin del Núcleo</span><span style={{ color: selectedColor }}>{currentMf.coreEnd.toFixed(3)}</span>
|
<span>Fin del Núcleo (Límite: {nextSupportStart.toFixed(2)})</span>
|
||||||
|
<span style={{ color: selectedColor }}>{currentMf.coreEnd.toFixed(3)}</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" min={minBound} max={maxBound} step="0.001" value={currentMf.coreEnd} onChange={(e) => updateCurrentMf('coreEnd', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor }} />
|
<input type="range" min={prevCoreEnd} max={nextSupportStart} step="0.001" value={currentMf.coreEnd} onChange={(e) => updateCurrentMf('coreEnd', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2">
|
||||||
<span>Fin del Soporte (Límite: {maxBound.toFixed(2)})</span><span style={{ color: selectedColor }}>{currentMf.supportEnd.toFixed(3)}</span>
|
<span>Fin del Soporte (Límite: {nextCoreStart.toFixed(2)})</span>
|
||||||
|
<span style={{ color: selectedColor }}>{currentMf.supportEnd.toFixed(3)}</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" min={minBound} max={maxBound} step="0.001" value={currentMf.supportEnd} onChange={(e) => updateCurrentMf('supportEnd', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor, opacity: 0.7 }} />
|
<input type="range" min={prevCoreEnd} max={nextCoreStart} step="0.001" value={currentMf.supportEnd} onChange={(e) => updateCurrentMf('supportEnd', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor, opacity: 0.7 }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import CriterionInput from '../components/CriterionInput';
|
|||||||
import CardEditor from '../components/CardEditor';
|
import CardEditor from '../components/CardEditor';
|
||||||
import BlankCardsCounter from '../components/BlankCardsCounter';
|
import BlankCardsCounter from '../components/BlankCardsCounter';
|
||||||
import AddLevelButton from '../components/AddLevelButton';
|
import AddLevelButton from '../components/AddLevelButton';
|
||||||
import MembershipFunctionChart from '../components/membershipFunction/Chart';
|
import Chart from '../components/membershipFunction/Chart';
|
||||||
import MembershipFunctionControls from '../components/membershipFunction/Controls';
|
import Controls from '../components/membershipFunction/Controls';
|
||||||
import { calculateValueFunction } from '../services/docService';
|
import { calculateValueFunction } from '../services/docService';
|
||||||
|
|
||||||
const COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#d946ef', '#06b6d4', '#8b5cf6', '#f43f5e', '#6366f1'];
|
const COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#d946ef', '#06b6d4', '#8b5cf6', '#f43f5e', '#6366f1'];
|
||||||
@@ -24,7 +24,7 @@ export default function AdvancedMode() {
|
|||||||
const [selectedTerm, setSelectedTerm] = useState(null);
|
const [selectedTerm, setSelectedTerm] = useState(null);
|
||||||
const [mfDefinitions, setMfDefinitions] = useState({});
|
const [mfDefinitions, setMfDefinitions] = useState({});
|
||||||
|
|
||||||
// --- Manejadores de Escala ---
|
// Manejadores de Escala
|
||||||
const handleCriterionChange = (val) => { setCriterionName(val); if (errors.criterion) setErrors({ ...errors, criterion: false }); };
|
const handleCriterionChange = (val) => { setCriterionName(val); if (errors.criterion) setErrors({ ...errors, criterion: false }); };
|
||||||
const handleLevelChange = (index, newValue) => { const newLevels = [...levels]; newLevels[index] = newValue; setLevels(newLevels); if (errors.levels[index]) setErrors({ ...errors, levels: errors.levels.map((e, i) => i === index ? false : e) }); };
|
const handleLevelChange = (index, newValue) => { const newLevels = [...levels]; newLevels[index] = newValue; setLevels(newLevels); if (errors.levels[index]) setErrors({ ...errors, levels: errors.levels.map((e, i) => i === index ? false : e) }); };
|
||||||
const handleAddLevel = () => { setLevels([...levels, '']); setBlankCards([...blankCards, 0]); setErrors({ ...errors, levels: [...errors.levels, false] }); };
|
const handleAddLevel = () => { setLevels([...levels, '']); setBlankCards([...blankCards, 0]); setErrors({ ...errors, levels: [...errors.levels, false] }); };
|
||||||
@@ -32,9 +32,11 @@ export default function AdvancedMode() {
|
|||||||
const handleBlankCardChange = (index, delta) => { const newCards = [...blankCards]; if (newCards[index] + delta >= 0) { newCards[index] += delta; setBlankCards(newCards); } };
|
const handleBlankCardChange = (index, delta) => { const newCards = [...blankCards]; if (newCards[index] + delta >= 0) { newCards[index] += delta; setBlankCards(newCards); } };
|
||||||
|
|
||||||
const handleGenerateBaseScale = async () => {
|
const handleGenerateBaseScale = async () => {
|
||||||
let hasError = false;
|
|
||||||
const newErrors = { criterion: !criterionName.trim(), levels: levels.map(l => !l.trim()) };
|
const newErrors = { criterion: !criterionName.trim(), levels: levels.map(l => !l.trim()) };
|
||||||
if (newErrors.criterion || newErrors.levels.includes(true)) { setErrors(newErrors); return alert("Por favor, rellena todos los campos."); }
|
if (newErrors.criterion || newErrors.levels.includes(true)) {
|
||||||
|
setErrors(newErrors);
|
||||||
|
return alert("Por favor, rellena todos los campos.");
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -51,17 +53,38 @@ export default function AdvancedMode() {
|
|||||||
} catch (error) { alert("Error: " + error); } finally { setIsLoading(false); }
|
} catch (error) { alert("Error: " + error); } finally { setIsLoading(false); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Manejadores de Franjas ---
|
// Manejadores de Franjas
|
||||||
const updateCurrentMf = (field, value) => {
|
const updateCurrentMf = (field, value) => {
|
||||||
if (!selectedTerm) return;
|
if (!selectedTerm) return;
|
||||||
const numValue = parseFloat(value);
|
let numValue = parseFloat(value);
|
||||||
|
|
||||||
setMfDefinitions(prev => {
|
setMfDefinitions(prev => {
|
||||||
|
const scaleKeys = Object.keys(baseScale);
|
||||||
|
const selectedIndex = scaleKeys.indexOf(selectedTerm);
|
||||||
|
|
||||||
|
let prevCoreEnd = 0, prevSupportEnd = 0, nextCoreStart = 1, nextSupportStart = 1;
|
||||||
|
|
||||||
|
if (selectedIndex > 0) {
|
||||||
|
prevCoreEnd = prev[scaleKeys[selectedIndex - 1]].coreEnd;
|
||||||
|
prevSupportEnd = prev[scaleKeys[selectedIndex - 1]].supportEnd;
|
||||||
|
}
|
||||||
|
if (selectedIndex < scaleKeys.length - 1) {
|
||||||
|
nextCoreStart = prev[scaleKeys[selectedIndex + 1]].coreStart;
|
||||||
|
nextSupportStart = prev[scaleKeys[selectedIndex + 1]].supportStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field === 'supportStart' && numValue < prevCoreEnd) numValue = prevCoreEnd;
|
||||||
|
if (field === 'coreStart' && numValue < prevSupportEnd) numValue = prevSupportEnd;
|
||||||
|
if (field === 'coreEnd' && numValue > nextSupportStart) numValue = nextSupportStart;
|
||||||
|
if (field === 'supportEnd' && numValue > nextCoreStart) numValue = nextCoreStart;
|
||||||
|
|
||||||
const current = { ...prev[selectedTerm], [field]: numValue };
|
const current = { ...prev[selectedTerm], [field]: numValue };
|
||||||
|
|
||||||
if (field === 'supportStart' && current.supportStart > current.coreStart) current.coreStart = current.supportStart;
|
if (field === 'supportStart' && current.supportStart > current.coreStart) current.coreStart = current.supportStart;
|
||||||
if (field === 'coreStart') { if (current.coreStart < current.supportStart) current.supportStart = current.coreStart; if (current.coreStart > current.coreEnd) current.coreEnd = current.coreStart; }
|
if (field === 'coreStart') { if (current.coreStart < current.supportStart) current.supportStart = current.coreStart; if (current.coreStart > current.coreEnd) current.coreEnd = current.coreStart; }
|
||||||
if (field === 'coreEnd') { if (current.coreEnd < current.coreStart) current.coreStart = current.coreEnd; if (current.coreEnd > current.supportEnd) current.supportEnd = current.coreEnd; }
|
if (field === 'coreEnd') { if (current.coreEnd < current.coreStart) current.coreStart = current.coreEnd; if (current.coreEnd > current.supportEnd) current.supportEnd = current.coreEnd; }
|
||||||
if (field === 'supportEnd' && current.supportEnd < current.coreEnd) current.coreEnd = current.supportEnd;
|
if (field === 'supportEnd' && current.supportEnd < current.coreEnd) current.coreEnd = current.supportEnd;
|
||||||
|
|
||||||
return { ...prev, [selectedTerm]: current };
|
return { ...prev, [selectedTerm]: current };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -75,6 +98,7 @@ export default function AdvancedMode() {
|
|||||||
const scaleKeys = Object.keys(baseScale);
|
const scaleKeys = Object.keys(baseScale);
|
||||||
const selectedColor = COLORS[scaleKeys.indexOf(selectedTerm) % COLORS.length] || '#2563eb';
|
const selectedColor = COLORS[scaleKeys.indexOf(selectedTerm) % COLORS.length] || '#2563eb';
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col items-center">
|
<div className="w-full flex flex-col items-center">
|
||||||
|
|
||||||
@@ -122,10 +146,16 @@ export default function AdvancedMode() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MembershipFunctionChart baseScale={baseScale} mfDefinitions={mfDefinitions} selectedTerm={selectedTerm} colors={COLORS} />
|
<Chart baseScale={baseScale} mfDefinitions={mfDefinitions} selectedTerm={selectedTerm} colors={COLORS} />
|
||||||
|
|
||||||
<MembershipFunctionControls selectedTerm={selectedTerm} currentMf={mfDefinitions[selectedTerm]} selectedColor={selectedColor} baseScale={baseScale} updateCurrentMf={updateCurrentMf} />
|
|
||||||
|
|
||||||
|
<Controls
|
||||||
|
selectedTerm={selectedTerm}
|
||||||
|
currentMf={mfDefinitions[selectedTerm]}
|
||||||
|
selectedColor={selectedColor}
|
||||||
|
baseScale={baseScale}
|
||||||
|
mfDefinitions={mfDefinitions}
|
||||||
|
updateCurrentMf={updateCurrentMf}
|
||||||
|
/>
|
||||||
<div className="w-full mt-12 flex justify-center">
|
<div className="w-full mt-12 flex justify-center">
|
||||||
<button onClick={handleFinalSubmit} className="px-12 py-4 bg-slate-900 text-white text-xl font-bold rounded-xl shadow-lg hover:bg-black hover:shadow-xl transition-all">
|
<button onClick={handleFinalSubmit} className="px-12 py-4 bg-slate-900 text-white text-xl font-bold rounded-xl shadow-lg hover:bg-black hover:shadow-xl transition-all">
|
||||||
Guardar Todo el Espectro Difuso
|
Guardar Todo el Espectro Difuso
|
||||||
|
|||||||
Reference in New Issue
Block a user