SVG Unit Display Tool - Part 2

SVG Unit Display Tool - Part 2

I’m pleased to share some recent updates to the SVG Unit Display Tool, a web application aimed at simplifying the visualization of unit data on floor plans. This release includes several new features and improvements based on the initial version.


??? New Features

1. Enhanced Drawing Capabilities:

  • Polygon and Circle Drawing: Users can now create and manage polygons and circles on the floor plan. The tool provides real-time feedback while drawing, allowing adjustments on-the-fly.

2. Improved Unit Interaction:

  • Detailed Unit Information: Clicking on a unit displays detailed information in the sidebar. The selection process is now more intuitive, with options to deselect units as needed.
  • Enhanced Sidebar: The sidebar has been updated to show comprehensive details, including coordinates and dimensions of selected units.

3. Updated Drawing Controls:

  • Tool Selection: A control panel allows switching between polygon and circle drawing modes. The active mode is clearly indicated to prevent confusion.
  • Real-Time Feedback: Visual updates occur as you draw, making it easier to finalize shapes accurately.


??? Sample Code

Here’s a brief overview of the updated code, illustrating how to integrate these new features:

Sidebar.tsx

import React from 'react';
import DrawControl from './DrawControl';
import { CircleUnit, DrawModeState, PolygonUnit } from './interfaces';
import { isCircleUnit, isPolygonUnit } from './typeGuards';
import { Trash } from 'react-bootstrap-icons';

type UnitData = PolygonUnit | CircleUnit;

interface SidebarProps {
  units: UnitData[];
  unit: UnitData | null;
  selectModeState: boolean;
  drawModeState: DrawModeState;
  onSelectModeState: () => void;
  onDrawModeState: (drawState: string) => void;
  onDeleteUnit: (unitID: number) => void;
}

const Sidebar: React.FC<SidebarProps> = ({
  unit,
  units,
  selectModeState,
  drawModeState,
  onSelectModeState,
  onDrawModeState,
  onDeleteUnit,
}) => {
  const renderCoordinates = (unitItem: UnitData = unit!) => {
    if (isPolygonUnit(unitItem)) {
      return (
        <>
          <span><i>Coordinates:</i></span>
          {unitItem.points.map((point, index) => (
            <span key={unitItem.label + index}>
              ({point.x.toFixed(0)}, {point.y.toFixed(0)})
            </span>
          ))}
        </>
      );
    } else if (isCircleUnit(unitItem)) {
      return (
        <span>
          <span><i>Coordinates:</i></span> ({unitItem.cx?.toFixed(0)}, {unitItem.cy?.toFixed(0)}, r: {unitItem.r?.toFixed(0)})
        </span>
      );
    }
    return <span><span><i>Coordinates:</i></span> ({(unitItem as any).x?.toFixed(0)}, {(unitItem as any).y?.toFixed(0)})</span>;
  };

  return (
    <div className="sidebar">
      <section className="sidebar-draw">
        <DrawControl
          selectModeState={selectModeState}
          drawModeState={drawModeState}
          onSelectModeState={onSelectModeState}
          onDrawModeState={onDrawModeState}
        />
      </section>
      <section className="sidebar-selected-unit">
        {unit && (
          <div>
            <span><h3>Selected Unit</h3><p>Label: {unit.label}</p>{renderCoordinates()}</span>
            <span>
              <button onClick={() => onDeleteUnit(unit.id)} className="control-button">
                <Trash color="black" size={20} />
              </button>
            </span>
          </div>
        )}
      </section>
      <section className="sidebar-units">
        <h3>Units</h3>
        {units.length ? units.map((unitItem, index) => (
          <div key={unitItem.label}>
            <span>{index + 1}. <b>{unitItem.label}</b>: </span>
            {renderCoordinates(unitItem)}
          </div>
        )) : <>No unit on the Floor Map. Start Drawing!</>}
      </section>
    </div>
  );
};

export default Sidebar;        

App.tsx

import React, { useEffect, useRef, useState } from 'react';
import FloorPlan from './components/FloorPlan';
import Sidebar from './components/Sidebar';
import './App.css';
import { CircleUnit, PolygonUnit, Point, DrawModeState } from './components/interfaces';
import { isCircleUnit } from './components/typeGuards';

type UnitData = PolygonUnit | CircleUnit;

function App() {
  const shapesCounter = useRef(2);
  const [unitsData, setUnitsData] = useState<UnitData[]>([
    {
      id: 1,
      label: 'Unit 1',
      points: [{ x: 230, y: 180 }, { x: 300, y: 200 }, { x: 260, y: 250 }],
    },
    {
      id: 2,
      label: 'Unit 2',
      cx: 30,
      cy: 30,
      r: 20
    }
  ]);

  const [newShape, setNewShape] = useState<UnitData>({
    id: 0,
    label: '',
    points: [],
    cx: 0,
    cy: 0,
    r: 0,
  });

  const [drawModeState, setDrawModeState] = useState<DrawModeState>({ drawType: '', drawState: false });
  const [selectModeState, setSelectModeState] = useState(true);
  const [selectedUnit, setSelectedUnit] = useState<UnitData | null>(null);

  const handleCreateNewShape = (svgPoint: Point | null, distance?: number) => {
    setNewShape(prevNewShape => {
      if (drawModeState.drawType === 'polygon') {
        return svgPoint ? {
          ...prevNewShape,
          points: [...(prevNewShape as PolygonUnit).points, { x: svgPoint.x, y: svgPoint.y }],
        } : { ...prevNewShape, points: [] };
      } else if (drawModeState.drawType === 'circle' && svgPoint && distance !== undefined) {
        return { ...prevNewShape, cx: svgPoint.x, cy: svgPoint.y, r: distance };
      }
      return prevNewShape;
    });
  };

  const handleAddNewShape = () => {
    shapesCounter.current += 1;

    if (isCircleUnit(newShape) && newShape.r < 20) {
      setNewShape({ id: 0, label: '', cx: 0, cy: 0, r: 0 } as CircleUnit);
      return;
    }

    setUnitsData(prevUnits => [
      ...prevUnits,
      { ...newShape, id: shapesCounter.current, label: `Unit ${shapesCounter.current}` }
    ]);
  };

  const handleSelectModeState = () => {
    setSelectModeState(prevState => !prevState);
    if (!selectModeState) {
      setDrawModeState(prevDrawMode => ({ ...prevDrawMode, drawState: false }));
    } else {
      setSelectedUnit(null);
    }
  };

  const handleDrawModeState = (drawType: string) => {
    setSelectedUnit(null);
    setDrawModeState(prevDrawMode => {
      return !prevDrawMode.drawState || prevDrawMode.drawType !== drawType
        ? { drawType, drawState: true }
        : { drawType, drawState: false };
    });
  };

  const handleDeleteUnit = (unitID: number) => {
    setSelectedUnit(null);
    setUnitsData(prevUnitsData => prevUnitsData.filter(unitData => unitData.id !== unitID));
  };

  const handleUnitClick = (unit: UnitData) => {
    if (selectModeState) {
      setSelectedUnit(prevUnit => prevUnit?.id === unit.id ? null : unit);
      setNewShape(unit);
    }
  };

  useEffect(() => {
    if (drawModeState.drawType === 'polygon') {
      setNewShape({ id: 0, label: '', points: [] } as PolygonUnit);
    } else if (drawModeState.drawType === 'circle') {
      setNewShape({ id: 0, label: '', cx: 0, cy: 0, r: 0 } as CircleUnit);
    }
  }, [drawModeState.drawType]);

  return (
    <div className="App">
      <h1>SVG Unit Display Tool</h1>
      <section className="app-content">
        <Sidebar
          units={unitsData}
          unit={selectedUnit}
          selectModeState={selectModeState}
          drawModeState={drawModeState}
          onSelectModeState={handleSelectModeState}
          onDrawModeState={handleDrawModeState}
          onDeleteUnit={handleDeleteUnit}
        />
        <FloorPlan
          units={unitsData}
          onUnitClick={handleUnitClick}
          selectedUnitId={selectedUnit?.id}
          drawModeState={drawModeState}
          onCreateNewShape={handleCreateNewShape}
          onAddNewShape={handleAddNewShape}
        />
      </section>
    </div>
  );
}

export default App;        


?? Looking Forward

This update represents progress in the development of the SVG Unit Display Tool, but there is always potential for further improvement.

If you're curious to see how it works, check out the CodeSandbox where I've set up a live demo. Feel free to explore the code and try it out yourself!

You can explore the project and contribute to its development through the GitHub repository.

Thank you for your time.

要查看或添加评论,请登录

Adrian Birta的更多文章

社区洞察

其他会员也浏览了