@mint-ui/map 1.2.0-test.11 → 1.2.0-test.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/mint-map/core/MintMapCore.js +1 -2
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongCanvasLayer.d.ts +94 -20
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongCanvasLayer.js +108 -57
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.d.ts +7 -7
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/renderer.d.ts +3 -3
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/types.d.ts +177 -27
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.d.ts +21 -5
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.js +24 -0
- package/dist/components/mint-map/google/GoogleMintMapController.js +1 -0
- package/dist/components/mint-map/kakao/KakaoMintMapController.js +1 -0
- package/dist/components/mint-map/naver/NaverMintMapController.js +1 -0
- package/dist/index.es.js +132 -58
- package/dist/index.js +1 -0
- package/dist/index.umd.js +132 -57
- package/package.json +1 -1
- package/dist/components/mint-map/core/advanced/woongCanvas/ClusterMarker.d.ts +0 -11
package/dist/index.es.js
CHANGED
|
@@ -961,6 +961,29 @@ var hexToRgba = function (hexColor, alpha) {
|
|
|
961
961
|
|
|
962
962
|
throw new Error('Invalid hex color format');
|
|
963
963
|
};
|
|
964
|
+
var tempCanvas = document.createElement('canvas');
|
|
965
|
+
var tempCtx = tempCanvas.getContext('2d');
|
|
966
|
+
/**
|
|
967
|
+
* 텍스트 박스의 너비를 계산합니다.
|
|
968
|
+
*
|
|
969
|
+
* @param {Object} params - 파라미터 객체
|
|
970
|
+
* @param {string} params.text - 측정할 텍스트
|
|
971
|
+
* @param {string} params.fontConfig - 폰트 설정 (예: 'bold 16px Arial')
|
|
972
|
+
* @param {number} params.padding - 텍스트 박스에 적용할 패딩 값
|
|
973
|
+
* @param {number} params.minWidth - 최소 너비
|
|
974
|
+
* @returns {number} 계산된 텍스트 박스의 너비
|
|
975
|
+
*/
|
|
976
|
+
|
|
977
|
+
var calculateTextBoxWidth = function (_a) {
|
|
978
|
+
var text = _a.text,
|
|
979
|
+
fontConfig = _a.fontConfig,
|
|
980
|
+
padding = _a.padding,
|
|
981
|
+
minWidth = _a.minWidth;
|
|
982
|
+
if (!tempCtx) return 0;
|
|
983
|
+
tempCtx.font = fontConfig;
|
|
984
|
+
var textWidth = tempCtx.measureText(text).width;
|
|
985
|
+
return Math.max(minWidth, textWidth + padding);
|
|
986
|
+
};
|
|
964
987
|
|
|
965
988
|
var KonvaMarkerContext = createContext(null);
|
|
966
989
|
var KonvaMarkerProvider = function (_a) {
|
|
@@ -5613,20 +5636,8 @@ var renderPolygonEvent = function (selectedFillColor, selectedStrokeColor, selec
|
|
|
5613
5636
|
// 메인 컴포넌트
|
|
5614
5637
|
// ============================================================================
|
|
5615
5638
|
|
|
5616
|
-
/**
|
|
5617
|
-
* Konva 기반 고성능 마커/폴리곤 렌더링 컴포넌트
|
|
5618
|
-
*
|
|
5619
|
-
* 특징:
|
|
5620
|
-
* - Base/Event 레이어 분리로 성능 최적화
|
|
5621
|
-
* - LRU 캐시로 좌표 변환 결과 캐싱
|
|
5622
|
-
* - Spatial Hash Grid로 빠른 Hit Test
|
|
5623
|
-
* - Viewport Culling으로 보이는 영역만 렌더링
|
|
5624
|
-
*
|
|
5625
|
-
* @template T 마커 데이터의 추가 속성 타입
|
|
5626
|
-
*/
|
|
5627
|
-
|
|
5628
5639
|
var WoongCanvasLayerComponent = function (props) {
|
|
5629
|
-
var
|
|
5640
|
+
var data = props.data,
|
|
5630
5641
|
dataType = props.dataType,
|
|
5631
5642
|
onClick = props.onClick,
|
|
5632
5643
|
onMouseOver = props.onMouseOver,
|
|
@@ -5645,11 +5656,13 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
5645
5656
|
externalSelectedItem = props.selectedItem,
|
|
5646
5657
|
_f = props.disableInteraction,
|
|
5647
5658
|
disableInteraction = _f === void 0 ? false : _f,
|
|
5648
|
-
options = __rest(props, ["
|
|
5659
|
+
options = __rest(props, ["data", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // renderConfig 추출 (MARKER 모드에서만 존재)
|
|
5660
|
+
|
|
5661
|
+
|
|
5662
|
+
var renderConfig = dataType === CanvasDataType.MARKER ? props.renderConfig : undefined; // --------------------------------------------------------------------------
|
|
5649
5663
|
// Hooks & Context
|
|
5650
5664
|
// --------------------------------------------------------------------------
|
|
5651
5665
|
|
|
5652
|
-
|
|
5653
5666
|
var controller = useMintMapController();
|
|
5654
5667
|
var context = useKonvaMarkerContext();
|
|
5655
5668
|
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // --------------------------------------------------------------------------
|
|
@@ -5670,9 +5683,9 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
5670
5683
|
// Data Refs - 선택 및 Hover 상태 관리
|
|
5671
5684
|
// --------------------------------------------------------------------------
|
|
5672
5685
|
|
|
5673
|
-
/**
|
|
5686
|
+
/** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
|
|
5674
5687
|
|
|
5675
|
-
var markersRef = useRef(
|
|
5688
|
+
var markersRef = useRef(data); // --------------------------------------------------------------------------
|
|
5676
5689
|
// State Refs - 선택 및 Hover 상태 관리
|
|
5677
5690
|
// --------------------------------------------------------------------------
|
|
5678
5691
|
|
|
@@ -5844,6 +5857,7 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
5844
5857
|
};
|
|
5845
5858
|
/**
|
|
5846
5859
|
* 마커 좌표 변환 결과를 캐시하고 반환
|
|
5860
|
+
*
|
|
5847
5861
|
* @param markerData 마커 데이터
|
|
5848
5862
|
* @returns 변환된 좌표 또는 null
|
|
5849
5863
|
*/
|
|
@@ -5980,7 +5994,8 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
5980
5994
|
ctx: ctx,
|
|
5981
5995
|
items: visibleMarkers,
|
|
5982
5996
|
selectedIds: selectedIdsRef.current,
|
|
5983
|
-
utils: renderUtils
|
|
5997
|
+
utils: renderUtils,
|
|
5998
|
+
config: renderConfig
|
|
5984
5999
|
});
|
|
5985
6000
|
},
|
|
5986
6001
|
perfectDrawEnabled: false,
|
|
@@ -6010,7 +6025,8 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6010
6025
|
layer: layer,
|
|
6011
6026
|
selectedIds: selectedIdsRef.current,
|
|
6012
6027
|
items: markersRef.current,
|
|
6013
|
-
utils: renderUtils
|
|
6028
|
+
utils: renderUtils,
|
|
6029
|
+
config: renderConfig
|
|
6014
6030
|
});
|
|
6015
6031
|
};
|
|
6016
6032
|
/**
|
|
@@ -6051,7 +6067,8 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6051
6067
|
hoveredItem: hoveredItemRef.current,
|
|
6052
6068
|
utils: renderUtils,
|
|
6053
6069
|
selectedItems: selectedItems,
|
|
6054
|
-
selectedItem: selectedItemRef.current
|
|
6070
|
+
selectedItem: selectedItemRef.current,
|
|
6071
|
+
config: renderConfig
|
|
6055
6072
|
});
|
|
6056
6073
|
},
|
|
6057
6074
|
perfectDrawEnabled: false,
|
|
@@ -6314,11 +6331,11 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6314
6331
|
|
|
6315
6332
|
try {
|
|
6316
6333
|
var clickedOffset = controller.positionToOffset(event.param.position);
|
|
6317
|
-
var
|
|
6334
|
+
var data_1 = findData(clickedOffset);
|
|
6318
6335
|
|
|
6319
|
-
if (
|
|
6320
|
-
handleLocalClick(
|
|
6321
|
-
onClick(
|
|
6336
|
+
if (data_1) {
|
|
6337
|
+
handleLocalClick(data_1);
|
|
6338
|
+
onClick(data_1, selectedIdsRef.current);
|
|
6322
6339
|
}
|
|
6323
6340
|
} catch (error) {
|
|
6324
6341
|
console.error('[WoongKonvaMarker] handleClick error:', error);
|
|
@@ -6595,13 +6612,13 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6595
6612
|
|
|
6596
6613
|
doRenderEvent();
|
|
6597
6614
|
}, [externalSelectedItem]); // --------------------------------------------------------------------------
|
|
6598
|
-
// Lifecycle:
|
|
6615
|
+
// Lifecycle: 데이터 변경 시 렌더링
|
|
6599
6616
|
// --------------------------------------------------------------------------
|
|
6600
6617
|
|
|
6601
6618
|
useEffect(function () {
|
|
6602
6619
|
if (!stageRef.current) return; // markersRef 동기화
|
|
6603
6620
|
|
|
6604
|
-
markersRef.current =
|
|
6621
|
+
markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
|
|
6605
6622
|
|
|
6606
6623
|
if (containerRef.current) {
|
|
6607
6624
|
containerRef.current.style.transform = '';
|
|
@@ -6618,18 +6635,18 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6618
6635
|
/**
|
|
6619
6636
|
* 선택 상태 동기화 (최적화 버전)
|
|
6620
6637
|
*
|
|
6621
|
-
*
|
|
6638
|
+
* data가 변경되면 selectedItemsMapRef도 업데이트 필요
|
|
6622
6639
|
* (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
|
|
6623
6640
|
*
|
|
6624
|
-
* 🔥 중요: 화면 밖
|
|
6625
|
-
* - 현재
|
|
6641
|
+
* 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
|
|
6642
|
+
* - 현재 data에 있으면 최신 데이터로 업데이트
|
|
6626
6643
|
* - 없으면 기존 selectedItemsMapRef의 데이터 유지
|
|
6627
6644
|
*
|
|
6628
|
-
* 최적화:
|
|
6629
|
-
* - O(전체
|
|
6645
|
+
* 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
|
|
6646
|
+
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
6630
6647
|
*/
|
|
6631
6648
|
|
|
6632
|
-
var markersMap = new Map(
|
|
6649
|
+
var markersMap = new Map(data.map(function (m) {
|
|
6633
6650
|
return [m.id, m];
|
|
6634
6651
|
}));
|
|
6635
6652
|
var newSelectedItemsMap = new Map();
|
|
@@ -6652,7 +6669,7 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6652
6669
|
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
6653
6670
|
|
|
6654
6671
|
renderAllImmediate();
|
|
6655
|
-
}, [
|
|
6672
|
+
}, [data]);
|
|
6656
6673
|
return createPortal(React.createElement("div", {
|
|
6657
6674
|
ref: containerRef,
|
|
6658
6675
|
style: {
|
|
@@ -6663,27 +6680,96 @@ var WoongCanvasLayerComponent = function (props) {
|
|
|
6663
6680
|
}), divElement);
|
|
6664
6681
|
};
|
|
6665
6682
|
/**
|
|
6666
|
-
*
|
|
6683
|
+
* 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
|
|
6684
|
+
*
|
|
6685
|
+
* ## 📌 주요 특징
|
|
6686
|
+
* - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
|
|
6687
|
+
* - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
|
|
6688
|
+
* - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
|
|
6689
|
+
* - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
|
|
6690
|
+
* - **Viewport Culling**: 화면에 보이는 영역만 렌더링
|
|
6691
|
+
* - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
|
|
6667
6692
|
*
|
|
6668
|
-
*
|
|
6669
|
-
* 1. markers 배열 비교
|
|
6670
|
-
* 2. selectedItems 배열 비교 (외부 제어)
|
|
6693
|
+
* ## 🎯 사용 방법
|
|
6671
6694
|
*
|
|
6672
|
-
*
|
|
6695
|
+
* ### 1️⃣ POLYGON 모드 (자동 렌더링)
|
|
6696
|
+
* ```tsx
|
|
6697
|
+
* <WoongCanvasLayer
|
|
6698
|
+
* dataType={CanvasDataType.POLYGON}
|
|
6699
|
+
* data={polygons}
|
|
6700
|
+
* baseFillColor="rgba(255, 100, 100, 0.5)"
|
|
6701
|
+
* baseStrokeColor="rgba(200, 50, 50, 0.8)"
|
|
6702
|
+
* baseLineWidth={2}
|
|
6703
|
+
* selectedFillColor="rgba(255, 193, 7, 0.7)"
|
|
6704
|
+
* selectedStrokeColor="rgba(255, 152, 0, 1)"
|
|
6705
|
+
* selectedLineWidth={4}
|
|
6706
|
+
* hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
|
|
6707
|
+
* hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
|
|
6708
|
+
* hoveredLineWidth={3} // optional
|
|
6709
|
+
* enableMultiSelect={true}
|
|
6710
|
+
* onClick={handleClick}
|
|
6711
|
+
* />
|
|
6712
|
+
* ```
|
|
6713
|
+
*
|
|
6714
|
+
* ### 2️⃣ MARKER 모드 (커스텀 렌더링)
|
|
6715
|
+
* ```tsx
|
|
6716
|
+
* <WoongCanvasLayer
|
|
6717
|
+
* dataType={CanvasDataType.MARKER}
|
|
6718
|
+
* data={markers}
|
|
6719
|
+
* renderBase={renderMarkerBase} // required
|
|
6720
|
+
* renderAnimation={renderMarkerAnimation} // optional
|
|
6721
|
+
* renderEvent={renderMarkerEvent} // optional
|
|
6722
|
+
* topOnHover={true}
|
|
6723
|
+
* onClick={handleClick}
|
|
6724
|
+
* />
|
|
6725
|
+
* ```
|
|
6726
|
+
*
|
|
6727
|
+
* ## 📊 데이터 형식
|
|
6728
|
+
* ```typescript
|
|
6729
|
+
* const data: KonvaCanvasData<T>[] = [
|
|
6730
|
+
* {
|
|
6731
|
+
* id: 'unique-id',
|
|
6732
|
+
* position: new Position(lat, lng),
|
|
6733
|
+
* // POLYGON: paths 필수
|
|
6734
|
+
* paths: [[[lat, lng], [lat, lng], ...]],
|
|
6735
|
+
* // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
|
|
6736
|
+
* boxWidth: 60,
|
|
6737
|
+
* boxHeight: 75,
|
|
6738
|
+
* // 커스텀 데이터
|
|
6739
|
+
* ...customData
|
|
6740
|
+
* }
|
|
6741
|
+
* ];
|
|
6742
|
+
* ```
|
|
6743
|
+
*
|
|
6744
|
+
* ## ⚡ 성능 최적화 팁
|
|
6745
|
+
* 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
|
|
6746
|
+
* 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
|
|
6747
|
+
* 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
|
|
6748
|
+
* 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
|
|
6749
|
+
*
|
|
6750
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
6751
|
+
*
|
|
6752
|
+
* @example
|
|
6753
|
+
* // 동적 boxWidth 계산 예시
|
|
6754
|
+
* const tempCtx = document.createElement('canvas').getContext('2d');
|
|
6755
|
+
* tempCtx.font = 'bold 15px Arial';
|
|
6756
|
+
* const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
|
|
6757
|
+
*
|
|
6758
|
+
* @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
|
|
6673
6759
|
*/
|
|
6674
6760
|
|
|
6675
6761
|
|
|
6676
6762
|
var WoongCanvasLayer = React.memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
|
|
6677
|
-
// 1.
|
|
6678
|
-
var
|
|
6679
|
-
var
|
|
6763
|
+
// 1. data 비교
|
|
6764
|
+
var prevData = prevProps.data;
|
|
6765
|
+
var nextData = nextProps.data; // 참조가 같으면 스킵
|
|
6680
6766
|
|
|
6681
|
-
if (
|
|
6767
|
+
if (prevData !== nextData) {
|
|
6682
6768
|
// 길이가 다르면 변경됨
|
|
6683
|
-
if (
|
|
6769
|
+
if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
|
|
6684
6770
|
|
|
6685
|
-
for (var i = 0; i <
|
|
6686
|
-
if (
|
|
6771
|
+
for (var i = 0; i < prevData.length; i++) {
|
|
6772
|
+
if (prevData[i].id !== nextData[i].id) {
|
|
6687
6773
|
return false; // 변경됨 → 리렌더링
|
|
6688
6774
|
}
|
|
6689
6775
|
}
|
|
@@ -6698,18 +6784,6 @@ var WoongCanvasLayer = React.memo(WoongCanvasLayerComponent, function (prevProps
|
|
|
6698
6784
|
return false; // 변경됨 → 리렌더링
|
|
6699
6785
|
}
|
|
6700
6786
|
|
|
6701
|
-
if (prevProps.onClick !== nextProps.onClick) {
|
|
6702
|
-
return false; // 변경됨 → 리렌더링
|
|
6703
|
-
}
|
|
6704
|
-
|
|
6705
|
-
if (prevProps.onMouseOver !== nextProps.onMouseOver) {
|
|
6706
|
-
return false; // 변경됨 → 리렌더링
|
|
6707
|
-
}
|
|
6708
|
-
|
|
6709
|
-
if (prevProps.onMouseOut !== nextProps.onMouseOut) {
|
|
6710
|
-
return false; // 변경됨 → 리렌더링
|
|
6711
|
-
}
|
|
6712
|
-
|
|
6713
6787
|
if (prevProps.disableInteraction !== nextProps.disableInteraction) {
|
|
6714
6788
|
return false; // 변경됨 → 리렌더링
|
|
6715
6789
|
}
|
|
@@ -9548,4 +9622,4 @@ function MintMap(_a) {
|
|
|
9548
9622
|
}), loading));
|
|
9549
9623
|
}
|
|
9550
9624
|
|
|
9551
|
-
export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController, KonvaMarkerProvider, LRUCache, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SPATIAL_GRID_CELL_SIZE, SVGCircle, SVGPolygon, SVGRect, Spacing, SpatialHashGrid, Status, WoongCanvasLayer, computeMarkerOffset, computePolygonOffsets, getClusterInfo, getMapOfType, hexToRgba, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, useKonvaMarkerContext, useMarkerMoving, useMintMapController, waiting };
|
|
9625
|
+
export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController, KonvaMarkerProvider, LRUCache, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SPATIAL_GRID_CELL_SIZE, SVGCircle, SVGPolygon, SVGRect, Spacing, SpatialHashGrid, Status, WoongCanvasLayer, calculateTextBoxWidth, computeMarkerOffset, computePolygonOffsets, getClusterInfo, getMapOfType, hexToRgba, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, useKonvaMarkerContext, useMarkerMoving, useMintMapController, waiting };
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,7 @@ Object.defineProperty(exports, 'CanvasDataType', {
|
|
|
65
65
|
enumerable: true,
|
|
66
66
|
get: function () { return types.CanvasDataType; }
|
|
67
67
|
});
|
|
68
|
+
exports.calculateTextBoxWidth = utils.calculateTextBoxWidth;
|
|
68
69
|
exports.computeMarkerOffset = utils.computeMarkerOffset;
|
|
69
70
|
exports.computePolygonOffsets = utils.computePolygonOffsets;
|
|
70
71
|
exports.hexToRgba = utils.hexToRgba;
|
package/dist/index.umd.js
CHANGED
|
@@ -965,6 +965,29 @@
|
|
|
965
965
|
|
|
966
966
|
throw new Error('Invalid hex color format');
|
|
967
967
|
};
|
|
968
|
+
var tempCanvas = document.createElement('canvas');
|
|
969
|
+
var tempCtx = tempCanvas.getContext('2d');
|
|
970
|
+
/**
|
|
971
|
+
* 텍스트 박스의 너비를 계산합니다.
|
|
972
|
+
*
|
|
973
|
+
* @param {Object} params - 파라미터 객체
|
|
974
|
+
* @param {string} params.text - 측정할 텍스트
|
|
975
|
+
* @param {string} params.fontConfig - 폰트 설정 (예: 'bold 16px Arial')
|
|
976
|
+
* @param {number} params.padding - 텍스트 박스에 적용할 패딩 값
|
|
977
|
+
* @param {number} params.minWidth - 최소 너비
|
|
978
|
+
* @returns {number} 계산된 텍스트 박스의 너비
|
|
979
|
+
*/
|
|
980
|
+
|
|
981
|
+
var calculateTextBoxWidth = function (_a) {
|
|
982
|
+
var text = _a.text,
|
|
983
|
+
fontConfig = _a.fontConfig,
|
|
984
|
+
padding = _a.padding,
|
|
985
|
+
minWidth = _a.minWidth;
|
|
986
|
+
if (!tempCtx) return 0;
|
|
987
|
+
tempCtx.font = fontConfig;
|
|
988
|
+
var textWidth = tempCtx.measureText(text).width;
|
|
989
|
+
return Math.max(minWidth, textWidth + padding);
|
|
990
|
+
};
|
|
968
991
|
|
|
969
992
|
var KonvaMarkerContext = React.createContext(null);
|
|
970
993
|
var KonvaMarkerProvider = function (_a) {
|
|
@@ -5617,20 +5640,8 @@
|
|
|
5617
5640
|
// 메인 컴포넌트
|
|
5618
5641
|
// ============================================================================
|
|
5619
5642
|
|
|
5620
|
-
/**
|
|
5621
|
-
* Konva 기반 고성능 마커/폴리곤 렌더링 컴포넌트
|
|
5622
|
-
*
|
|
5623
|
-
* 특징:
|
|
5624
|
-
* - Base/Event 레이어 분리로 성능 최적화
|
|
5625
|
-
* - LRU 캐시로 좌표 변환 결과 캐싱
|
|
5626
|
-
* - Spatial Hash Grid로 빠른 Hit Test
|
|
5627
|
-
* - Viewport Culling으로 보이는 영역만 렌더링
|
|
5628
|
-
*
|
|
5629
|
-
* @template T 마커 데이터의 추가 속성 타입
|
|
5630
|
-
*/
|
|
5631
|
-
|
|
5632
5643
|
var WoongCanvasLayerComponent = function (props) {
|
|
5633
|
-
var
|
|
5644
|
+
var data = props.data,
|
|
5634
5645
|
dataType = props.dataType,
|
|
5635
5646
|
onClick = props.onClick,
|
|
5636
5647
|
onMouseOver = props.onMouseOver,
|
|
@@ -5649,11 +5660,13 @@
|
|
|
5649
5660
|
externalSelectedItem = props.selectedItem,
|
|
5650
5661
|
_f = props.disableInteraction,
|
|
5651
5662
|
disableInteraction = _f === void 0 ? false : _f,
|
|
5652
|
-
options = tslib.__rest(props, ["
|
|
5663
|
+
options = tslib.__rest(props, ["data", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // renderConfig 추출 (MARKER 모드에서만 존재)
|
|
5664
|
+
|
|
5665
|
+
|
|
5666
|
+
var renderConfig = dataType === exports.CanvasDataType.MARKER ? props.renderConfig : undefined; // --------------------------------------------------------------------------
|
|
5653
5667
|
// Hooks & Context
|
|
5654
5668
|
// --------------------------------------------------------------------------
|
|
5655
5669
|
|
|
5656
|
-
|
|
5657
5670
|
var controller = useMintMapController();
|
|
5658
5671
|
var context = useKonvaMarkerContext();
|
|
5659
5672
|
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // --------------------------------------------------------------------------
|
|
@@ -5674,9 +5687,9 @@
|
|
|
5674
5687
|
// Data Refs - 선택 및 Hover 상태 관리
|
|
5675
5688
|
// --------------------------------------------------------------------------
|
|
5676
5689
|
|
|
5677
|
-
/**
|
|
5690
|
+
/** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
|
|
5678
5691
|
|
|
5679
|
-
var markersRef = React.useRef(
|
|
5692
|
+
var markersRef = React.useRef(data); // --------------------------------------------------------------------------
|
|
5680
5693
|
// State Refs - 선택 및 Hover 상태 관리
|
|
5681
5694
|
// --------------------------------------------------------------------------
|
|
5682
5695
|
|
|
@@ -5848,6 +5861,7 @@
|
|
|
5848
5861
|
};
|
|
5849
5862
|
/**
|
|
5850
5863
|
* 마커 좌표 변환 결과를 캐시하고 반환
|
|
5864
|
+
*
|
|
5851
5865
|
* @param markerData 마커 데이터
|
|
5852
5866
|
* @returns 변환된 좌표 또는 null
|
|
5853
5867
|
*/
|
|
@@ -5984,7 +5998,8 @@
|
|
|
5984
5998
|
ctx: ctx,
|
|
5985
5999
|
items: visibleMarkers,
|
|
5986
6000
|
selectedIds: selectedIdsRef.current,
|
|
5987
|
-
utils: renderUtils
|
|
6001
|
+
utils: renderUtils,
|
|
6002
|
+
config: renderConfig
|
|
5988
6003
|
});
|
|
5989
6004
|
},
|
|
5990
6005
|
perfectDrawEnabled: false,
|
|
@@ -6014,7 +6029,8 @@
|
|
|
6014
6029
|
layer: layer,
|
|
6015
6030
|
selectedIds: selectedIdsRef.current,
|
|
6016
6031
|
items: markersRef.current,
|
|
6017
|
-
utils: renderUtils
|
|
6032
|
+
utils: renderUtils,
|
|
6033
|
+
config: renderConfig
|
|
6018
6034
|
});
|
|
6019
6035
|
};
|
|
6020
6036
|
/**
|
|
@@ -6055,7 +6071,8 @@
|
|
|
6055
6071
|
hoveredItem: hoveredItemRef.current,
|
|
6056
6072
|
utils: renderUtils,
|
|
6057
6073
|
selectedItems: selectedItems,
|
|
6058
|
-
selectedItem: selectedItemRef.current
|
|
6074
|
+
selectedItem: selectedItemRef.current,
|
|
6075
|
+
config: renderConfig
|
|
6059
6076
|
});
|
|
6060
6077
|
},
|
|
6061
6078
|
perfectDrawEnabled: false,
|
|
@@ -6318,11 +6335,11 @@
|
|
|
6318
6335
|
|
|
6319
6336
|
try {
|
|
6320
6337
|
var clickedOffset = controller.positionToOffset(event.param.position);
|
|
6321
|
-
var
|
|
6338
|
+
var data_1 = findData(clickedOffset);
|
|
6322
6339
|
|
|
6323
|
-
if (
|
|
6324
|
-
handleLocalClick(
|
|
6325
|
-
onClick(
|
|
6340
|
+
if (data_1) {
|
|
6341
|
+
handleLocalClick(data_1);
|
|
6342
|
+
onClick(data_1, selectedIdsRef.current);
|
|
6326
6343
|
}
|
|
6327
6344
|
} catch (error) {
|
|
6328
6345
|
console.error('[WoongKonvaMarker] handleClick error:', error);
|
|
@@ -6599,13 +6616,13 @@
|
|
|
6599
6616
|
|
|
6600
6617
|
doRenderEvent();
|
|
6601
6618
|
}, [externalSelectedItem]); // --------------------------------------------------------------------------
|
|
6602
|
-
// Lifecycle:
|
|
6619
|
+
// Lifecycle: 데이터 변경 시 렌더링
|
|
6603
6620
|
// --------------------------------------------------------------------------
|
|
6604
6621
|
|
|
6605
6622
|
React.useEffect(function () {
|
|
6606
6623
|
if (!stageRef.current) return; // markersRef 동기화
|
|
6607
6624
|
|
|
6608
|
-
markersRef.current =
|
|
6625
|
+
markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
|
|
6609
6626
|
|
|
6610
6627
|
if (containerRef.current) {
|
|
6611
6628
|
containerRef.current.style.transform = '';
|
|
@@ -6622,18 +6639,18 @@
|
|
|
6622
6639
|
/**
|
|
6623
6640
|
* 선택 상태 동기화 (최적화 버전)
|
|
6624
6641
|
*
|
|
6625
|
-
*
|
|
6642
|
+
* data가 변경되면 selectedItemsMapRef도 업데이트 필요
|
|
6626
6643
|
* (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
|
|
6627
6644
|
*
|
|
6628
|
-
* 🔥 중요: 화면 밖
|
|
6629
|
-
* - 현재
|
|
6645
|
+
* 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
|
|
6646
|
+
* - 현재 data에 있으면 최신 데이터로 업데이트
|
|
6630
6647
|
* - 없으면 기존 selectedItemsMapRef의 데이터 유지
|
|
6631
6648
|
*
|
|
6632
|
-
* 최적화:
|
|
6633
|
-
* - O(전체
|
|
6649
|
+
* 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
|
|
6650
|
+
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
6634
6651
|
*/
|
|
6635
6652
|
|
|
6636
|
-
var markersMap = new Map(
|
|
6653
|
+
var markersMap = new Map(data.map(function (m) {
|
|
6637
6654
|
return [m.id, m];
|
|
6638
6655
|
}));
|
|
6639
6656
|
var newSelectedItemsMap = new Map();
|
|
@@ -6656,7 +6673,7 @@
|
|
|
6656
6673
|
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
6657
6674
|
|
|
6658
6675
|
renderAllImmediate();
|
|
6659
|
-
}, [
|
|
6676
|
+
}, [data]);
|
|
6660
6677
|
return reactDom.createPortal(React__default["default"].createElement("div", {
|
|
6661
6678
|
ref: containerRef,
|
|
6662
6679
|
style: {
|
|
@@ -6667,27 +6684,96 @@
|
|
|
6667
6684
|
}), divElement);
|
|
6668
6685
|
};
|
|
6669
6686
|
/**
|
|
6670
|
-
*
|
|
6687
|
+
* 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
|
|
6688
|
+
*
|
|
6689
|
+
* ## 📌 주요 특징
|
|
6690
|
+
* - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
|
|
6691
|
+
* - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
|
|
6692
|
+
* - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
|
|
6693
|
+
* - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
|
|
6694
|
+
* - **Viewport Culling**: 화면에 보이는 영역만 렌더링
|
|
6695
|
+
* - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
|
|
6671
6696
|
*
|
|
6672
|
-
*
|
|
6673
|
-
* 1. markers 배열 비교
|
|
6674
|
-
* 2. selectedItems 배열 비교 (외부 제어)
|
|
6697
|
+
* ## 🎯 사용 방법
|
|
6675
6698
|
*
|
|
6676
|
-
*
|
|
6699
|
+
* ### 1️⃣ POLYGON 모드 (자동 렌더링)
|
|
6700
|
+
* ```tsx
|
|
6701
|
+
* <WoongCanvasLayer
|
|
6702
|
+
* dataType={CanvasDataType.POLYGON}
|
|
6703
|
+
* data={polygons}
|
|
6704
|
+
* baseFillColor="rgba(255, 100, 100, 0.5)"
|
|
6705
|
+
* baseStrokeColor="rgba(200, 50, 50, 0.8)"
|
|
6706
|
+
* baseLineWidth={2}
|
|
6707
|
+
* selectedFillColor="rgba(255, 193, 7, 0.7)"
|
|
6708
|
+
* selectedStrokeColor="rgba(255, 152, 0, 1)"
|
|
6709
|
+
* selectedLineWidth={4}
|
|
6710
|
+
* hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
|
|
6711
|
+
* hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
|
|
6712
|
+
* hoveredLineWidth={3} // optional
|
|
6713
|
+
* enableMultiSelect={true}
|
|
6714
|
+
* onClick={handleClick}
|
|
6715
|
+
* />
|
|
6716
|
+
* ```
|
|
6717
|
+
*
|
|
6718
|
+
* ### 2️⃣ MARKER 모드 (커스텀 렌더링)
|
|
6719
|
+
* ```tsx
|
|
6720
|
+
* <WoongCanvasLayer
|
|
6721
|
+
* dataType={CanvasDataType.MARKER}
|
|
6722
|
+
* data={markers}
|
|
6723
|
+
* renderBase={renderMarkerBase} // required
|
|
6724
|
+
* renderAnimation={renderMarkerAnimation} // optional
|
|
6725
|
+
* renderEvent={renderMarkerEvent} // optional
|
|
6726
|
+
* topOnHover={true}
|
|
6727
|
+
* onClick={handleClick}
|
|
6728
|
+
* />
|
|
6729
|
+
* ```
|
|
6730
|
+
*
|
|
6731
|
+
* ## 📊 데이터 형식
|
|
6732
|
+
* ```typescript
|
|
6733
|
+
* const data: KonvaCanvasData<T>[] = [
|
|
6734
|
+
* {
|
|
6735
|
+
* id: 'unique-id',
|
|
6736
|
+
* position: new Position(lat, lng),
|
|
6737
|
+
* // POLYGON: paths 필수
|
|
6738
|
+
* paths: [[[lat, lng], [lat, lng], ...]],
|
|
6739
|
+
* // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
|
|
6740
|
+
* boxWidth: 60,
|
|
6741
|
+
* boxHeight: 75,
|
|
6742
|
+
* // 커스텀 데이터
|
|
6743
|
+
* ...customData
|
|
6744
|
+
* }
|
|
6745
|
+
* ];
|
|
6746
|
+
* ```
|
|
6747
|
+
*
|
|
6748
|
+
* ## ⚡ 성능 최적화 팁
|
|
6749
|
+
* 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
|
|
6750
|
+
* 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
|
|
6751
|
+
* 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
|
|
6752
|
+
* 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
|
|
6753
|
+
*
|
|
6754
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
6755
|
+
*
|
|
6756
|
+
* @example
|
|
6757
|
+
* // 동적 boxWidth 계산 예시
|
|
6758
|
+
* const tempCtx = document.createElement('canvas').getContext('2d');
|
|
6759
|
+
* tempCtx.font = 'bold 15px Arial';
|
|
6760
|
+
* const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
|
|
6761
|
+
*
|
|
6762
|
+
* @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
|
|
6677
6763
|
*/
|
|
6678
6764
|
|
|
6679
6765
|
|
|
6680
6766
|
var WoongCanvasLayer = React__default["default"].memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
|
|
6681
|
-
// 1.
|
|
6682
|
-
var
|
|
6683
|
-
var
|
|
6767
|
+
// 1. data 비교
|
|
6768
|
+
var prevData = prevProps.data;
|
|
6769
|
+
var nextData = nextProps.data; // 참조가 같으면 스킵
|
|
6684
6770
|
|
|
6685
|
-
if (
|
|
6771
|
+
if (prevData !== nextData) {
|
|
6686
6772
|
// 길이가 다르면 변경됨
|
|
6687
|
-
if (
|
|
6773
|
+
if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
|
|
6688
6774
|
|
|
6689
|
-
for (var i = 0; i <
|
|
6690
|
-
if (
|
|
6775
|
+
for (var i = 0; i < prevData.length; i++) {
|
|
6776
|
+
if (prevData[i].id !== nextData[i].id) {
|
|
6691
6777
|
return false; // 변경됨 → 리렌더링
|
|
6692
6778
|
}
|
|
6693
6779
|
}
|
|
@@ -6702,18 +6788,6 @@
|
|
|
6702
6788
|
return false; // 변경됨 → 리렌더링
|
|
6703
6789
|
}
|
|
6704
6790
|
|
|
6705
|
-
if (prevProps.onClick !== nextProps.onClick) {
|
|
6706
|
-
return false; // 변경됨 → 리렌더링
|
|
6707
|
-
}
|
|
6708
|
-
|
|
6709
|
-
if (prevProps.onMouseOver !== nextProps.onMouseOver) {
|
|
6710
|
-
return false; // 변경됨 → 리렌더링
|
|
6711
|
-
}
|
|
6712
|
-
|
|
6713
|
-
if (prevProps.onMouseOut !== nextProps.onMouseOut) {
|
|
6714
|
-
return false; // 변경됨 → 리렌더링
|
|
6715
|
-
}
|
|
6716
|
-
|
|
6717
6791
|
if (prevProps.disableInteraction !== nextProps.disableInteraction) {
|
|
6718
6792
|
return false; // 변경됨 → 리렌더링
|
|
6719
6793
|
}
|
|
@@ -9597,6 +9671,7 @@
|
|
|
9597
9671
|
exports.SpatialHashGrid = SpatialHashGrid;
|
|
9598
9672
|
exports.Status = Status;
|
|
9599
9673
|
exports.WoongCanvasLayer = WoongCanvasLayer;
|
|
9674
|
+
exports.calculateTextBoxWidth = calculateTextBoxWidth;
|
|
9600
9675
|
exports.computeMarkerOffset = computeMarkerOffset;
|
|
9601
9676
|
exports.computePolygonOffsets = computePolygonOffsets;
|
|
9602
9677
|
exports.getClusterInfo = getClusterInfo;
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
interface Cluster {
|
|
3
|
-
buildingCnt: number;
|
|
4
|
-
lat: number;
|
|
5
|
-
lng: number;
|
|
6
|
-
}
|
|
7
|
-
export interface ClusterMarkerProps {
|
|
8
|
-
cluster: Cluster;
|
|
9
|
-
}
|
|
10
|
-
declare const ClusterMarker: ({ cluster }: ClusterMarkerProps) => JSX.Element;
|
|
11
|
-
export default ClusterMarker;
|