Spaces:
Sleeping
Sleeping
| import { useEffect, useRef } from 'react'; | |
| export default function DangVanChart({ result }) { | |
| const canvasRef = useRef(null); | |
| useEffect(() => { | |
| if (!result || !canvasRef.current) return; | |
| const canvas = canvasRef.current; | |
| const ctx = canvas.getContext('2d'); | |
| const { width, height } = canvas; | |
| // Clear | |
| ctx.fillStyle = '#1a1a2e'; | |
| ctx.fillRect(0, 0, width, height); | |
| const padding = { left: 70, right: 40, top: 40, bottom: 60 }; | |
| const plotWidth = width - padding.left - padding.right; | |
| const plotHeight = height - padding.top - padding.bottom; | |
| // Calculer les bornes | |
| const points = result.points || []; | |
| const sigmaHValues = points.map(p => p.sigma_H); | |
| const tauValues = points.map(p => p.tau_max); | |
| // Inclure la ligne de critère dans les bornes | |
| const criterionLine = result.criterion_line || []; | |
| const allSigmaH = [...sigmaHValues, ...criterionLine.map(p => p.sigma_H)]; | |
| const allTau = [...tauValues, ...criterionLine.map(p => p.tau)]; | |
| const sigmaHMin = Math.min(...allSigmaH) * 1.1; | |
| const sigmaHMax = Math.max(...allSigmaH) * 1.1; | |
| const tauMin = 0; | |
| const tauMax = Math.max(...allTau) * 1.2; | |
| // Fonctions de transformation | |
| const toX = (sigmaH) => padding.left + ((sigmaH - sigmaHMin) / (sigmaHMax - sigmaHMin)) * plotWidth; | |
| const toY = (tau) => padding.top + plotHeight - ((tau - tauMin) / (tauMax - tauMin)) * plotHeight; | |
| // Grille | |
| ctx.strokeStyle = '#333'; | |
| ctx.lineWidth = 1; | |
| ctx.setLineDash([5, 5]); | |
| // Lignes verticales | |
| const numGridLines = 5; | |
| for (let i = 0; i <= numGridLines; i++) { | |
| const sigmaH = sigmaHMin + (i / numGridLines) * (sigmaHMax - sigmaHMin); | |
| const x = toX(sigmaH); | |
| ctx.beginPath(); | |
| ctx.moveTo(x, padding.top); | |
| ctx.lineTo(x, padding.top + plotHeight); | |
| ctx.stroke(); | |
| } | |
| // Lignes horizontales | |
| for (let i = 0; i <= numGridLines; i++) { | |
| const tau = tauMin + (i / numGridLines) * (tauMax - tauMin); | |
| const y = toY(tau); | |
| ctx.beginPath(); | |
| ctx.moveTo(padding.left, y); | |
| ctx.lineTo(padding.left + plotWidth, y); | |
| ctx.stroke(); | |
| } | |
| ctx.setLineDash([]); | |
| // Axes | |
| ctx.strokeStyle = '#666'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(padding.left, padding.top); | |
| ctx.lineTo(padding.left, padding.top + plotHeight); | |
| ctx.lineTo(padding.left + plotWidth, padding.top + plotHeight); | |
| ctx.stroke(); | |
| // Labels des axes | |
| ctx.fillStyle = '#aaa'; | |
| ctx.font = '12px system-ui'; | |
| ctx.textAlign = 'center'; | |
| // Axe X | |
| ctx.fillText('σH (MPa)', padding.left + plotWidth / 2, height - 15); | |
| for (let i = 0; i <= numGridLines; i++) { | |
| const sigmaH = sigmaHMin + (i / numGridLines) * (sigmaHMax - sigmaHMin); | |
| const x = toX(sigmaH); | |
| ctx.fillText(sigmaH.toFixed(0), x, padding.top + plotHeight + 20); | |
| } | |
| // Axe Y | |
| ctx.save(); | |
| ctx.translate(20, padding.top + plotHeight / 2); | |
| ctx.rotate(-Math.PI / 2); | |
| ctx.fillText('τmax (MPa)', 0, 0); | |
| ctx.restore(); | |
| ctx.textAlign = 'right'; | |
| for (let i = 0; i <= numGridLines; i++) { | |
| const tau = tauMin + (i / numGridLines) * (tauMax - tauMin); | |
| const y = toY(tau); | |
| ctx.fillText(tau.toFixed(0), padding.left - 10, y + 4); | |
| } | |
| // Ligne de critère (zone dangereuse au-dessus) | |
| if (criterionLine.length >= 2) { | |
| const x1 = toX(criterionLine[0].sigma_H); | |
| const y1 = toY(criterionLine[0].tau); | |
| const x2 = toX(criterionLine[1].sigma_H); | |
| const y2 = toY(criterionLine[1].tau); | |
| // Zone dangereuse (au-dessus de la ligne) | |
| ctx.fillStyle = 'rgba(255, 100, 100, 0.1)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(x1, y1); | |
| ctx.lineTo(x2, y2); | |
| ctx.lineTo(x2, padding.top); | |
| ctx.lineTo(x1, padding.top); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Zone sûre (en dessous) | |
| ctx.fillStyle = 'rgba(100, 255, 150, 0.1)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(x1, y1); | |
| ctx.lineTo(x2, y2); | |
| ctx.lineTo(x2, padding.top + plotHeight); | |
| ctx.lineTo(x1, padding.top + plotHeight); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Ligne de critère | |
| ctx.strokeStyle = '#ff6b6b'; | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| ctx.moveTo(x1, y1); | |
| ctx.lineTo(x2, y2); | |
| ctx.stroke(); | |
| // Label de la ligne | |
| ctx.fillStyle = '#ff6b6b'; | |
| ctx.font = 'bold 11px system-ui'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText(`τ = ${result.b} - ${result.a}σH`, x2 - 120, y2 - 10); | |
| } | |
| // Points (σH, τmax) | |
| points.forEach((point, i) => { | |
| const x = toX(point.sigma_H); | |
| const y = toY(point.tau_max); | |
| // Couleur selon si le point est critique ou non | |
| const isCritical = point.dv > result.b; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 4, 0, Math.PI * 2); | |
| ctx.fillStyle = isCritical ? '#ff6b6b' : '#00d4ff'; | |
| ctx.fill(); | |
| // Marquer le point max | |
| if (i === result.index_max) { | |
| ctx.strokeStyle = '#ffd700'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 8, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Label | |
| ctx.fillStyle = '#ffd700'; | |
| ctx.font = 'bold 10px system-ui'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText('MAX', x + 12, y + 4); | |
| } | |
| }); | |
| // Légende | |
| const legendX = padding.left + 10; | |
| const legendY = padding.top + 10; | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
| ctx.fillRect(legendX, legendY, 150, 70); | |
| ctx.font = '11px system-ui'; | |
| // Point sûr | |
| ctx.beginPath(); | |
| ctx.arc(legendX + 10, legendY + 15, 4, 0, Math.PI * 2); | |
| ctx.fillStyle = '#00d4ff'; | |
| ctx.fill(); | |
| ctx.fillStyle = '#aaa'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText('Points sûrs', legendX + 20, legendY + 19); | |
| // Point critique | |
| ctx.beginPath(); | |
| ctx.arc(legendX + 10, legendY + 35, 4, 0, Math.PI * 2); | |
| ctx.fillStyle = '#ff6b6b'; | |
| ctx.fill(); | |
| ctx.fillStyle = '#aaa'; | |
| ctx.fillText('Points critiques', legendX + 20, legendY + 39); | |
| // Ligne de critère | |
| ctx.strokeStyle = '#ff6b6b'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(legendX + 5, legendY + 55); | |
| ctx.lineTo(legendX + 15, legendY + 55); | |
| ctx.stroke(); | |
| ctx.fillStyle = '#aaa'; | |
| ctx.fillText('Limite de Dang Van', legendX + 20, legendY + 59); | |
| }, [result]); | |
| return ( | |
| <div className="dangvan-chart"> | |
| <canvas | |
| ref={canvasRef} | |
| width={800} | |
| height={500} | |
| /> | |
| <div className="chart-help"> | |
| Diagramme de Dang Van : τ<sub>max</sub> vs σ<sub>H</sub> | |
| </div> | |
| </div> | |
| ); | |
| } | |