import React, { useEffect, useRef, useState } from "react"; import * as d3 from "d3"; import { formatNumber } from "~/lib/utils"; export type CompanyInfo = { id: string; imageUrl: string; name: string; costReduction: number; // absolute value revenue?: number; capacity?: number; }; export type D3ImageInfoProps = { companies: CompanyInfo[]; // exactly 6 items width?: number; height?: number; }; export function D3ImageInfo({ companies, width = 900, height = 400 }: D3ImageInfoProps) { const containerRef = useRef(null); const svgRef = useRef(null); const draw = () => { if (!containerRef.current || !svgRef.current) return; const container = containerRef.current; const svg = d3.select(svgRef.current); const W = Math.max(480, container.clientWidth || width); const H = Math.max(300, height); svg.attr("width", W).attr("height", H); svg.selectAll("*").remove(); const padding = 10; const cols = 3; const rows = 2; const boxWidth = (W - padding * (cols + 1)) / cols; const boxHeight = (H - padding * (rows + 1)) / rows; const group = svg.append("g").attr("transform", `translate(${padding}, ${padding})`); companies.forEach((company, i) => { const col = i % cols; const row = Math.floor(i / cols); const x = col * (boxWidth + padding); const y = row * (boxHeight + padding); const companyGroup = group.append("g").attr("transform", `translate(${x}, ${y})`); // Draw background box companyGroup .append("rect") .attr("width", boxWidth) .attr("height", boxHeight) .attr("rx", 10) .attr("ry", 10) .attr("fill", "transparent") // Draw image const imgSize = Math.min(boxWidth, boxHeight) * 0.5; companyGroup .append("image") .attr("href", company.imageUrl) .attr("x", 10) .attr("y", 10) .attr("width", imgSize) .attr("height", imgSize) .attr("preserveAspectRatio", "xMidYMid slice") .style("background", "transparent"); // Adjust positions to match picture // Position image slightly left and info box to right with spacing const infoX = imgSize + 30; const infoY = 10; const infoWidth = 120; const infoHeight = imgSize; const infoGroup = companyGroup.append("g"); infoGroup .append("rect") .attr("x", infoX) .attr("y", infoY) .attr("width", infoWidth) .attr("height", infoHeight) .attr("rx", 10) .attr("ry", 10) .attr("fill", "transparent") .attr("stroke", "#3F415A") .attr("stroke-width", 1); // Add text inside info box const lineHeight = 20; infoGroup .append("text") .attr("x", imgSize + 10 ) .attr("y", infoY + imgSize + 10) .attr("fill", "#FFFFFF") .attr("font-weight", "700") .attr("font-size", 14) .text(company.name); infoGroup .append("text") .attr("x", infoX + imgSize) .attr("y", infoY + lineHeight ) .attr("fill", "#FFFFFF") .attr("font-size", 12) .text(`درآمد: ${formatNumber(company?.revenue || "0")}`); infoGroup .append("text") .attr("x", infoX + imgSize -20 ) .attr("y", infoY + lineHeight +5 ) .attr("fill", "#ACACAC") .attr("font-size", 6) .text(`میلیون ریال`); infoGroup .append("text") .attr("x", infoX + imgSize) .attr("y", infoY + lineHeight *2 ) .attr("fill", "#FFFFFF") .attr("font-size", 12) .text(`هزینه: ${formatNumber(company?.cost || "0")} میلیون ریال`); infoGroup .append("text") .attr("x", infoX + imgSize) .attr("y", infoY + lineHeight * 3 ) .attr("fill", "#FFFFFF") .attr("font-size", 12) .text(`ظرفیت: ${formatNumber(company?.capacity || "0")} تن در سال`); // Remove click handlers and popup companyGroup.style("cursor", "default"); }); }; useEffect(() => { draw(); const ro = new ResizeObserver(() => draw()); if (containerRef.current) ro.observe(containerRef.current); return () => ro.disconnect(); }, [companies, width, height]); return (
); }