[as] design: 대시보드 전국 행정지원 지도 레이아웃 구현
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 538 755">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<!-- Generator: Adobe Illustrator 28.7.9, SVG Export Plug-In . SVG Version: 1.2.0 Build 218) -->
|
||||
<g>
|
||||
<g id="Layer_1">
|
||||
<g id="han" data-label-shift="dx,dy">
|
||||
<path class="cls-1" d="M159.2,197l-5,5.8,5.1,3.7,13.2,9.7-7.3,3.5-8.2-1.2-9.4,1.4,1.4,4.9-3.5,7.3,3.8,2.6-.9,4.7,8.9-2.3,4.2-3.7,5.2,4.2-6.1,2.3.9,12,3,7-.9,3.3-.5,1.6,5.4,6.8,5.2,13.4h-3.6c0-.1,6.4,3.8,6.4,3.8l4.8,6h1.2l3.6-5.4,3.6-.6,13.8,5.9,19.7-1.1,4.6-6.5,3-5.2,8.2-1.7,11.7-17.8,7.1,1.9,4.9,10,13.2,9.6,3.8,13-1.4,12.2-.5,9.5,1.6,1.1-.6,1.3v1.1c.1,0-2.2,2.4-2.2,2.4l-17.5,7.4,7.6,19.2,16.3,1.1,8.4,10.8,12.6.6,4.2-5.2-4.6-7.9-9.2-6.2,12.1-7.1,6.4,5.2,1.3-5.6-6.6-6.5,17.5-5.8,4.8,4.4,4.8-2.1-4.7-6.5,4.5-8.8,6.3-.4,4.7-3,8,3,24.1,4.5,11.5-7.8-2.8-6.4,6.5-11.7,10.5-5.5,7.6-7.9,10.2-2.3,13.4,4.6,4.4-8,12.4,8.2,3.4-3.1.2-15.2.4-11.3,13.4-3.2,4.6-.4-1.9,9.9,9.8,4.6,1.6,12.8,12.2,9.2,9.8-8.4,15-8.9-3.7-19-21.8-40.6-5.7-10.6,1.2-6.8-4.9-6.1-4-4.9,1.2-5.2-26.3-33.1-5.8-12.1-7.1-14.9-11.9-20.9-2.9-9.4-2.1-6.7-1.8-5.2-8.7-25.5-8.6-14.4-.9-11-5.2-11-3-6.3-6,6.5-5.2,22.8-9.8,11-5.5,2.8-7.4,3.7-17.1.5-12.7-1.6-8.2,2.3-5.2-3.4-3.4-2.2-18.8-2.3-21.8,4.5-19-3.7-3.5,4.7-11.2-7.3-5.2,4.2-6.1-.7-11.5,3.5-14.1,10.8-3.7,10.1-12.9,9.8-4,7.3-8.5,6.8-2.3,1.9-9.1,4.5-4,18.3-9.2,11.5-14.3,1.2-2.1,30,5.6,1.4,3.3,9.2-1.4,2.7.9,2.6,2.9-.7,1.8,3.2-4.5,2.2,1.7,8.6,3.8,6.9,8.9,3.4Z"/>
|
||||
</g>
|
||||
<g id="yeongsan" data-label-shift="dx,dy">
|
||||
<path class="cls-1" d="M57.4,578.4s.2,1.4.2,2.3.7,1.6.7,1.9.5,1.4.7,2.3c.2.9,1.2,1.2,1.2,1.2h0s.5,1.4.5,1.4c0,0-.5.7-.9,1.4-.5.7,1.6.9,1.6.9l2.8-.7s1.4-.7,1.4-1.2,0,0-.2-.7c-.2-.7-.5-.7-.9-1.2-.5-.5.5-.2.5-.2l.5.2,2.6-.5s.9-.5,1.4-.7c.5-.2-.2-.5-.2-.9s-.2-1.2-.2-1.2l.2-.9v-1.2c0-.5.7-.7.9-1.2.2-.5.5-.7.9-1.2.5-.5,0-.7,0-1.6s-.5-.5-.5-.9-.5,0-.5,0l-.9.5-.9.7s-.2.9-.5,1.2c-.2.2-1.4.2-1.4.2,0,0-.2-.5-1.2-.9-.9-.5,0,0-.5,0s-1.2-.2-1.2-.2c0,0-1.2-.7-1.6-.9-.5-.2-.5-.9-.5-.9h-1.2s-1.9,1.4-2.8,1.9c-.9.5,0,1.2,0,1.2Z"/>
|
||||
<path class="cls-1" d="M45.2,585.7c.2.2,0,.2.7.9.7.7.5,1.6.9,1.6s.2-.5,1.2,0c.9.5,1.4.5,1.9.7.5.2,1.6.9,1.9.9s1.4.7,1.4.9-.2.2,0,.5c.2.2,0,.5.5,1.4.5.9,1.9,2.6,1.9,2.6,0,0-.7.7.5.5,1.2-.2,1.9-.2,1.9-.9s.2-1.2.2-1.4-.2-.2-.5-1.2c-.2-.9-.7-1.6-.7-1.6,0,0,1.2-.2,0-1.2-1.2-.9-.2-1.2-1.2-.9-.9.2-1.9.2-1.9.2-.2-1.2-.2-1.9-.2-1.9l-.5-.5.7-1.4h0c.5,0,.9-.2,1.2-.2s.9-.7.9-.7c0,0,.2-.2,0-.7-.2-.5-.2-.2-.9-.7s-1.6-.7-1.6-.7l-2.1-1.2-1.4-.9c-.7-.2-2.6.9-2.6.9,0,0,.9-.5,0,.7-.9,1.2-2.3,1.9-2.3,1.9,0,0-.2.5,0,.9.2.5,0,1.2.2,1.4Z"/>
|
||||
<path class="cls-1" d="M28.6,585.5s0,.7,1.2.7h2.1c.5,0,.9.7.9.7v1.9l1.2.7s1.4,0,1.9-.5c.5-.5.5-.7.5-.7l-.5-1.4,2.3-.7s.5-.5.7-1.2c.2-.7-.2-1.2,0-2.3.2-1.2,0-2.8,0-2.8,0,0,.9-1.4.9-2.1s1.9-2.6,1.9-2.8h0v-.7h-1.4c-.2,0-1.7.5-2.1.7-.5.2,0,0-.9,1.2s-.2,1.6-.2,1.6l-1.9.5-1.4.7-.9.2s-1.2-.2-2.1-.2-.5.9-.5.9c0,0-1.2,1.4-1.9,1.6-.7.2-.7.7-.7.7,0,0,.9,2.3.9,2.8v.5Z"/>
|
||||
<path class="cls-1" d="M41.7,591.4c-.5.2-.7-1.2-.5,0,.2,1.2,1.2,2.1,1.2,2.1l1.4.9s.9-.2,1.4,0c.5.2.9,0,.9,0l1.9.7v.2s.7-.2.5-1.6c-.2-1.4,0-1.4,0-1.4l-.7-.9-2.6-1.4h0s-2.8-.7-3-.5c-.2.2-.5-.2-.5.2s.5,1.4,0,1.6Z"/>
|
||||
<path class="cls-1" d="M34.2,597.7s0,0,0,0c0,0,0,0,0,0Z"/>
|
||||
<path class="cls-1" d="M45,598.9l-.7-.7-.5-.9s.2-1.4-.2-1.4-1.9-.5-1.9-.5l-1.4-.7h0s-1.4,0-1.4,0c0,0-.9,0-1.2.7-.2.7-.9.5-.9.9s-2.1,1.2-2.5,1.4c0,0,.2.2.4.5.5.7.5,1.4,1.2,1.4s2.1.2,2.3,0c.2-.2.9-.7,1.2-.5.2.2.5.5.5.9s-.5,1.2-.5,1.6.5,0,0,.5c-.5.5.2,1.4.5,1.6.2.2-.7,2.1-.7,2.1,0,0-.5.5-.5.7s1.4.7,1.6,0c.2-.7.7-1.2.9-1.4.2-.2.9-.5.9-.5,0,0-.5.2.7.2s2.1.2,2.1-.5v-1.9s-.2-1.2,0-1.4c.2-.2,0-2.3,0-2.3Z"/>
|
||||
<path class="cls-1" d="M18.5,619.7c.7,0,1.9-.9,2.3,0,.5.9,1.9,2.6,1.9,2.6,0,0,.2-.2,1.4,0,1.2.2,1.2.5,2.1,0,.9-.5,1.4-1.4,1.4-1.4l1.2-1.2,3.3-1.4s-.2-.7-.7-2.1c-.5-1.4-.7-2.1-.7-2.1,0,0,1.2.2.5-.9-.7-1.2-.9-1.9-.9-1.9l-.5-1.6h0s-.7-.9-.7-.9l-2.1.2s-.2,1.2,0,1.9c.2.7.2.9,0,1.4-.2.5-.7-.5-1.2,0-.5.5-2.3.2-2.3.2h-1.2l-.9,1.4-1.2.9-2.3.9-2.1,1.4s-.5,0,0,.9c.5.9,2.1,1.6,2.8,1.6Z"/>
|
||||
<path class="cls-1" d="M33.1,624h-.9l-1.4-.7-2.1.7s-.9,1.2-.9,1.6.7,1.9,1.4,2.3c.7.5,1.6.7,1.9,1.6.2.9.2,1.9.2,1.9,0,0,1.9.7,2.3-.7.5-1.4.2-2.3.7-2.6.5-.2,1.9-1.6,1.9-1.6,0,0-.5-.5.7-.5s1.6-.5,1.6-.7v-1.9l-.9-.9h.9l.7-.9s.2-.7,0-1.2c-.2-.5-.5-1.9-.5-1.9l-2.6.5h0l-1.4,1.4-.5.9s.2.9,0,1.2c-.2.2-1.2,1.4-1.2,1.4Z"/>
|
||||
<path class="cls-1" d="M65.6,620.2l1.9-.7-2.1-2.6-3.5-1.9h0s-3.3.7-3.3.7l1.6,2.1.9,1.4s-.5.5-.9.9c-.5.5-1.6.7-1.6.7,0,0-.5.5-.5.9s-.9,0-2.1.7c-1.2.7-4.5,1.9-4.5,1.9h-.9c.9,0,1.6,2.6,2.3,2.6h3.1s-.2-.5.9-.7c1.2-.2,2.6-1.2,2.6-1.2,1.4.2,2.3-.7,3,.5.7,1.2,1.4.9,1.6,1.4.2.5,1.2,2.1,1.2,2.1l-.7,2.6s1.2.9,1.6.7c.5-.2,1.9-1.4,1.9-1.4l.9-1.9s-.5-1.4,0-1.9c.5-.5-1.6-1.4-1.6-1.4l-2.8-.7-1.9-2.6v-1.2l2.8-1.2Z"/>
|
||||
<path class="cls-1" d="M64.2,635.5v-1.6s-3.3,1.4-3.3,1.6.9,1.6.9,1.6h1.6l.7-1.6Z"/>
|
||||
<path class="cls-1" d="M59.8,639.2c0,1.2.5,2.8,1.2,3,.7.2,1.6,0,1.6,0v-3l-1.6-.9s-1.2-.2-1.2.9Z"/>
|
||||
<path class="cls-1" d="M38.4,638.3c1.2,0,2.8-4.5,2.8-4.5v-1.4h0s-3.5,0-3.5,0l-2.1,1.4-3.5.7s-.2,1.9,1.4,1.9.7-.1.6-.2c0,0,.3,0,.6.2,1.6.5,3.3.7,3.3,1.2s-.7.7.5.7Z"/>
|
||||
<path class="cls-1" d="M15.4,630.1h0s0,0,0,0h0Z"/>
|
||||
<path class="cls-1" d="M7,642.7c.2-.5,1.2-.7,1.2-1.4s-1.6-2.6-.5-3.1c1.2-.5,2.6-.9,2.8-1.2.2-.2,1.2-.7,1.6-.5s.9.9.9.9l1.4-.5.5-.9s.2-.9.5-1.4c.2-.5.7-1.9.7-1.9v-1.2l-.7-1.6c-.3.2-2.6,2.1-2.8,2.3-.2.2-3,1.2-3.3,1.2s-3.1.2-3.1.2l-3.8.5-2.6,1.9,1.9,2.1s-.2,1.9,0,2.1c.2.2-.5,1.2.5,1.6.9.5,1.6.9,1.6.9,0,0,2.8.2,3-.2Z"/>
|
||||
<path class="cls-1" d="M16.6,649.1c0-.7-1.2-1.6-1.2-1.6l-.7-.7-.9-.9s-.2-1.2-.2-1.4-.7-.9-.7-.9c0,0-2.3.2-2.3.5s-.7.7-.7.7c0,0-.2.7-1.6.7s-2.8-.5-2.6,0c.2.5-.2.9-.2,1.6s-.5,1.9-.5,1.9v1.2s.2,1.2.7,1.4c.5.2,1.2.5,1.4.7.2.2,1.4.9,1.2,1.4-.2.5,2.3.2,2.3.2l.5-.5c.2-.2,1.9-1.2,1.9-1.2,0,0,0-.9.5-.9s3.3-.5,3.3-.5v-1.6Z"/>
|
||||
<polygon class="cls-1" points="12.9 643.4 12.9 643.4 12.9 643.4 12.9 643.4"/>
|
||||
<path class="cls-1" d="M27.7,639.9c.5.5,1.4,1.6,2.1,1.9.7.2,1.2.5,1.4,1.2.2.7.2,1.4.5,2.1.2.7,1.9.7,1.9,1.2s.5-.2,1.4,0c.9.2.2-.2,1.6,1.2,1.4,1.4,2.3,2.1,2.6,2.6.2.5.9,0,.9,0l1.2-.9s-.2-1.6,0-1.6.2-2.1.2-2.3-.5-2.3-.5-2.3c0,0-1.2-1.4-1.4-1.4s-2.1-.7-2.1-.7l-1.2-1.2-.9-1.2-1.4-.5h0l-3.7-.7h-2.1l-1.2,1.6s.2.7.7,1.2Z"/>
|
||||
<path class="cls-1" d="M32.6,663.4c.5.2,2.3.5,2.8.5s1.6-.2,1.9-.5c.2-.2.9-.2,1.6-.2s3.5.2,3.5.2c0,0,.5-.7.2-1.4-.2-.7-.9-1.4-.9-1.4l-.2-.7-.7-.5.7-1.2v-.9s-.7-.5-1.4-.5-1.4.2-1.4-.2-.9-1.9-.9-1.9l-1.4.7h0c-.9.5-1.9.7-2.1.9-.2.2-3.3.2-3.1.7.2.5.7,1.2.9,1.4.2.2.9,1.6.9,1.6,0,0-.5,1.6-.5,1.9s-.5,1.2,0,1.4Z"/>
|
||||
<path class="cls-1" d="M21.3,670.4c0-.2,0-1.4.5-1.6.5-.2.7-.5.7-.7s-.7-1.2-.7-1.4-1.2-1.2-1.2-1.2l-.5-1.2-.2-1.2-.2-1.9h0s-.9-1.9-1.2-1.9-1.4-.2-1.4,0-.5.9-.5.9v.7c0,.2-.2.7,0,1.2.2.5-.2,1.4,0,1.6.2.2.2.7.5,1.4.2.7.7,1.2.7,1.4s-.2,1.2-.2,1.2c0,0-.5-.7-.5.5s-1.9,1.9-.5,2.1c1.4.2,2.8.5,3,.5s1.6-.2,1.6-.5Z"/>
|
||||
<path class="cls-1" d="M27.4,673s-.5-.9,0-1.6c.3-.5.5-.9.7-1.2.2-.5.9-.5.9-.9s.5-2.3,0-2.8c-.5-.5-.9-1.6-1.4-2.3-.5-.7-.2-.9-1.2-1.6-.9-.7-1.9-1.2-1.9-1.2l-1.2.2h0s-.7,2.1-.7,2.3.2,2.3.7,2.6c.5.2,1.6,1.4,1.9,1.9.2.5.2,1.2.2,1.6s-.7,2.1-.9,2.1,0,0-1.2.5c-1.2.5-2.6.2-3,.9-.5.7-1.2,1.4-1.2,1.4v1.4c0,.2-.2.7.5.7s2.3-.2,2.8-.5c.5-.2,1.4-.7,1.4-.7l2.3-2.1,1.2-.7Z"/>
|
||||
<path class="cls-1" d="M63.7,683.1c-.2-.5-1.4-2.1-1.4-2.1l-.5-.9s0-1.6-.9-1.9c-.9-.2-3-.2-3-.2l-1.6-2.3s-1.4-2.6-1.6-2.6-1.4,0-2.1-.2c-.7-.2-2.6-1.2-2.6-1.2l-.9.7c.2.7,1.6,2.8,1.6,2.8l1.2,2.3-.5,1.9s-1.9.5-2.3.5h-3s-.5-.7-.7.7c-.2,1.4-2.6,2.6-2.6,2.6h-3s-.9,1.6-.9,2.1-6.1,3.1-6.1,3.1l-1.9,2.6-1.9.5s-.5.7-.5.9-1.2,1.6-1.2,1.6h-.7s-2.3.5-2.3.9.5,1.6.5,2.1v1.6s0,1.6.2,1.6,1.4.7,1.4.7l.7,1.2s.5.2.5.5.7,1.2.7,1.2l1.2,1.2s-.5,1.6,0,2.1c.5.5.9.5,1.4.7.5.2,4.2.7,4.2.7,0,0,1.9.2,2.1.5.2.2,1.4.5,2.1,0,.7-.5,7-1.6,7-1.6,0,0-.7-.9,0-1.2.7-.2,1.9-.5,3-1.4,1.2-.9,3.7-1.2,3.7-1.2,0,0-.5-1.2.2-1.6.7-.5,2.6-2.1,2.8-2.1s2.1,1.4,2.1,1.4c0,0-.7.7.5.7s1.4-.5,1.9-2.1c.5-1.6,1.9-2.3,1.9-2.3l.5-.7s-.5-.5,0-1.6c.5-1.2.9-2.6.9-2.6v-3.5s-.5,0,.5-.5c.9-.5,2.1-2.3,1.4-3.1-.7-.7-1.6-1.9-1.9-2.3Z"/>
|
||||
<polygon class="cls-1" points="49 672.3 49 672.3 49 672.3 49 672.3"/>
|
||||
<path class="cls-1" d="M93.5,737.5c-2.3-1.2-4.2-2.3-4.2-2.3h-1.9l-1.2,3.5s1.4,0,1.2.9c-.2.9-1.6.5-1.2,1.6.5,1.2-.5.7,1.6,1.6,2.1.9,2.8,1.9,3.7.9.9-.9,1.9-2.6,1.9-2.6h0l.7-1.2s1.6-1.4-.7-2.6Z"/>
|
||||
<path class="cls-1" d="M86.2,745l-2.8-1.9s-2.8,2.6-2.8,3.1-2.1,3.3-1.6,4,1.2,1.6,2.8,1.6,3.3-1.6,5.4-2.6c2.1-.9,2.8-2.3,3.7-1.9.9.5,2.1-.5,2.1-.5h0l-1.9-1.2-4.9-.7Z"/>
|
||||
<path class="cls-1" d="M101.7,742h0l-3.5,1.2s-.9,1.2-.9,1.6.5,2.6.5,2.6c0,0-.5.5-.5,1.6v2.3s1.2,3.1,2.1,3.5c.9.5,2.6-.5,3.1-1.9.5-1.4-.9-5.6-.9-5.6,0,0-2.8-1.2-2.1-1.4.7-.2,2.8-.5,3.1-.5s.9-.7.9-.7l-.7-2.8h-.9Z"/>
|
||||
<path class="cls-1" d="M115.1,720.4l-1.2-1.6-.5-1.6h0v-2.6l-2.1-3.3s-1.6-1.2-1.9-1.6c-.2-.5-3-2.3-3-2.1s-2.3,2.3-2.3,2.6-3.7,2.8-3.7,2.8c0,0,1.2,4,1.2,4.2s-1.9.2-.5,1.9c1.4,1.6,5.9,2.1,5.9,2.1,0,0,.2-1.4.5.5.2,1.9-.5,2.8-.5,3.1s1.2,1.2,2.6,1.2,3.3-.2,3.5,0c.2.2,3.5.5,3.5.5,0,0,.2,0,0-2.1-.2-2.1-1.4-3.8-1.4-3.8Z"/>
|
||||
<path class="cls-1" d="M125.6,711.9c.5,0,1.4-1.9,1.4-1.9,0,0-.5-.7.9-.5,1.4.2.9,0,1.6.5.7.5,1.2.5,1.4.5s1.2-.7.5-1.4c-.7-.7-1.4-.9-1.9-1.6-.5-.7-.5-2.6-.5-2.6,0,0-.5-1.4-.7-1.4h-2.8l-.9.9h0c-.7,0-3.5-.7-3.5-.7l-.5,2.8-2.1,1.4s-.9.5-.5,1.2c.5.7.9,1.2.7,1.6-.2.5-1.6,0-1.4.7.2.7,1.6,1.9,1.6,1.9,0,0-.7,1.2-.7,1.6s.9,1.4,1.4,1.4,1.9-2.1,1.9-2.1c0,0,.5-.9.9-1.2.5-.2,2.1-1.2,2.1-1.2h.9Z"/>
|
||||
<path class="cls-1" d="M141.5,712.4c0-.2-.9-.5-1.2-.7-.2-.2-1.6-.7-1.6-.7l-3-.9h-1.7s-.7,2.1-.7,2.3-.5,1.2,0,1.4c.5.2,2.1.7,2.1.7v1.2c0,.5-.7.7.5,1.2,1.2.5,4.2-.5,4.2-.5h2.6s.9-1.4.5-1.9c-.5-.5-1.6-1.9-1.6-2.1Z"/>
|
||||
<path class="cls-1" d="M128.6,725.1s.2-.2.5,0c.2.2,1.4-.5,1.4-.5,0,0-.2-.5.5-.5s1.9.5,1.9.5c0,0,.7-.2.9-.7.2-.5-.2-.9,0-1.4.2-.5,0-1.6,0-1.6l-.7-1.6s-.9-.2-1.4-.2-1.2-.2-1.2-.2l-.5-.2-.9-.7h0s-2.3-.2-2.6.5c-.2.7-.7,1.6-.9,1.9-.2.2-1.9.9-1.9.9l1.4,1.4s.2.7,1.2,1.2c.9.5,2.3,1.4,2.3,1.4Z"/>
|
||||
<path class="cls-1" d="M121.1,719.9c-.3-.3-.7-.5-1.2-.5h0c-.6,0-1.1.6-1.2,1.2,0,.4.2.8.5,1.2.1.2.3.5.7.7.5.3,1.4.6,1.6.2.2-.2,0-.5-.2-1.6,0-.7,0-.9-.2-1.2Z"/>
|
||||
<path class="cls-1" d="M137.3,747.6c-.9-.5-1.9-.9-1.9-.9l-.5-1.2.9-1.2.2-2.3-4.2-.5-2.1.5h0s-1.2,2.1-1.4,2.6c-.2.5-2.1,1.9-2.1,2.1s0,.7-.2,1.4c-.2.7-.9,1.9,0,2.3.9.5,2.1.7,2.1.7,0,0,2.6-1.4,2.8-.7.2.7.2,1.2,1.2,1.9.9.7,2.1.9,2.3.9s.2-1.2.9-1.4c.7-.2,2.6-1.9,2.6-1.9,0,0,.2-1.9-.7-2.3Z"/>
|
||||
<path class="cls-1" d="M267.1,679.6s.5-.5,0-1.6c-.5-1.2-.7-2.1-.7-2.1l-.7-1.2-.5-1.4s-.5-.5-.2-1.2c.2-.7,0-2.6,0-2.6l-.2-.7.7-.9h0c.2-.2.5-2.1.5-2.1l-3.3-2.3s-1.4.9-1.2,1.9c.2.9.9,2.1.9,2.6s.2,1.6,0,2.1c-.2.5-.9,1.2-.9,1.4s-.7,1.4-.9,1.9c-.2.5-2.8,1.9-2.8,1.9,0,0-1.9,1.9-1.9,2.1s-1.2.2-.7,1.6c.5,1.4.5,3.3.5,3.3,0,0-1.2.9-.5,1.6.7.7,1.4,1.6,2.8,2.1,1.4.5,4.2,1.6,4.2,1.6l3.1.5s2.3-.9,1.9-1.4c-.5-.5-1.6-1.4-1.6-1.4l-.5-1.2s0-1.2.5-1.9,1.6-2.6,1.6-2.6Z"/>
|
||||
<polygon class="cls-1" points="279.4 600.4 279.4 586.1 272 579.9 262.7 571.1 261.3 561.5 254.5 559.9 251.7 557.3 244.7 562.9 243.5 566.9 235.1 563.3 234.4 553 235.1 535.7 240.9 533.6 245.9 532.1 247.1 523.8 256.9 510.3 248.3 507 239.9 515.4 232.7 509.4 238.7 493.8 231.6 486.6 229.2 475.8 217.2 472.2 206.4 484.2 205.2 493.8 195.7 486.6 186.1 495 186.1 502.2 176.5 510.6 159.8 515.4 157.4 523.8 140.6 521.4 127.4 517.8 124.5 512.7 119.4 508.3 115.3 512.5 110.2 512.4 96.4 517.8 89.3 530 84.4 535 85.6 538.3 88.3 540.1 87.2 541.1 82.3 539.9 76.8 551.3 72.3 553.6 67.3 564.8 76.6 566.5 73.6 571.4 75.3 574.4 80.6 583.9 85.4 585.9 85.4 589.7 80 596 78.4 589.4 69.9 588.5 68.8 590 71.3 593.4 75.4 595.9 74.3 598.4 69.7 600 68.7 603.3 63 605.4 61.9 611.5 67.8 615.5 70.4 613.1 73.2 605.9 74.3 613.6 75.3 626.5 69.7 634.5 69.7 639.2 68.1 643.4 67.8 644.3 80.4 650.4 92.7 661.4 89.3 661.9 86.5 659.8 85.6 664 79 654.2 69.4 650 67.8 656.3 70.6 661.9 74.3 663.3 77.1 670.4 72.7 669.9 66.1 661.5 62.9 648.1 58.5 642.1 51.9 654.2 55.1 668.3 59.6 674.6 61.9 673.9 65.8 676.6 67.1 680.5 69.9 680.2 72.7 677.9 79 686.6 77.7 698.2 82.8 699.7 79.7 706.7 76.6 709.9 77.4 712.1 82.5 712.8 82.7 721.2 93.1 719.9 97.3 713.3 97.1 709.8 112.1 701.6 110.7 695.9 117.2 694.3 120.8 688.2 120.2 673.4 122.8 671.1 124.7 675.1 124.5 684.5 122.4 694.3 125.7 699.9 130.4 702.1 134.8 704.2 136.9 700.4 143.5 702.3 145.8 697.6 149.1 698.3 148.3 688.6 152.1 687 152.9 684.2 151.7 682 155 673 162 671.1 167.4 666.2 174.9 662.4 179.6 664 183.8 662.6 187.1 655.8 192.4 654.7 194.6 657.2 202.1 657.2 202.7 663.8 200.6 669.2 196 669.8 194.8 663.8 189.6 664 187.5 671.6 189.1 675.6 185.9 681.9 181.9 681.2 183.1 676 181.4 674.3 176 692.2 189.2 694.5 191.7 701.4 195.5 706.3 203.9 704.4 207.4 697.8 213.3 694.1 214.2 689.9 209.3 687.1 210 684 218 684.7 221 686.6 224.2 683.9 222.7 674.6 209.2 661.4 209.5 657 212.8 653.7 210.5 650.9 215.4 646.2 211.9 643.7 213.5 642.5 216.5 642.5 222 642.5 227.6 640.4 228.1 635.9 230.9 638 231.2 641.7 231.8 647.4 236.2 650.7 238.4 657 234.6 663.8 232.6 672.3 234.9 676.9 238.7 676.9 243.5 679.8 244.5 675.1 243.3 671.1 245.9 662.9 249.9 659.4 255 664.1 261.6 661 262.3 655.8 265.1 651.9 267 645.5 264.6 642 261.1 642.5 257.9 642 248 646 246.3 642 239.9 634.6 244.2 626.8 246.3 631 251.6 632.8 262.5 623.5 278.8 628.6 285.4 617.2 285.4 607.6 279.4 600.4"/>
|
||||
</g>
|
||||
<g id="nakdong" data-label-shift="dx,dy">
|
||||
<path class="cls-1" d="M405.4,649.3l-2.1-.2-1.6,1.2-.9,1.6-1.2.2-.9-.5v-1.2l.2-.9,1.4-.9.9-.7v-.9l.9-.9.7-1.2-.2-1.6-.2-1.4.5-3v-1.9l-1.6-1.6-.9-.9.2-1.9.7-2.8h0c0,0,1.6-.5,1.6-.5,0,0,1.2-.9,1.2-1.2s.2-.7.2-.7l-.2-.9-1.4-.9-1.9-.7-1.6.2-1.2.5-.9,1.2-.7,1.2-.2,1.2.5,1.9v.9l-.5.7-.7.2-1.2.2-.9.5-.5.5-.2,1.2-.2.5h-1.4l-1.4.5s-.9.2-1.2.2-.7.7-.7.7l-1.2.5h-1.4c-.2,0-.7.2-.7.2l-.5.5v.5l.2.7s.7.7.9.7,0,.7,0,.7l.2.9v.7l.2.9.5.5.7.5s.5.5.7.5.2.7.2.7c0,0,.5.7.5.9v2.1h-.2s-.7-.2-.9-.2-.5-.5-.5-.5l-.9-.7h-.9l-.9-.5-.5-.7-.7-1.2-.7-.5-.5-.2-.5.2-.7.7-.7.2h-.9l-1.4-.9-1.4-.9-1.4-.7h-1.2l-.5.2-.7.9-.5.7-1.2.7s-.2,0-.5.2c-.2.2-.7.5-.7.5l-.5.7-.7,1.2-.5,1.2-.9.9v3.3l1.2,1.4s.7,1.4.7,1.6.2,1.4.2,1.4l.7,1.2.7,1.6,1.6.2,1.4-.9.7-1.2.7-.7h2.6l1.4-1.2.9-1.6.7-.9.7-.5.5.5.5.7v2.6s.5.5.7.5,0,.7,0,.7l-.7,1.2-1.2,1.9-.5.7-.2.5-.2.5-.9.5-1.2.2-1.4.2-.7.5-.5.5-.2.7,1.4.5.5.5v.7l.5.5.7.2h1.2l2.1.2.7.5.2.7-.2.5-.7.5s-.9.7-1.2.7-.5.9-.5.9l-.2.7.2.9v.7l.9.2.9.7,1.2.2v.9l-.2.2-.9.5-1.4.5-.7.2-.5.7v.9l.5.7s.5.5.7.5,1.4.2,1.4.2c0,0,1.4.2,1.6.2s.9-.5.9-.5l1.2-.5,1.2-.7.5-.7v-.7l.2-1.2s.5-.9.5-1.2.9-.5.9-.5l1.4-.2.2-.7-.9-.9-.7-1.4.5-1.6v-1.2l.7-.7,1.6-.2,1.6-.2.7-.9.2-1.2v-2.1l.7-.7,1.9.2,1.6-.2.5,2.1,1.2,1.4,2.1.9,1.6-.7.2-1.2-.2-1.2-1.6-1.2-.7-1.4.2-1.2-1.2-.7-.9-.9.5-1.4h1.9c.2,0,.7-.5.7-.5l.7-.9v-1.6h.9c.2,0,.7-.5.7-.5l.2-1.4s.2-.5.7-.7c.5-.2,0-1.4,0-1.4v-1.4Z"/>
|
||||
<path class="cls-1" d="M330.3,650.8v-1.2l-.9-.5h0s-4.2-1.2-4.2-.9-2.8,0-2.8,0l-1.4.9s-.2.7-.2.9.2,1.2.2,1.2l.5.2.9.7,2.8.5h1.9l.9-.7.9-.7.9-.2.5-.2Z"/>
|
||||
<polygon class="cls-1" points="333.8 654.8 332.6 654.1 332.2 652.9 331.7 652.7 331 652.9 329.8 653.9 329.6 654.8 329.1 656 329.1 657.6 329.4 659 329.8 659.7 330.8 659.7 331.9 659.5 332.4 659 333.8 659 335 658.8 335.2 657.8 335.2 656.9 334.7 655.7 333.8 654.8"/>
|
||||
<polygon class="cls-1" points="308 649.2 307.6 648.4 307.6 647.5 308 646.3 308.7 645.2 308.7 644.2 308.3 644 307.3 643.5 306.4 642.8 305.5 641.9 304.7 641.2 303.1 641.4 302.2 642.8 302.2 644.7 301.2 645.2 300.8 644.2 300.5 642.6 301 641.4 301.7 639.8 302.4 638.4 303.6 637.2 303.6 636.5 303.3 635.8 302.6 635.8 301 636 299.8 637 298.4 638.1 298.2 639.5 297.3 640 295.6 640.9 294.7 642.4 293.5 643.5 293.3 644.9 293.3 645.9 293.5 647 294.2 648.2 295.4 648.4 297 649.2 299.4 649.2 301.2 648.7 303.1 649.2 304.1 649.9 306.2 651 307.8 652 308.3 651.7 309.4 651.7 309.7 651 309.2 650.3 308 649.2"/>
|
||||
<path class="cls-1" d="M305.2,653.4l-1.2-.5-.9-.7-.7-.2h-2.1l-1.6-.2-.9-.7h-1.6l-1.2,1.2-1.6,1.2-.9.7v.7l-1.4-.2-.5-1.2-.9-1.4-.2-.7-.5-.9-1.2-.5-1.2-.5-.5-.5s-.2-.5-.2-.7-.2-1.2-.2-1.2v-.5c0-.2-.7-.7-.7-.7l-.2-.7-.5-1.2-.2-1.4.7-.9.2-.9,1.6-.2.7-.5.5-.2.2-.7.2-1.2.5-.5.7-.9v-.7l-.2-1.2-.5-1.4.2-1.4-2.1-1.4h-3.1l-1.9.2-1.4,1.2-.9,1.4-.5,1.2-.5,1.4v.7l-1.6.5-.9.5-.7.9s.2.5,0,.7c-.2.2-1.2.9-1.2.9v1.4l-.5,1.2-.7.5-.9.5-.2.9v1.4l.2,1.2-.2,1.4.5,1.6.5,1.9,1.2,1.9h0v1.6l.2,1.4.7.9.9.7.2.7-.2.7-.9.9-.9.7-.5.7-.2.7,1.2.7.7.5,1.4.7v.7l.2,1.2.5.9.9.5.9-.2h1.9l1.2-.2.9-.5.9-.9v-1.4l-.5-.9-.2-1.4.2-.7.7-.5,1.9-.2,1.2-.9h2.3l.5.9v.7s-.5.7-.5.9-.2,1.2-.2,1.2l-.5,1.4v.5l-.2.9.2.5.2.5.2,1.2v1.2l.5.9,2.1.2,2.3.7,1.6-.2,1.2.2.9-.7s.5.5.7.5.5.9.5.9l1.6.7,1.6.2h.7l.2-1.2-.2-1.4-.7-.7-.2-.9v-1.2l.5-.5.9-.9.7-.2.5-.5v-2.3s-.5-2.1-.5-2.3v-.9l.2-1.2.5-1.4s-.2-.9,0-.9.7-.7.7-.7l.7-.5.2-.9-.2-.9-1.2-.5Z"/>
|
||||
<polygon class="cls-1" points="533.7 451.9 529.9 457.3 516.8 465.7 509.4 457.5 516.6 447.7 513.8 417.3 513.1 408.8 521.3 392.4 522.9 355.1 523.6 353.3 531.1 333.9 526.2 315.4 526.2 282.1 519.8 263.8 504.8 272.7 495 281.2 482.8 272 481.1 259.2 471.4 254.5 473.3 244.6 468.7 245 455.3 248.2 454.9 259.5 454.7 274.8 451.4 277.9 438.9 269.7 434.5 277.6 421.1 273 410.9 275.3 403.3 283.3 392.8 288.8 386.2 300.5 389 306.9 377.6 314.7 353.5 310.3 345.4 307.2 340.7 310.3 334.5 310.7 330 319.5 334.6 326 329.8 328.1 325 323.7 307.5 329.5 314.2 336 312.9 341.5 306.5 336.4 294.4 343.5 303.6 349.6 308.1 357.5 304 362.7 314.1 371.6 316.5 383.6 314.1 388.4 326.1 392 323.3 402.6 325.2 414.4 315.5 413.8 315 423.6 307.5 439.1 298 443 298.5 447.6 301.2 454.4 296.8 462.5 286.2 472.3 276.7 473.4 269.4 480.7 267.1 483.1 264.5 485.8 256.9 505.2 256.9 510.3 247.1 523.8 245.9 532.1 240.9 533.6 235.1 535.7 234.4 553 235.1 563.3 243.5 566.9 244.7 562.9 251.7 557.3 254.5 559.9 261.3 561.5 262.7 571.1 272 579.9 279.4 586.1 279.4 600.4 285.4 607.6 285.4 617.2 278.8 628.6 282.6 628.4 288.8 622.6 289.3 620.3 291.9 621.5 292.3 624.3 296.3 623 298.7 624.3 301.6 622.6 298.9 615.9 301.2 614.9 303.4 615.5 304.7 611.2 309.9 609.7 307.1 614.2 306.9 625.2 305.5 632.3 314.8 637.2 317 641.5 318.3 642.1 322.4 640.3 325.1 641.7 326.8 641.4 329.4 634.9 335.5 635.5 338.5 640.5 342.8 638.4 346.2 633 347.4 636.3 348.6 639.8 343.1 642.3 344.4 645.9 349.4 647.4 351.4 645.9 353.5 647.7 353.3 655.5 347.7 654.1 346.2 655.7 351 659.7 353.7 666.6 356.3 667.8 358.7 665.9 359.9 659.7 358.5 655.6 361.6 654.2 365.6 646.5 363.1 643.5 360.7 647.8 358.5 646.8 361.3 632.3 361.1 630.7 361 629.5 368.3 628 371.4 624.3 365.9 619.1 357.2 620.8 355.4 623.6 351.9 624.1 354.8 618.5 361.5 616.3 367.3 614.4 366.1 610.6 379 609.8 381.7 611.6 383.5 619.4 389.9 616.8 383.5 596.4 387.2 591.5 388.9 592.4 387.7 599.2 390.1 604.4 394.7 605 398 608.8 399.5 604.8 401.5 606.5 400.5 612.2 406.2 614.7 414 613.3 414.9 615.6 420.1 616.2 424.6 617.3 428.8 614.1 432.5 616.6 438.5 610.1 436.6 622.3 438 626.7 441.3 625.8 442.5 620.6 445.7 622.8 453.5 612.9 457 616.4 461.6 616.6 461.7 613.8 459.8 610.5 463.1 606.6 469.9 608.2 480.9 594.4 480.7 591.3 485.5 581.8 491 579.8 494.6 576.1 498.5 573.7 497.1 571.8 499.6 567.6 499.9 561.3 498 558.2 504.1 554 504 546.1 505.5 548.8 506.9 554.5 511.6 551.2 514.6 544.4 514.4 537.4 515.8 532.7 513.9 528 514 525.4 524.7 493.4 527.1 486.2 527.8 477.8 535.1 466.5 538 455.6 533.7 451.9"/>
|
||||
</g>
|
||||
<g id="geum" data-label-shift="dx,dy">
|
||||
<path class="cls-1" d="M298,443l9.5-3.9,7.5-15.6.5-9.8,9.7.6-1.9-11.8,2.8-10.6-12-3.6,2.4-4.8-2.4-12-10.1-8.9-12.6-.6-8.4-10.8-16.3-1.1-7.6-19.2,17.5-7.4,2.3-2.4v-1.1c-.1,0,.4-1.3.4-1.3l-1.6-1.1.5-9.5,1.4-12.2-3.8-13-13.2-9.6-4.9-10-7.1-1.9-11.7,17.8-8.2,1.7-3,5.2-4.6,6.5-19.7,1.1-13.8-5.9-3.6.6-3.6,5.4h-1.2l-4.8-6-6.4-3.9-5-.2-9.1-18.5-5.4,3-2.4-5.7-11.4-2.8-11.7-9.2h-3.9c0,.1-3.4,4.8-3.4,4.8l1,6.2,2.1,5.4-1.2.8-4.4-7.5-6.7-3.2-6.3-.3-5.2,3.3,5.9,3.4-3.6,2.2,5.3,4.4,2.5.8.4,3-6.5,6.1-8.7,11.2-4-1.9,6.4-13.5-1.6-1.9,1.1-7.9-2.1-3-4.7,14.5-3,1.8-3.8-5.7-4.4,2.7.5,8.4-1.5,1.7-3.3-.8-4.2,3.2-5.1,13.8.5,1.5,3.9-.2.2-6.5,5.6,2.5,2.8,5.3-7.4,2.4v3c0,0,5.9,1.2,5.9,1.2l4.3-1.5,2.1-3.3,4-.3-1.4,6,4.1,3.7-2.4,9.4,4.3,6.1,1.1,27,11.6,2.1-6.6-26.7,2.3-4.1-6.3-4.9,1.7-1.9,1-6.9,2-1.5,2.2,9,4.4,3.2,3.1-1.9,3.1-6.1,1.8,1.5-1.9,8.6,2,14.4,1.9,13.7-2.8,6.5,8.2,7.8v2.1l-6.9,2.9,2.9,21.8-1.6,1.9-5.9,6.7,1.3,2.7,3.3-2.7,8.2,2.8,6.2,7.7-1.2,2.8,5.1,13.1,5.6,3.1-19.1.5-1.8,3.3,6.4,3.1-.6,10.7,7.3,2.8,3-3.3,8.9,1.9,9.4-3.4.3,1.4.2.9-3.1,5.4-5.1-.9-9.4,3.5,6.8,6.4,4.3,1.4-.2,5.6-20.6-5.8-6.4,13.7-18.1,10.4-1.9,6.6,5.9,4.7h19.9s3.8,4.2,3.8,4.2l-.5,3.4,3,5.1,13.2,3.6,16.8,2.4,2.4-8.4,16.8-4.8,9.6-8.4v-7.2l9.6-8.4,9.6,7.2,1.2-9.6,10.8-12,12,3.6,2.4,10.8,7.2,7.2-6,15.6,7.2,6,8.4-8.4,8.6,3.3v-5.1s7.6-19.4,7.6-19.4l12.2-12.4,9.6-1.1,10.5-9.8,4.5-8.1-2.7-6.8-.6-4.6Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
@@ -0,0 +1,153 @@
|
||||
.mapArea {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bgLayer {
|
||||
position: absolute;
|
||||
inset: var(--map-inset);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
width: calc(100% - var(--map-inset) * 2);
|
||||
height: calc(100% - var(--map-inset) * 2);
|
||||
background-image: url("/images/region_map_bg.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
opacity: 0.7;
|
||||
filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.02));
|
||||
pointer-events: none;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.svgLayer {
|
||||
position: absolute;
|
||||
inset: var(--map-inset);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
width: calc(100% - var(--map-inset) * 2);
|
||||
height: calc(100% - var(--map-inset) * 2);
|
||||
}
|
||||
|
||||
.svgLayer > svg {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
shape-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(path) {
|
||||
fill: rgba(0, 150, 255, 0.2) !important;
|
||||
stroke: #0066cc;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(.cls-1) {
|
||||
fill: rgba(0, 150, 255, 0.2) !important;
|
||||
stroke: #0066cc;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(.basin) {
|
||||
opacity: 0;
|
||||
pointer-events: all;
|
||||
transition: opacity 0.15s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(.basin.active),
|
||||
.svgContainer :global(svg) :global(.basin:hover) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(.basin) :global(path),
|
||||
.svgContainer :global(svg) :global(.basin) :global(polygon),
|
||||
.svgContainer :global(svg) :global(.basin) :global(polyline) {
|
||||
fill: rgba(0, 209, 209, 0.5);
|
||||
stroke: #399ac3;
|
||||
stroke-width: 1.3;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
min-width: 220px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
box-shadow:
|
||||
0 12px 24px rgba(0, 0, 0, 0.18),
|
||||
0 2px 0 rgba(0, 0, 0, 0.05);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translate(-50%, -8px) scale(0.98);
|
||||
transition:
|
||||
opacity 0.12s,
|
||||
visibility 0.12s,
|
||||
transform 0.12s;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.popup.show {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
transform: translate(-50%, -12px) scale(1) !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.labelsLayer {
|
||||
position: absolute;
|
||||
inset: var(--map-inset);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.basinLabel {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
background: #ffffffee;
|
||||
border: 2px solid #d0d0d0;
|
||||
white-space: nowrap;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.basinLabel.active {
|
||||
background: #006bf8;
|
||||
color: #fff;
|
||||
border-color: #96c3ff;
|
||||
box-shadow: 0 4px 12px rgba(0, 107, 248, 0.3);
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
import { type CSSProperties, useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
BASIN_DATA,
|
||||
BASIN_IDS,
|
||||
type BasinKey,
|
||||
MANUAL_LABEL_POS,
|
||||
type ManualLabelPos,
|
||||
} from "../model/map-data";
|
||||
|
||||
import styles from "./national-as-map.module.css";
|
||||
|
||||
const SVG_URL = "/images/region_map.svg";
|
||||
const POPUP_EDGE_PADDING_PX = 12;
|
||||
const LABEL_OFFSET_Y_PX = 18;
|
||||
const DEFAULT_VIEWBOX = "0 0 100 100";
|
||||
|
||||
export const PLACEHOLDER_POPUP_ITEMS = [{ header: "현황", data: "-" }] as const;
|
||||
|
||||
export interface MapPopupItem {
|
||||
header: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface MapPopupContent {
|
||||
title: string;
|
||||
items: MapPopupItem[];
|
||||
}
|
||||
|
||||
const INITIAL_POPUP_CONTENT: MapPopupContent = {
|
||||
title: "",
|
||||
items: [...PLACEHOLDER_POPUP_ITEMS],
|
||||
};
|
||||
|
||||
function isBasinKey(key: string | undefined): key is BasinKey {
|
||||
return Boolean(key && key in BASIN_DATA);
|
||||
}
|
||||
|
||||
function resolveBasinName(group: SVGGElement) {
|
||||
const key = group.dataset.key;
|
||||
if (isBasinKey(key)) return BASIN_DATA[key].name;
|
||||
return group.dataset.label ?? "";
|
||||
}
|
||||
|
||||
function hidePopup(popup: HTMLDivElement | null) {
|
||||
if (!popup) return;
|
||||
if (styles.show) popup.classList.remove(styles.show);
|
||||
popup.style.opacity = "0";
|
||||
popup.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
function showPopupAtGroup(stage: HTMLDivElement, popup: HTMLDivElement, group: SVGGElement) {
|
||||
const stageRect = stage.getBoundingClientRect();
|
||||
const groupRect = group.getBoundingClientRect();
|
||||
const centerX = (groupRect.left + groupRect.right) / 2 - stageRect.left;
|
||||
const topY = groupRect.top - stageRect.top;
|
||||
|
||||
popup.style.left = `${centerX}px`;
|
||||
popup.style.top = `${topY + 6}px`;
|
||||
popup.style.opacity = "1";
|
||||
popup.style.visibility = "visible";
|
||||
if (styles.show) popup.classList.add(styles.show);
|
||||
|
||||
const popupRect = popup.getBoundingClientRect();
|
||||
const clampedX = Math.max(
|
||||
POPUP_EDGE_PADDING_PX + popupRect.width / 2,
|
||||
Math.min(centerX, stageRect.width - POPUP_EDGE_PADDING_PX - popupRect.width / 2),
|
||||
);
|
||||
popup.style.left = `${clampedX}px`;
|
||||
}
|
||||
|
||||
function collectBasinGroups(svg: SVGSVGElement) {
|
||||
const groups: SVGGElement[] = [];
|
||||
|
||||
BASIN_IDS.forEach((item) => {
|
||||
let group: SVGGElement | null = null;
|
||||
for (const id of item.labels) {
|
||||
group = svg.getElementById(id) as SVGGElement | null;
|
||||
if (group) break;
|
||||
}
|
||||
if (!group) return;
|
||||
|
||||
group.classList.add("basin");
|
||||
group.dataset.key = item.key;
|
||||
group.dataset.label = item.labels[0] ?? "";
|
||||
|
||||
group.querySelectorAll("path").forEach((path) => {
|
||||
path.style.fill = "rgba(0, 150, 255, 0.2)";
|
||||
path.style.stroke = "#0066cc";
|
||||
path.style.strokeWidth = "1";
|
||||
path.style.vectorEffect = "non-scaling-stroke";
|
||||
});
|
||||
|
||||
groups.push(group);
|
||||
});
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
function createLabels(labelsLayer: HTMLDivElement, groups: SVGGElement[]) {
|
||||
labelsLayer.innerHTML = "";
|
||||
return groups.map((group) => {
|
||||
const label = document.createElement("div");
|
||||
label.className = styles.basinLabel ?? "";
|
||||
label.textContent = group.dataset.label ?? "";
|
||||
labelsLayer.appendChild(label);
|
||||
return label;
|
||||
});
|
||||
}
|
||||
|
||||
function applyViewBox(svg: SVGSVGElement, stage: HTMLDivElement) {
|
||||
if (!svg.hasAttribute("viewBox")) {
|
||||
const bbox = svg.getBBox();
|
||||
svg.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" "));
|
||||
}
|
||||
|
||||
const viewBox = (svg.getAttribute("viewBox") || "").split(/\s+/).map(Number);
|
||||
if (viewBox.length === 4 && viewBox[3]! > 0)
|
||||
stage.style.aspectRatio = String(viewBox[2]! / viewBox[3]!);
|
||||
|
||||
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
||||
svg.style.width = "100%";
|
||||
svg.style.height = "100%";
|
||||
}
|
||||
|
||||
function positionLabels(
|
||||
stage: HTMLDivElement,
|
||||
svg: SVGSVGElement,
|
||||
groups: SVGGElement[],
|
||||
labels: HTMLDivElement[],
|
||||
) {
|
||||
const stageRect = stage.getBoundingClientRect();
|
||||
const inset = parseFloat(getComputedStyle(stage).getPropertyValue("--map-inset") || "0") / 100;
|
||||
const left = stageRect.width * inset;
|
||||
const top = stageRect.height * inset;
|
||||
const width = stageRect.width * (1 - inset * 2);
|
||||
const height = stageRect.height * (1 - inset * 2);
|
||||
const [viewX, viewY, viewWidth, viewHeight] = (svg.getAttribute("viewBox") || DEFAULT_VIEWBOX)
|
||||
.split(/\s+/)
|
||||
.map(Number);
|
||||
|
||||
const svgRect = svg.getBoundingClientRect();
|
||||
const offsetX = svgRect.left - stageRect.left - left;
|
||||
const offsetY = svgRect.top - stageRect.top - top;
|
||||
|
||||
groups.forEach((group, index) => {
|
||||
const key = group.dataset.key;
|
||||
const manual = key ? (MANUAL_LABEL_POS as Record<string, ManualLabelPos>)[key] : undefined;
|
||||
let posX: number;
|
||||
let posY: number;
|
||||
|
||||
if (manual && (("x" in manual && "y" in manual) || ("px" in manual && "py" in manual))) {
|
||||
if ("px" in manual) {
|
||||
posX = left + (manual.px || 0) + offsetX;
|
||||
posY = top + (manual.py || 0) + offsetY;
|
||||
} else {
|
||||
posX = left + width * (manual.x || 0) + offsetX;
|
||||
posY = top + height * (manual.y || 0) + offsetY;
|
||||
}
|
||||
} else {
|
||||
const bbox = group.getBBox();
|
||||
const centerX = bbox.x + bbox.width / 2;
|
||||
const centerY = bbox.y + bbox.height / 2;
|
||||
posX = ((centerX - viewX!) / viewWidth!) * width + left + offsetX;
|
||||
posY = ((centerY - viewY!) / viewHeight!) * height + top + offsetY;
|
||||
}
|
||||
|
||||
const label = labels[index];
|
||||
if (!label) return;
|
||||
label.style.left = `${posX}px`;
|
||||
label.style.top = `${posY + LABEL_OFFSET_Y_PX}px`;
|
||||
});
|
||||
}
|
||||
|
||||
export function useNationalAsMap() {
|
||||
const stageRef = useRef<HTMLDivElement>(null);
|
||||
const svgLayerRef = useRef<HTMLDivElement>(null);
|
||||
const labelsLayerRef = useRef<HTMLDivElement>(null);
|
||||
const popupRef = useRef<HTMLDivElement>(null);
|
||||
const [popupContent, setPopupContent] = useState<MapPopupContent>(INITIAL_POPUP_CONTENT);
|
||||
|
||||
const showFor = useCallback((group: SVGGElement) => {
|
||||
const stage = stageRef.current;
|
||||
const popup = popupRef.current;
|
||||
if (!stage || !popup) return;
|
||||
|
||||
setPopupContent({
|
||||
title: resolveBasinName(group),
|
||||
items: [...PLACEHOLDER_POPUP_ITEMS],
|
||||
});
|
||||
showPopupAtGroup(stage, popup, group);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController();
|
||||
let svg: SVGSVGElement | null = null;
|
||||
let groups: SVGGElement[] = [];
|
||||
let labels: HTMLDivElement[] = [];
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let positionLabelsHandler: (() => void) | null = null;
|
||||
let clearActive: (() => void) | null = null;
|
||||
let handleStageClick: (() => void) | null = null;
|
||||
|
||||
const listeners: Array<{
|
||||
target: EventTarget;
|
||||
type: string;
|
||||
listener: EventListener;
|
||||
}> = [];
|
||||
|
||||
function addListener(target: EventTarget, type: string, listener: EventListener) {
|
||||
target.addEventListener(type, listener);
|
||||
listeners.push({ target, type, listener });
|
||||
}
|
||||
|
||||
fetch(SVG_URL, { signal: abortController.signal })
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
return response.text();
|
||||
})
|
||||
.then((svgText) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
if (!svgLayerRef.current || !stageRef.current) return;
|
||||
|
||||
svgLayerRef.current.innerHTML = svgText;
|
||||
svg = svgLayerRef.current.querySelector("svg");
|
||||
if (!svg) throw new Error("SVG element not found in loaded content");
|
||||
|
||||
applyViewBox(svg, stageRef.current);
|
||||
groups = collectBasinGroups(svg);
|
||||
labels = labelsLayerRef.current ? createLabels(labelsLayerRef.current, groups) : [];
|
||||
|
||||
clearActive = () => {
|
||||
groups.forEach((group) => group.classList.remove("active"));
|
||||
hidePopup(popupRef.current);
|
||||
labels.forEach((label) => label.classList.remove("active"));
|
||||
};
|
||||
|
||||
const revealGroup = (group: SVGGElement) => {
|
||||
showFor(group);
|
||||
const index = groups.indexOf(group);
|
||||
labels.forEach((label) => label.classList.remove("active"));
|
||||
if (index >= 0) labels[index]?.classList.add("active");
|
||||
};
|
||||
|
||||
groups.forEach((group) => {
|
||||
addListener(group, "mouseenter", () => {
|
||||
clearActive?.();
|
||||
group.classList.add("active");
|
||||
revealGroup(group);
|
||||
});
|
||||
addListener(group, "mousemove", () => {
|
||||
if (group.classList.contains("active")) revealGroup(group);
|
||||
});
|
||||
addListener(group, "mouseleave", () => clearActive?.());
|
||||
addListener(group, "click", (event) => {
|
||||
event.stopPropagation();
|
||||
clearActive?.();
|
||||
group.classList.add("active");
|
||||
revealGroup(group);
|
||||
});
|
||||
});
|
||||
|
||||
handleStageClick = () => clearActive?.();
|
||||
addListener(stageRef.current, "mouseleave", () => clearActive?.());
|
||||
addListener(stageRef.current, "click", () => handleStageClick?.());
|
||||
|
||||
positionLabelsHandler = () => {
|
||||
if (!stageRef.current || !svg) return;
|
||||
positionLabels(stageRef.current, svg, groups, labels);
|
||||
};
|
||||
positionLabelsHandler();
|
||||
window.addEventListener("resize", positionLabelsHandler);
|
||||
resizeObserver = new ResizeObserver(positionLabelsHandler);
|
||||
resizeObserver.observe(stageRef.current);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
const message = error instanceof Error ? error.message : "알 수 없는 오류";
|
||||
if (svgLayerRef.current) {
|
||||
svgLayerRef.current.innerHTML = `<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #666; font-size: 14px;">지도를 불러올 수 없습니다. (${message})</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
listeners.forEach(({ target, type, listener }) => {
|
||||
target.removeEventListener(type, listener);
|
||||
});
|
||||
if (positionLabelsHandler) window.removeEventListener("resize", positionLabelsHandler);
|
||||
resizeObserver?.disconnect();
|
||||
if (svgLayerRef.current) svgLayerRef.current.innerHTML = "";
|
||||
if (labelsLayerRef.current) labelsLayerRef.current.innerHTML = "";
|
||||
};
|
||||
}, [showFor]);
|
||||
|
||||
const popupGridStyle = {
|
||||
minWidth: 160,
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${Math.max(1, popupContent.items.length)}, 1fr)`,
|
||||
} satisfies CSSProperties;
|
||||
|
||||
return {
|
||||
stageRef,
|
||||
svgLayerRef,
|
||||
labelsLayerRef,
|
||||
popupRef,
|
||||
popupContent,
|
||||
popupGridStyle,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export const MANUAL_LABEL_POS = {
|
||||
han: { x: 0.52, y: 0.14 },
|
||||
geum: { x: 0.28, y: 0.47 },
|
||||
nakdong: { x: 0.64, y: 0.47 },
|
||||
yeongsan: { x: 0.18, y: 0.77 },
|
||||
} as const;
|
||||
|
||||
export const BASIN_DATA = {
|
||||
han: { name: "한강" },
|
||||
geum: { name: "금강" },
|
||||
nakdong: { name: "낙동강" },
|
||||
yeongsan: { name: "영산강" },
|
||||
} as const;
|
||||
|
||||
export const BASIN_IDS = [
|
||||
{ key: "han", labels: ["한강", "han"] },
|
||||
{ key: "geum", labels: ["금강", "geum"] },
|
||||
{ key: "nakdong", labels: ["낙동강", "nakdong"] },
|
||||
{ key: "yeongsan", labels: ["영산강", "yeongsan"] },
|
||||
] as const;
|
||||
|
||||
export type BasinKey = keyof typeof BASIN_DATA;
|
||||
|
||||
export interface ManualLabelPos {
|
||||
x?: number;
|
||||
y?: number;
|
||||
px?: number;
|
||||
py?: number;
|
||||
}
|
||||
@@ -11,9 +11,11 @@ export function DashboardPage() {
|
||||
const { data, isPending, isError, dataUpdatedAt } = useGetMainDashboard();
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 px-[1.8rem] pb-6 lg:grid-cols-2">
|
||||
<NationalAsMap />
|
||||
<div className="flex min-h-0 flex-col gap-4">
|
||||
<div className="flex gap-4 px-[1.8rem] pb-6">
|
||||
<div className="flex-[0.5]">
|
||||
<NationalAsMap />
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-4">
|
||||
<DocPrgrsSttsCards items={data?.docPrgrsSttsList} isPending={isPending} isError={isError} />
|
||||
<DashboardNotiList
|
||||
notiList={data?.notiList}
|
||||
|
||||
@@ -1,13 +1,84 @@
|
||||
import { Skeleton } from "@common/components";
|
||||
"use client";
|
||||
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
import { useNationalAsMap } from "../lib/use-national-as-map";
|
||||
|
||||
import styles from "../lib/national-as-map.module.css";
|
||||
|
||||
export function NationalAsMap() {
|
||||
const { stageRef, svgLayerRef, labelsLayerRef, popupRef, popupContent, popupGridStyle } =
|
||||
useNationalAsMap();
|
||||
const popupColumnSpan = Math.max(1, popupContent.items.length);
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 flex h-full min-h-[40rem] flex-col bg-background">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">전국 행정지원 지도</h3>
|
||||
</div>
|
||||
<div className="flex-1 p-4">
|
||||
<Skeleton className="h-full min-h-[36rem] w-full rounded-xl" />
|
||||
<div className={styles.mapArea}>
|
||||
<div
|
||||
ref={stageRef}
|
||||
style={
|
||||
{
|
||||
aspectRatio: 0.72 / 1,
|
||||
width: "100%",
|
||||
maxHeight: "100%",
|
||||
position: "relative",
|
||||
margin: "auto",
|
||||
"--map-inset": "0%",
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<div className={styles.bgLayer} />
|
||||
<div
|
||||
ref={svgLayerRef}
|
||||
className={`${styles.svgLayer} ${styles.svgContainer}`}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
/>
|
||||
<div
|
||||
ref={labelsLayerRef}
|
||||
className={styles.labelsLayer}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
ref={popupRef}
|
||||
className={`${styles.popup} invisible absolute z-20 overflow-hidden rounded-lg border border-blue-200 bg-blue-50 font-sans opacity-0 shadow-md`}
|
||||
style={popupGridStyle}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center border-b-2 border-blue-200 bg-blue-100 text-center font-bold"
|
||||
style={{ gridColumn: `span ${popupColumnSpan}` }}
|
||||
>
|
||||
{popupContent.title}
|
||||
</div>
|
||||
{popupContent.items.map((item, index) => (
|
||||
<div
|
||||
key={`header-${item.header}`}
|
||||
className={`flex items-center justify-center border-b-2 border-blue-200 text-center ${index < popupContent.items.length - 1 ? "border-r-2" : ""}`}
|
||||
>
|
||||
{item.header}
|
||||
</div>
|
||||
))}
|
||||
{popupContent.items.map((item, index) => (
|
||||
<div
|
||||
key={`data-${item.header}`}
|
||||
className={`flex items-center justify-center text-center ${index < popupContent.items.length - 1 ? "border-r-2" : ""}`}
|
||||
>
|
||||
{item.data}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user