Помогите исправить проблему с определением вершин при рисовании на Canvas

Рейтинг: 0Ответов: 0Опубликовано: 25.03.2023

Моя задача от руки нарисовать некий график, а затем определить где были самые высшие точки и самые нижние. Все работает, но если рисовать очень медленно, если же рисовать быстро, то определяется не правильно. Помогите исправить, спасибо!


Edit cool-jones-tcfpfu

import { createRef, MouseEvent, useEffect, useRef, useState } from "react";

type Point = { x: number; y: number; };


const CANVAS_SIZE: [ width: number, height: number ] = [ 980, 600 ];
const LINE_STROKE =
    {
        width: 5,
        color: "#FFFFFF"
    } as const;


function findPeakIndex(points: Point[])
{
    let maxIndex: null | number = null;
    let maxValue = -Infinity;
    for (let i = 0; i < points.length; i++)
    {
        if (points[ i ].y > maxValue)
        {
            maxIndex = i;
            maxValue = points[ i ].y;
        }
    }
    return maxIndex;
}

function findValleyIndex(paths: Point[])
{
    let minIndex: number | null = null;
    let minValue = Infinity;
    for (let i = 0; i < paths.length; i++)
    {
        if (paths[ i ].y < minValue)
        {
            minIndex = i;
            minValue = paths[ i ].y;
        }
    }
    return minIndex;
}

function findPeaksAndValleys(path: Point[])
{
    // Smooth path with moving average filter
    const smoothPath = path.map((point) => point.y);
    const smoothWindowSize = 20;
    for (let i = 0; i < smoothPath.length; i++)
    {
        const windowStart = Math.max(0, i - smoothWindowSize);
        const windowEnd = Math.min(smoothPath.length - 1, i + smoothWindowSize);
        const window = smoothPath.slice(windowStart, windowEnd + 1);
        const windowSum = window.reduce((a, b) => a + b, 0);
        smoothPath[ i ] = windowSum / window.length;
    }

    // Find peaks and valleys based on filtered path
    const peaks: Point[] = [];
    let peakStart: null | number = null;
    let peakEnd: null | number = null;
    let peakMax = -Infinity;
    let peakThreshold = 0.05; // Initial threshold for peak detection

    for (let i = 0; i < smoothPath.length; i++)
    {
        const value = smoothPath[ i ];
        if (value > peakMax)
        {
            peakMax = value;
            peakEnd = i;
        }
        else if (peakStart !== null && peakEnd !== null)
        {
            const peakIndex = findPeakIndex(path.slice(peakStart, peakEnd + 1));
            if (peakIndex !== null)
            {
                peaks.push(path[ peakStart + peakIndex ]);
            }
            peakStart = null;
            peakEnd = null;
            peakMax = -Infinity;
            peakThreshold = 0.05; // Reset threshold for peak detection
        }
        if (peakStart === null || value > peakMax - peakThreshold * peakMax)
        {
            peakStart = i;
            peakMax = value;
        }
        else if (value < peakMax - peakThreshold * peakMax)
        {
            peakThreshold = 0.01; // Decrease threshold for peak detection
        }
    }

    const valleys: Point[] = [];
    let valleyStart: null | number = null;
    let valleyEnd: null | number = null;
    let valleyMin = Infinity;
    let valleyThreshold = 0.05; // Initial threshold for valley detection

    for (let i = 0; i < smoothPath.length; i++)
    {
        const value = smoothPath[ i ];
        if (value < valleyMin)
        {
            valleyMin = value;
            valleyEnd = i;
        }
        else if (valleyStart !== null && valleyEnd !== null)
        {
            const valleyIndex = findValleyIndex(path.slice(valleyStart, valleyEnd + 1));
            if (valleyIndex !== null)
            {
                valleys.push(path[ valleyStart + valleyIndex ]);
            }
            valleyStart = null;
            valleyEnd = null;
            valleyMin = Infinity;
            valleyThreshold = 0.05; // Reset threshold for valley detection
        }
        if (valleyStart === null || value < valleyMin + valleyThreshold * valleyMin)
        {
            valleyStart = i;
            valleyMin = value;
        }
        else if (value > valleyMin + valleyThreshold * valleyMin)
        {
            valleyThreshold = 0.01; // Decrease threshold for valley detection
        }
    }

    return { peaks, valleys };
}

export default function App() {
    const loadRef = useRef<boolean>(false);
    const canvasRef = createRef<HTMLCanvasElement>();
    const contextRef = useRef<CanvasRenderingContext2D>();
    const [ isDrawing, setIsDrawing ] = useState<boolean>(false);
    const [ path, setPath ] = useState<Point[]>([]);

    // useEffect(() => console.info(path), [ path ]);
    // Effects 
    useEffect(() =>
    {
        function prepareCanvas()
        {
            const canvas = canvasRef.current!;
            canvas.width = CANVAS_SIZE[ 0 ];
            canvas.height = CANVAS_SIZE[ 1 ];
            canvas.style.width = CANVAS_SIZE[ 0 ] + "px";
            canvas.style.height = CANVAS_SIZE[ 1 ] + "px";

            const ctx = canvas.getContext("2d")!;

            ctx.lineCap = "round";
            ctx.shadowColor = "rgba(126, 196, 255, 0.102)";
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 5;
            ctx.shadowBlur = 10;

            contextRef.current = ctx;
        }

        if (loadRef.current === false)
        {
            loadRef.current = true;
            prepareCanvas();
        }
    }, []);

  // Handles
    function clearCanvas()
    {
        contextRef.current!.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height);
  }
  
    function startDrawing({ nativeEvent }: MouseEvent<HTMLCanvasElement>)
    {
        if (!contextRef.current)
        {
            return;
        }

        clearCanvas();

        const { offsetX: x, offsetY: y } = nativeEvent;
        const ctx = contextRef.current;

        ctx.strokeStyle = LINE_STROKE.color;
        ctx.lineWidth = LINE_STROKE.width;

        ctx.beginPath();
        ctx.moveTo(x, y);

        setIsDrawing(true);
        setPath([ { x, y } ]);
    }

    function finishDrawing()
    {
        if (!contextRef.current)
        {
            return;
        }

        setIsDrawing(false);

        const ctx = contextRef.current;
        const { peaks, valleys } = findPeaksAndValleys(path);
        
        ctx.closePath();

        // Draw incursions
        const circleRadius = 10;

        // Peaks
        ctx.fillStyle = "red";
        peaks.forEach((peak) =>
        {
            ctx.beginPath();
            ctx.arc(peak.x, peak.y, circleRadius, 0, 2 * Math.PI);
            ctx.fill();
        });

        // Valleys
        ctx.fillStyle = "blue";
        valleys.forEach((valley) =>
        {
            ctx.beginPath();
            ctx.arc(valley.x, valley.y, circleRadius, 0, 2 * Math.PI);
            ctx.fill();
        });
    }

    function onDraw({ nativeEvent }: MouseEvent<HTMLCanvasElement>)
    {
        if (!contextRef.current || !isDrawing)
        {
            return;
        }

        const { offsetX: x, offsetY: y } = nativeEvent;
        const ctx = contextRef.current;

        ctx.lineTo(x, y);
        ctx.stroke();

        setPath((prevPath) => [ ...prevPath, { x, y } ]);
    }

    // Render
    return (
        <canvas
            ref={canvasRef}
            onMouseDown={startDrawing}
            onMouseUp={finishDrawing}
            onMouseLeave={finishDrawing}
            onMouseMove={onDraw}
            style={{ backgroundColor: "#FFF", borderRadius: "16px", border: "1px solid black" }}
        />
    );
}


Проблема при быстром рисовании:

введите сюда описание изображения


Правильное отображение (если рисовать медленно):

введите сюда описание изображения

Ответы

Ответов пока нет.