const Cup = ({workerRef, symbol, settings, setShowSettings}: CupProps) => {
  const cupParams = useSelector((state: AppState) => state.cupSlice);
  const [dpiScale, setDpiScale] = useState(Math.ceil(window.devicePixelRatio));
  const [canvasSize, setCanvasSize] = useState<CanvasSize>({height: 0, width: 0});
  const containerRef = useRef<HTMLDivElement|null>(null);
  const canvasRef = useRef<HTMLCanvasElement|null>(null);
  const [zoom, setZoom] = useState(1);
 const cupSubscribe = useCallback(async(pair: string, zoom: number) => {
    workerRef.current?.postMessage(JSON.stringify({type: "subscribe", pair, zoom}));
  }, []);

  const cupUnsubscribe = useCallback(async(pair: string) => {
    workerRef.current?.postMessage(JSON.stringify({type: "unsubscribe", pair}));
  }, []);
 const zoomAdd = () => {
    let newZoom;

    if (zoom >= 1 && zoom < 10) {
      newZoom = zoom + 1;
    } else if (zoom >= 10 && zoom < 30) {
      newZoom = zoom + 5;
    } else if (zoom >= 30 && zoom < 100) {
      newZoom = zoom + 10;
    } else {
      newZoom = zoom;
    }

    setZoom(newZoom);
  };

  const zoomSub = () => {
    let newZoom;

    if (zoom > 1 && zoom <= 10) {
      newZoom = zoom - 1;
    } else if (zoom > 10 && zoom <= 30) {
      newZoom = zoom - 5;
    } else if (zoom > 30 && zoom <= 100) {
      newZoom = zoom - 10;
    } else {
      newZoom = zoom;
    }

    setZoom(newZoom);
  };


  useEffect(() => {
    workerRef.current = new Worker(new URL("/workers/cup-builder.ts", import.meta.url));
    canvasRef.current?.addEventListener("wheel", wheelHandler, {passive: false});
    sendSettings();

    return () => {
      workerRef.current?.terminate();
      canvasRef.current?.removeEventListener("wheel", wheelHandler);
    };
  }, []);

  useEffect(() => {
    if (!workerRef.current) return;
    let animationFrameId: number|null = null;

    workerRef.current.onmessage = (event: MessageEvent<{
      type: string,
      camera: number,
      aggregation: number,
      bestBidPrice: number,
      bestAskPrice: number,
      maxVolume: number,
      pricePrecision: number,
      quantityPrecision: number,
      priceStep: number,
      cup: {[key: number]: CupItem},
      rowsCount: number,
      volumeAsDollars: boolean,
    }>) => {
      if (event?.data?.type === "update_cup") {
        if (null !== animationFrameId) {
          cancelAnimationFrame(animationFrameId);
        }

        animationFrameId = requestAnimationFrame(() => {
          const context = canvasRef.current?.getContext("2d");
          const zoomedTickSize = event.data.priceStep * event.data.aggregation;

          if (context) {
            const rowsOnScreenCount = cupTools.getRowsCountOnScreen(
              canvasSize.height,
              cupOptions().cell.defaultHeight * dpiScale,
            );
            const realCellHeight = parseInt((canvasSize.height / rowsOnScreenCount).toFixed(0));

            if (event.data.rowsCount !== rowsOnScreenCount) {
              workerRef.current?.postMessage(JSON.stringify({type: "change_rows_count", value: rowsOnScreenCount}));
            }

            cupDrawer.clear(context, canvasSize);

            if (cupParams.rowCount !== rowsOnScreenCount
              || cupParams.cellHeight !== realCellHeight
              || cupParams.aggregation !== event.data.aggregation
            ) {
              dispatch(setCupParams({
                aggregation: event.data.aggregation,
                rowCount: rowsOnScreenCount,
                cellHeight: realCellHeight,
                pricePrecision: event.data.pricePrecision,
                priceStep: event.data.priceStep,
                quantityPrecision: event.data.quantityPrecision,
              }));
            }

            if (event.data.camera === 0 && isLoaded) {
              setIsLoaded(false);
            }

            if (event.data.camera > 0 && !isLoaded) {
              setIsLoaded(true);
            }

            if (event.data.camera !== 0) {
              cupDrawer.draw(
                context,
                canvasSize,
                dpiScale,
                event.data.bestBidPrice,
                event.data.bestAskPrice,
                event.data.maxVolume,
                event.data.pricePrecision,
                event.data.quantityPrecision,
                event.data.priceStep,
                event.data.aggregation,
                rowsOnScreenCount,
                event.data.camera,
                realCellHeight,
                {
                  buy: parseInt((Math.floor(event.data.bestBidPrice / zoomedTickSize) * zoomedTickSize).toFixed(0)),
                  sell: parseInt((Math.ceil(event.data.bestAskPrice / zoomedTickSize) * zoomedTickSize).toFixed(0)),
                },
                darkMode,
                event.data.volumeAsDollars,
                event.data.cup,
              );
            }
          }
        });
      }
    };

    return () => {
      if (null !== animationFrameId) {
        cancelAnimationFrame(animationFrameId);
      }
    };
  }, [workerRef.current, canvasSize, darkMode, dpiScale, isLoaded]);
 return <div ref={containerRef} className={styles.canvasWrapper}>
  <canvas
      ref={canvasRef}
      className={[styles.canvas, isLoaded ? "" : styles.loading].join(" ")}
      width={canvasSize?.width}
      height={canvasSize?.height}
    />
  </div>;
};

export default Cup;


const cupDrawer = {
  clear: (ctx: CanvasRenderingContext2D, size: CanvasSize) => {
    ctx.clearRect(0, 0, size.width, size.height);
  },
  draw: (
    ctx: CanvasRenderingContext2D,
    size: CanvasSize,
    dpiScale: number,
    bestBidPrice: number,
    bestAskPrice: number,
    maxVolume: number,
    pricePrecision: number,
    quantityPrecision: number,
    priceStep: number,
    zoom: number,
    rowCount: number,
    camera: number,
    cellHeight: number,
    zoomedBestMicroPrice: any,
    darkMode: boolean,
    volumeAsDollars: boolean,
    cup: {[key: number]: CupItem},
  ) => {
    const zoomedTickSize = priceStep * zoom;
    const startMicroPrice = camera + rowCount / 2 * zoomedTickSize - zoomedTickSize;
  for (let index = 0; index <= rowCount; index++) {
      const microPrice = startMicroPrice - index * zoomedTickSize;

      if (microPrice < 0) continue;

      const item = cup[microPrice] || {};
      let side = "undefined" !== typeof item?.bid
        ? "bid"
        : "undefined" !== typeof item?.ask ? "ask" : "";

      if ("" === side || (!item?.bid && !item.ask)) {
        side = (microPrice >= (zoomedBestMicroPrice?.buy || zoomedBestMicroPrice?.sell || 0) ? "ask" : "bid");
      }

      const yPosition = cellHeight * index;
      const quantity = volumeAsDollars
        ? ((side === "bid" ? item?.bid || 0 : item?.ask || 0) / quantityDivider) * (microPrice / priceDivider)
        : (side === "bid" ? item?.bid || 0 : item?.ask || 0) / quantityDivider;

      const isBestPrice = cupTools.isBestPrice(
        side,
        microPrice,
        zoomedBestMicroPrice,
      );

   cupDrawer.drawCell(
        ctx,
        quantity,
        side,
        maxVolume,
        isBestPrice,
        yPosition,
        cellHeight,
        size.width,
        dpiScale,
        darkMode
      );
   cupDrawer.drawPrice(
        ctx,
        cupTools.microPriceToReal(microPrice, pricePrecision),
        side,
        isBestPrice,
        yPosition,
        cellHeight,
        size.width,
        dpiScale,
        darkMode
      );
};

export default cupDrawer;

изменяется aggregatedPriceStep, aggregation и zoom это сжатие.
Есть центровка стакана, при сжатии больше 1, возможно расчет идет неправильный на центровку стакана.
Есть проблема. ПРОАНАЛИЗИРУЙ КОД, НАЙДИ ОШИБКУ