Issue
I've searched for this a lot, including GPT4, and I was not able to find a working answer.
Detailed description:
- I have an array of points, which are drawn inside an svg as small circles. The svg is inside a container together with a title. I'm working with tailwind here and almost everything is flex based. The container itself is a "flex flex-col w-full flex-1" So it takes all space, and the svg tag also have "w-full flex-1" to stretch itself to fit the remaining space on the container after the title.
- I want this to be responsive (and its not working, after making the window smaller the svg keeps a small width instead of actually being 100% of the container's width).
- I want all the points rendered inside the svg to be centered (what I mean by that is that if there only one point at 0;0, I want 0;0 to be at the center of the figure, and If I have, lets say, points 0;0 and 2;2, I want the svg to be centered at the medium point 1;1).
- The svg tag itself should fit the entire remaining space of the container, but its viewBox should recalculate everytime a point is added or the window is resized so that all the points are shown (centered) and with a minimum padding around the smaller dimension at the time.
This is what I've got so far, with some comments:
const PreviewPanel = () => {
//stuff to get the array of points called pointsArr
const viewBox = useMemo(() => {
//Find min and max points to define bounds
let minX = pointsArr[0]!.coords.x;
let maxX = pointsArr[0]!.coords.x;
let minY = pointsArr[0]!.coords.y;
let maxY = pointsArr[0]!.coords.y;
store.points.forEach((point) => {
minX = Math.min(minX, point.coords.x);
maxX = Math.max(maxX, point.coords.x);
minY = Math.min(minY, point.coords.y);
maxY = Math.max(maxY, point.coords.y);
});
const pointsWidth = maxX - minX;
const pointsHeight = maxY - minY;
const padding = 0.1;
//Not in use right now, but I've tried it.
//const centroidX = pointsWidth / 2;
//const centroidY = pointsHeight / 2;
const viewBoxX = minX - pointsWidth * padding;
const viewBoxY = -maxY + pointsHeight * padding;
const viewBoxWidth = pointsWidth * (1 + 2 * padding);
const viewBoxHeight = pointsHeight * (1 + 2 * padding);
return `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`;
}, [store]);
return (
<div className="flex w-full flex-1 flex-col items-center rounded-md border-2 border-c_discrete max-h-full">
<div className="border-b-2 border-b-c_discrete">{title}</div>
<div className="flex w-full flex-1 border-2 border-green-400">
<svg
//width="100%" //<--Setting width and height here didnt work
//height="100%"
viewBox={viewBox}
//preserveAspectRatio="xMidYMid meet" //<---I've tried this too
className="border-2 border-black" //I've tried setting flex-1 or w-full h-full here too
>
<g transform={`scale(1, -1)`}>
{pointsArr.map((point, index) => {
return (
<path
key={"svg_path_"+point.id}
d={getPointPath(point)} //<--This is just a function that calculates the path, and its working fine
stroke={stroke}
strokeWidth="0.05"
fill={fill}
/>
);
})}
</g>
</svg>
</div>
</div>
);
};
Problems:
Svg tag is taking the whole width and height of the container until I rescale the window to a smaller size and then back, the width keeps its value when it was smaller.
When adding points svg becomes way taller then it should, and it overflows. I don't want to deal with overflow because I want it to always fit exactly the container (or what it remains after the title).
Points are not being centered on the image
Solution
Got it by creating a custom 'useDimensions' hook, that keeps track of the container dimensions when resizing.
This is the custom hook:
function useDimensions<T extends HTMLElement = HTMLDivElement>() {
const ref = useRef<T|null>(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
function updateSize() {
if(ref.current) {
setDimensions({
width: ref.current.offsetWidth,
height: ref.current.offsetHeight
});
}
}
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, [ref]);
return [ref, dimensions] as const;
}
export default useDimensions;
This is inside the component:
const [ref, dimensions] = useDimensions();
const svgRef = useRef<SVGSVGElement>(null);
const [viewBox, setViewBox] = useState("0 0 100 100");
const [svgDim, setSvgDim] = useState({ width: 200, height: 200 });
useEffect(() => {
if (!store || !store.points || store.points.size === 0) return;
const pointsArr = Array.from(store.points.values());
//Find min and max points to define bounds
let minX = pointsArr[0]!.coords.x;
let maxX = pointsArr[0]!.coords.x;
let minY = pointsArr[0]!.coords.y;
let maxY = pointsArr[0]!.coords.y;
store.points.forEach((point) => {
minX = Math.min(minX, point.coords.x);
maxX = Math.max(maxX, point.coords.x);
minY = Math.min(minY, point.coords.y);
maxY = Math.max(maxY, point.coords.y);
});
let pointsWidth = maxX - minX;
let pointsHeight = maxY - minY;
let padding = 0.5; //of the maximum dimension
if (pointsArr.length == 1 && pointsArr[0]) {
pointsWidth = pointsArr[0].size * 2;
pointsHeight = pointsWidth;
padding = 10;
}
const viewAR = pointsWidth / pointsHeight;
padding =
viewAR >= 1 ? (padding *= pointsWidth) : (padding *= pointsHeight);
let viewBoxX = +(minX - padding);
let viewBoxY = -(maxY + padding);
let viewBoxWidth = pointsWidth + 2 * padding;
let viewBoxHeight = pointsHeight + 2 * padding;
const pixelAR = dimensions.width / dimensions.height;
if (viewAR >= pixelAR) {
//image is fatter than container, so image width needs to be cap set to the container width
//and the image height should be set to keep viewAR
setSvgDim({
width: dimensions.width,
height: dimensions.width / viewAR,
});
} else {
//image is taller than container, so image height should be cap set to container height
//and image width should be set to keep viewAR
setSvgDim({
width: viewAR * dimensions.height,
height: dimensions.height,
});
}
setViewBox(`${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`);
}, [store, store?.points, ref, svgRef, dimensions.width, dimensions.height]);
This is the svg tag in the return:
<svg
width={svgDim.width > 0 ? svgDim.width : "100%"}
height={svgDim.height > 0 ? svgDim.height : "100%"}
viewBox={viewBox}
preserveAspectRatio="xMidYMid"
className="border-2 border-c_disabled2 border-opacity-10"
ref={svgRef}
>
Answered By - Daniel Guedes
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.