From d9fb8433d4ad5981e16cb8e7a90be78e7ec24f9d Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 30 Jan 2025 15:10:57 +0300 Subject: [PATCH 01/19] YandexMap widget (v3) --- .../ChartKit/plugins/YandexMapV3/index.ts | 8 +++ .../YandexMapV3/renderer/YandexMapWidget.scss | 7 +++ .../YandexMapV3/renderer/YandexMapWidget.tsx | 52 +++++++++++++++++++ .../renderer/components/Map/Map.tsx | 25 +++++++++ .../plugins/YandexMapV3/renderer/utils.ts | 32 ++++++++++++ .../plugins/YandexMapV3/renderer/yamap.ts | 15 ++++++ .../ChartKit/plugins/YandexMapV3/types.ts | 34 ++++++++++++ .../ChartKit/plugins/index.ts | 3 +- 8 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts new file mode 100644 index 0000000000..fb01647950 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts @@ -0,0 +1,8 @@ +import React from 'react'; + +import type {ChartKitPlugin} from '@gravity-ui/chartkit'; + +export const YandexMapV3Plugin: ChartKitPlugin = { + type: 'yandexmap', + renderer: React.lazy(() => import('./renderer/YandexMapWidget')), +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.scss new file mode 100644 index 0000000000..751c5044a1 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.scss @@ -0,0 +1,7 @@ +.chartkit-ymap-widget { + display: flex; + height: 100%; + width: 100%; + outline: none; + justify-content: center; +} diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx new file mode 100644 index 0000000000..2eb9497966 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import {CHARTKIT_ERROR_CODE, ChartKitError} from '@gravity-ui/chartkit'; +import block from 'bem-cn-lite'; + +import Performance from '../../../../modules/perfomance'; +import {getRandomCKId} from '../../../helpers/getRandomCKId'; +import type {YandexMapWidgetProps} from '../types'; +import {isYmapsReady} from './yamap'; +import {Loader} from '@gravity-ui/uikit'; + +import './YandexMapWidget.scss'; + +const Map = React.lazy(() => import('./components/Map/Map')); + +const b = block('chartkit-ymap-widget'); + +export const YandexMapWidget = (props: YandexMapWidgetProps) => { + const { + id, + // onLoad, + data: {data: originalData, config, libraryConfig}, + // _splitTooltip, + } = props; + + const [isLoading, setLoading] = React.useState(true); + + const generatedId = React.useMemo(() => `${id}_${getRandomCKId()}`, [originalData, config, id]); + Performance.mark(generatedId); + + React.useEffect(() => { + isYmapsReady({ + apiKey: libraryConfig?.apiKey ?? '' + }).then(() => { + // setLoading(false); + }) + }, []); + + if (!originalData || (typeof originalData === 'object' && !Object.keys(originalData).length)) { + throw new ChartKitError({ + code: CHARTKIT_ERROR_CODE.NO_DATA, + }); + } + + return ( +
+ {isLoading ? : } +
+ ); +}; + +export default YandexMapWidget; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx new file mode 100644 index 0000000000..cc9f15b13f --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDom from 'react-dom'; + +const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]); + +export const reactify = ymaps3React.reactify.bindTo(React, ReactDom); +export const {YMap, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer} = reactify.module(ymaps3); + +type Props = {}; + +export const Map = (_props: Props) => { + const location = { + center: [25.229762, 55.289311], + zoom: 10 + }; + + return ( + + + + + ); +}; + +export default Map; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts new file mode 100644 index 0000000000..62a0a263b3 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -0,0 +1,32 @@ +import type { YMapLocationRequest } from '@yandex/ymaps3-types'; + +type Args = { + +} + +export async function initMap(args: Args) { + // The `ymaps3.ready` promise will be resolved when all the API components are loaded + await isYmapsReady(); + + const {YMap, YMapDefaultSchemeLayer} = ymaps3; + + // Map creation + const map = new YMap( + // Pass the link to the HTMLElement of the container + document.getElementById('map'), + + // Pass the initialization parameters + { + location: { + // The map center coordinates + center: [25.229762, 55.289311], + + // Zoom level + zoom: 10 + } + } + ); + + // Add a layer to display the schematic map + map.addChild(new YMapDefaultSchemeLayer()); +} \ No newline at end of file diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts new file mode 100644 index 0000000000..f92f4ae468 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts @@ -0,0 +1,15 @@ +export function isYmapsReady(args: {apiKey: string; lang?: string}) { + const {apiKey, lang = 'ru_RU'} = args; + return new Promise((resolve) => { + if (typeof ymaps3 !== 'undefined') { + ymaps3.ready.then(() => resolve(ymaps3)); + } + + const script = document.createElement('script'); + script.src = `https://api-maps.yandex.ru/v3/?apikey=${apiKey}&lang=${lang}`; + script.onload = () => { + ymaps3.ready.then(() => resolve(ymaps3)); + }; + document.head.appendChild(script); + }); +} \ No newline at end of file diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts new file mode 100644 index 0000000000..3455feeb0a --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -0,0 +1,34 @@ +import type {ChartKitOnLoadData, ChartKitProps, ChartKitType} from '@gravity-ui/chartkit'; +import type {Language} from 'shared'; + +import type {OnChangeData} from '../../../types'; +import {StringParams} from '@gravity-ui/chartkit/highcharts'; + +export type YandexMapWidgetDataItem = Record; + +export type YandexMapWidgetData = { + data?: YandexMapWidgetDataItem; + config?: Record; + libraryConfig?: { + apiKey?: string; + state?: Record; + options?: Record; + }; + unresolvedParams?: StringParams; +}; + +export type YandexMapWidgetProps = { + id: string; + data: YandexMapWidgetData; + lang: Language; + splitTooltip?: boolean; + onChange?: ( + data: OnChangeData, + state: {forceUpdate: boolean}, + callExternalOnChange?: boolean, + callChangeByClick?: boolean, + ) => void; + onLoad?: ( + data?: ChartKitOnLoadData<'yandexmap'> & {yandexMapAPIWaiting?: number | null}, + ) => void; +} & Pick, 'onRender' | 'onChartLoad'>; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts index d9148d3722..bac6870dde 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts @@ -8,7 +8,8 @@ import {HighchartsMapPlugin} from './HighchartsMap'; import {MarkupPlugin} from './Markup'; import {MetricPlugin} from './Metric'; import {TablePlugin} from './Table'; -import {YandexMapPlugin} from './YandexMap'; +// import {YandexMapPlugin} from './YandexMap'; +import {YandexMapV3Plugin as YandexMapPlugin} from './YandexMapV3'; export {MetricPlugin} from './Metric'; export {HighchartsMapPlugin} from './HighchartsMap'; From 8c02707ab15ee6e97e3b064c43a2c7c98c7f63a8 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Wed, 12 Feb 2025 15:14:22 +0300 Subject: [PATCH 02/19] ymap 3 --- .../YandexMapV3/renderer/YandexMapWidget.tsx | 116 +++++++++++++++++- .../renderer/components/Map/Map.tsx | 40 ++++-- .../plugins/YandexMapV3/renderer/yamap.ts | 5 +- .../ChartKit/plugins/YandexMapV3/types.ts | 32 ++++- 4 files changed, 177 insertions(+), 16 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx index 2eb9497966..938e809f13 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -1,13 +1,15 @@ import React from 'react'; -import {CHARTKIT_ERROR_CODE, ChartKitError} from '@gravity-ui/chartkit'; +import {CHARTKIT_ERROR_CODE, ChartKitError, ChartKitWidgetRef} from '@gravity-ui/chartkit'; import block from 'bem-cn-lite'; +import type {PolygonGeometry, LngLat} from '@yandex/ymaps3-types'; import Performance from '../../../../modules/perfomance'; import {getRandomCKId} from '../../../helpers/getRandomCKId'; import type {YandexMapWidgetProps} from '../types'; import {isYmapsReady} from './yamap'; import {Loader} from '@gravity-ui/uikit'; +import * as turf from "@turf/circle"; import './YandexMapWidget.scss'; @@ -15,7 +17,25 @@ const Map = React.lazy(() => import('./components/Map/Map')); const b = block('chartkit-ymap-widget'); -export const YandexMapWidget = (props: YandexMapWidgetProps) => { +function reverseCoordinates(data: unknown): unknown { + if (Array.isArray(data)) { + if (Array.isArray(data[0])) { + return data.map(reverseCoordinates); + } + + return [...data].reverse(); + } + + return data as LngLat; +} + +function getCircleGeoJSON(center: LngLat, radiusMeters: number): PolygonGeometry { + const {geometry} = turf.circle(center as number[], radiusMeters, {units: 'meters'}); + return geometry as PolygonGeometry; +}; + +export const YandexMapWidget = React.forwardRef( + (props, forwardedRef) => { const { id, // onLoad, @@ -23,16 +43,28 @@ export const YandexMapWidget = (props: YandexMapWidgetProps) => { // _splitTooltip, } = props; + console.log('YandexMapWidget:', {originalData, config, libraryConfig}); + const [isLoading, setLoading] = React.useState(true); const generatedId = React.useMemo(() => `${id}_${getRandomCKId()}`, [originalData, config, id]); Performance.mark(generatedId); + React.useImperativeHandle( + forwardedRef, + () => ({ + reflow() { + // debuncedHandleResize(); + }, + }), + [], + ); + React.useEffect(() => { isYmapsReady({ apiKey: libraryConfig?.apiKey ?? '' }).then(() => { - // setLoading(false); + setLoading(false); }) }, []); @@ -42,11 +74,85 @@ export const YandexMapWidget = (props: YandexMapWidgetProps) => { }); } + const center = reverseCoordinates(libraryConfig?.state?.center); + const features = React.useMemo(() => { + return originalData.reduce((acc, item) => { + switch (item.feature.geometry.type) { + case 'Circle': { + const center = reverseCoordinates(item.feature.geometry.coordinates); + acc.push({ + geometry: getCircleGeoJSON(center as LngLat, item.feature.geometry.radius), + style: { + simplificationRate: 0, + fill: item.options?.fillColor, + fillOpacity: item.options?.opacity, + }, + }); + break; + } + case 'Rectangle': { + const [[left, bottom], [right, top]] = item.feature.geometry.coordinates; + acc.push({ + geometry: { + type: 'Polygon', + coordinates: [[[bottom, left], [top, left], [top, right], [bottom, right]]], + }, + style: { + simplificationRate: 0, + fill: item.options?.fillColor, + fillOpacity: item.options?.opacity, + }, + }); + break; + } + case 'LineString': + case 'Polygon': { + acc.push({ + geometry: { + ...item.feature.geometry, + coordinates: reverseCoordinates(item.feature.geometry.coordinates), + }, + style: { + stroke: [{ + width: item.options?.strokeWidth, + }], + }, + }); + break; + } + default: { + break; + } + } + return acc; + }, [] as any[]); + }, [originalData]); + + const points = React.useMemo(() => { + return originalData.reduce((acc, item) => { + if (item.feature.geometry.type === 'Point') { + acc.push({ + coordinates: reverseCoordinates(item.feature.geometry.coordinates), + }); + } + return acc; + }, [] as any[]); + }, [originalData]); + + const mapProps: any = { + location: { + center, + zoom: libraryConfig?.state?.zoom ?? 10, + }, + features, + points, + }; + return (
- {isLoading ? : } + {isLoading ? : }
); -}; +}); export default YandexMapWidget; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index cc9f15b13f..01d37e9291 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -1,23 +1,47 @@ +import {YMapLocationRequest, GenericGeometry, LngLat, DrawingStyle} from '@yandex/ymaps3-types'; import React from 'react'; import ReactDom from 'react-dom'; const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]); export const reactify = ymaps3React.reactify.bindTo(React, ReactDom); -export const {YMap, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer} = reactify.module(ymaps3); +export const {YMap, YMapFeature, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer} = reactify.module(ymaps3); +// const {YMapHint, YMapHintContext} = reactify.module(await ymaps3.import('@yandex/ymaps3-hint@0.0.1')); +// const {YMapDefaultMarker} = reactify.module(await ymaps3.import('@yandex/ymaps3-markers@0.0.1')); +const {YMapDefaultMarker} = reactify.module(await import('@yandex/ymaps3-default-ui-theme')); -type Props = {}; +import '@yandex/ymaps3-default-ui-theme/dist/esm/index.css'; -export const Map = (_props: Props) => { - const location = { - center: [25.229762, 55.289311], - zoom: 10 - }; +export type Props = { + location: YMapLocationRequest; + features: { + geometry: GenericGeometry; + style?: DrawingStyle; + }[]; + points: any[]; +}; + +export const Map = (props: Props) => { + const {location, features, points = [{}]} = props; + + console.log('Map:', props); return ( - + + { + features.map((feature, index) => { + return (); + }) + } + { + points.map((point, index) => { + return ( + + ); + }) + } ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts index f92f4ae468..6f977983a1 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts @@ -1,14 +1,15 @@ export function isYmapsReady(args: {apiKey: string; lang?: string}) { - const {apiKey, lang = 'ru_RU'} = args; + const {apiKey, lang = 'en_EN'} = args; return new Promise((resolve) => { if (typeof ymaps3 !== 'undefined') { ymaps3.ready.then(() => resolve(ymaps3)); + return; } const script = document.createElement('script'); script.src = `https://api-maps.yandex.ru/v3/?apikey=${apiKey}&lang=${lang}`; script.onload = () => { - ymaps3.ready.then(() => resolve(ymaps3)); + (ymaps3 as any).ready.then(() => resolve(ymaps3)); }; document.head.appendChild(script); }); diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index 3455feeb0a..d11497345a 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -4,7 +4,37 @@ import type {Language} from 'shared'; import type {OnChangeData} from '../../../types'; import {StringParams} from '@gravity-ui/chartkit/highcharts'; -export type YandexMapWidgetDataItem = Record; +type GeometryCircle = { + type: 'Circle'; + coordinates: [number, number]; + radius: number; +}; + +type GeometryPolyline = { + type: 'LineString'; + coordinates: [number, number][]; +}; + +type GeometryPolygon = { + type: 'Polygon'; + coordinates: [number, number][][]; +}; + +type Point = { + type: 'Point'; + coordinates: [number, number]; +}; + +export type YandexMapWidgetDataItem = { + feature: { + geometry: GeometryCircle | GeometryPolygon | GeometryPolyline | Point; + }; + options: { + fillColor?: string; + opacity?: number; + strokeWidth?: number; + }; +}[]; export type YandexMapWidgetData = { data?: YandexMapWidgetDataItem; From 79980bf69bb767a95445d7b8b95cf7db07854656 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 13 Feb 2025 18:03:55 +0300 Subject: [PATCH 03/19] hint --- .../YandexMapV3/renderer/YandexMapWidget.tsx | 146 ++++++++++++------ .../ClusterMarker/ClusterMarker.scss | 11 ++ .../ClusterMarker/ClusterMarker.tsx | 23 +++ .../renderer/components/Map/Map.tsx | 67 +++++++- .../renderer/components/Tooltip/Tooltip.scss | 3 + .../renderer/components/Tooltip/Tooltip.tsx | 23 +++ .../ChartKit/plugins/YandexMapV3/types.ts | 40 ++++- 7 files changed, 252 insertions(+), 61 deletions(-) create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx index 938e809f13..d5b1d4a2eb 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -6,7 +6,7 @@ import type {PolygonGeometry, LngLat} from '@yandex/ymaps3-types'; import Performance from '../../../../modules/perfomance'; import {getRandomCKId} from '../../../helpers/getRandomCKId'; -import type {YandexMapWidgetProps} from '../types'; +import type {SingleItem, YandexMapWidgetProps} from '../types'; import {isYmapsReady} from './yamap'; import {Loader} from '@gravity-ui/uikit'; import * as turf from "@turf/circle"; @@ -34,6 +34,53 @@ function getCircleGeoJSON(center: LngLat, radiusMeters: number): PolygonGeometry return geometry as PolygonGeometry; }; +function getMapObject(item: SingleItem) { + switch (item.feature.geometry.type) { + case 'Circle': { + const center = reverseCoordinates(item.feature.geometry.coordinates); + return { + geometry: getCircleGeoJSON(center as LngLat, item.feature.geometry.radius), + style: { + simplificationRate: 0, + fill: item.options?.fillColor, + fillOpacity: item.options?.opacity, + }, + }; + } + case 'Rectangle': { + const [[left, bottom], [right, top]] = item.feature.geometry.coordinates; + return { + geometry: { + type: 'Polygon', + coordinates: [[[bottom, left], [top, left], [top, right], [bottom, right]]], + }, + style: { + simplificationRate: 0, + fill: item.options?.fillColor, + fillOpacity: item.options?.opacity, + }, + }; + } + case 'LineString': + case 'Polygon': { + return { + geometry: { + ...item.feature.geometry, + coordinates: reverseCoordinates(item.feature.geometry.coordinates), + }, + style: { + stroke: [{ + width: item.options?.strokeWidth, + }], + }, + }; + } + default: { + return null; + } + } +} + export const YandexMapWidget = React.forwardRef( (props, forwardedRef) => { const { @@ -77,64 +124,64 @@ export const YandexMapWidget = React.forwardRef { return originalData.reduce((acc, item) => { - switch (item.feature.geometry.type) { - case 'Circle': { - const center = reverseCoordinates(item.feature.geometry.coordinates); - acc.push({ - geometry: getCircleGeoJSON(center as LngLat, item.feature.geometry.radius), - style: { - simplificationRate: 0, - fill: item.options?.fillColor, - fillOpacity: item.options?.opacity, - }, - }); - break; - } - case 'Rectangle': { - const [[left, bottom], [right, top]] = item.feature.geometry.coordinates; - acc.push({ - geometry: { - type: 'Polygon', - coordinates: [[[bottom, left], [top, left], [top, right], [bottom, right]]], - }, - style: { - simplificationRate: 0, - fill: item.options?.fillColor, - fillOpacity: item.options?.opacity, - }, - }); - break; - } - case 'LineString': - case 'Polygon': { - acc.push({ - geometry: { - ...item.feature.geometry, - coordinates: reverseCoordinates(item.feature.geometry.coordinates), - }, - style: { - stroke: [{ - width: item.options?.strokeWidth, - }], - }, - }); - break; - } - default: { - break; + if ('feature' in item) { + const mapObject = getMapObject(item); + if (mapObject) { + acc.push(mapObject); } } + + if ('collection' in item) { + item.collection.children.forEach(d => { + const mapObject = getMapObject(d); + if (mapObject) { + acc.push(mapObject); + } + }); + } + return acc; }, [] as any[]); }, [originalData]); + function getPointObject(item: SingleItem) { + return { + coordinates: reverseCoordinates(item.feature.geometry.coordinates), + properties: item.feature.properties, + popup: { + content: '123' + } + }; + } + const points = React.useMemo(() => { return originalData.reduce((acc, item) => { - if (item.feature.geometry.type === 'Point') { - acc.push({ - coordinates: reverseCoordinates(item.feature.geometry.coordinates), + if ('feature' in item && item.feature.geometry.type === 'Point') { + acc.push(getPointObject(item)); + } + + if ('collection' in item) { + item.collection.children.forEach(d => { + if (d.feature.geometry.type === 'Point') { + acc.push(getPointObject(d)); + } + }); + } + + return acc; + }, [] as any[]); + }, [originalData]); + + const clusteredPoints = React.useMemo(() => { + return originalData.reduce((acc, item) => { + if ('clusterer' in item) { + item.clusterer.forEach(d => { + if (d.feature.geometry.type === 'Point') { + acc.push(getPointObject(d)); + } }); } + return acc; }, [] as any[]); }, [originalData]); @@ -146,6 +193,7 @@ export const YandexMapWidget = React.forwardRef { + const {count} = props; + + return ( +
+
+ {count} +
+
+ ); +} \ No newline at end of file diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 01d37e9291..641c4f6921 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -2,15 +2,23 @@ import {YMapLocationRequest, GenericGeometry, LngLat, DrawingStyle} from '@yande import React from 'react'; import ReactDom from 'react-dom'; +import {ClusterMarker} from '../ClusterMarker/ClusterMarker'; + const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]); export const reactify = ymaps3React.reactify.bindTo(React, ReactDom); -export const {YMap, YMapFeature, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer} = reactify.module(ymaps3); -// const {YMapHint, YMapHintContext} = reactify.module(await ymaps3.import('@yandex/ymaps3-hint@0.0.1')); +export const {YMap, YMapFeatureDataSource, YMapLayer, YMapFeature, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer, YMapMarker} = reactify.module(ymaps3); +const {YMapHint, YMapHintContext} = reactify.module(await ymaps3.import('@yandex/ymaps3-hint@0.0.1')); // const {YMapDefaultMarker} = reactify.module(await ymaps3.import('@yandex/ymaps3-markers@0.0.1')); const {YMapDefaultMarker} = reactify.module(await import('@yandex/ymaps3-default-ui-theme')); +const {clusterByGrid} = await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1'); +const {YMapClusterer} = reactify.module( + await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1') +); + import '@yandex/ymaps3-default-ui-theme/dist/esm/index.css'; +import {Tooltip} from '../Tooltip/Tooltip'; export type Props = { location: YMapLocationRequest; @@ -19,13 +27,61 @@ export type Props = { style?: DrawingStyle; }[]; points: any[]; + clusteredPoints: any[]; }; +const clusterSource = 'clusterer-source'; + +// Create a custom control component for a hint window +function HintWindow() { + const hintContext = React.useContext(YMapHintContext) as { + hint: { + title: string; + }; + }; + + console.log(hintContext); + + // Use dangerouslySetInnerHTML because the hint message has and
tags + return ( + hintContext && ( + + ) + ); +} + export const Map = (props: Props) => { - const {location, features, points = [{}]} = props; + const {location, features = [], points = [], clusteredPoints = []} = props; console.log('Map:', props); + const clusterPoints: YMapClusterer[''] = React.useMemo(() => { + return clusteredPoints.map((p, index) => ({ + id: index, + geometry: {coordinates: p.coordinates}, + properties: p.properties + })); + }, [clusteredPoints]); + const gridSizedMethod = clusterByGrid({gridSize: 64}); + + const marker = React.useCallback( + (feature) => ( + + {/* */} + + ), + [] + ); + + const cluster = React.useCallback( + (coordinates, features) => ( + + + + ), + [] + ); + return ( @@ -42,8 +98,11 @@ export const Map = (props: Props) => { ); }) } + + + ); -}; + }; export default Map; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss new file mode 100644 index 0000000000..e4f4e03364 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss @@ -0,0 +1,3 @@ +.ymap-tooltip { + background-color: var(--g-color-base-background); +} \ No newline at end of file diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..8ff5232b99 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import block from 'bem-cn-lite'; + +import './Tooltip.scss'; + +const b = block('ymap-tooltip'); + +type Props = { + title?: string; +}; + +export const Tooltip = (props: Props) => { + const {title} = props; + + return ( +
+
+ {title} +
+
+ ); +} \ No newline at end of file diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index d11497345a..79cdcdf023 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -10,6 +10,11 @@ type GeometryCircle = { radius: number; }; +type GeometryRectangle = { + type: 'Rectangle'; + coordinates: [number, number][]; +}; + type GeometryPolyline = { type: 'LineString'; coordinates: [number, number][]; @@ -25,19 +30,38 @@ type Point = { coordinates: [number, number]; }; -export type YandexMapWidgetDataItem = { +type YmapItemOptions = { + fillColor?: string; + opacity?: number; + strokeWidth?: number; +}; + +type GeometryType = GeometryCircle | GeometryRectangle | GeometryPolygon | GeometryPolyline | Point; + +export type SingleItem = { feature: { - geometry: GeometryCircle | GeometryPolygon | GeometryPolyline | Point; + geometry: GeometryType; + properties?: Record; }; - options: { - fillColor?: string; - opacity?: number; - strokeWidth?: number; + options: YmapItemOptions; +}; + +type ItemCollection = { + collection: { + children: SingleItem[]; }; -}[]; + options: YmapItemOptions; +}; + +type ItemClusterer = { + clusterer: SingleItem[]; + options: YmapItemOptions; +} + +export type YandexMapWidgetDataItem = SingleItem | ItemCollection | ItemClusterer; export type YandexMapWidgetData = { - data?: YandexMapWidgetDataItem; + data?: YandexMapWidgetDataItem[]; config?: Record; libraryConfig?: { apiKey?: string; From 51dfc76bda75da6a211c8be6deed104ffa265ab2 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Wed, 19 Feb 2025 11:59:12 +0300 Subject: [PATCH 04/19] fix map tooltip --- .../YandexMapV3/renderer/YandexMapWidget.tsx | 16 +++- .../renderer/components/Map/Map.tsx | 82 ++++++++----------- .../renderer/components/Tooltip/Tooltip.scss | 30 ++++++- .../renderer/components/Tooltip/Tooltip.tsx | 32 ++++++-- .../plugins/YandexMapV3/renderer/constants.ts | 0 5 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx index d5b1d4a2eb..81318a55fa 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -45,6 +45,9 @@ function getMapObject(item: SingleItem) { fill: item.options?.fillColor, fillOpacity: item.options?.opacity, }, + properties: { + hint: item.feature.properties, + }, }; } case 'Rectangle': { @@ -59,6 +62,9 @@ function getMapObject(item: SingleItem) { fill: item.options?.fillColor, fillOpacity: item.options?.opacity, }, + properties: { + hint: item.feature.properties, + }, }; } case 'LineString': @@ -73,6 +79,9 @@ function getMapObject(item: SingleItem) { width: item.options?.strokeWidth, }], }, + properties: { + hint: item.feature.properties, + }, }; } default: { @@ -147,10 +156,9 @@ export const YandexMapWidget = React.forwardRef; style?: DrawingStyle; + properties?: Record; }[]; points: any[]; clusteredPoints: any[]; @@ -32,30 +32,12 @@ export type Props = { const clusterSource = 'clusterer-source'; -// Create a custom control component for a hint window -function HintWindow() { - const hintContext = React.useContext(YMapHintContext) as { - hint: { - title: string; - }; - }; - - console.log(hintContext); - - // Use dangerouslySetInnerHTML because the hint message has and
tags - return ( - hintContext && ( - - ) - ); -} - export const Map = (props: Props) => { const {location, features = [], points = [], clusteredPoints = []} = props; - console.log('Map:', props); + const getHint = React.useCallback((mapObject: unknown) => get(mapObject, 'properties.hint'), []); - const clusterPoints: YMapClusterer[''] = React.useMemo(() => { + const clusterPoints = React.useMemo(() => { return clusteredPoints.map((p, index) => ({ id: index, geometry: {coordinates: p.coordinates}, @@ -66,9 +48,7 @@ export const Map = (props: Props) => { const marker = React.useCallback( (feature) => ( - - {/* */} - + ), [] ); @@ -83,24 +63,32 @@ export const Map = (props: Props) => { ); return ( - - - - { - features.map((feature, index) => { - return (); - }) - } - { - points.map((point, index) => { - return ( - - ); - }) - } - - - + + + + + + + { + features.map((feature, index) => { + return (); + }) + } + { + points.map((point, index) => { + return ( + + ); + }) + } + + + ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss index e4f4e03364..7b2b1c6cc1 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss @@ -1,3 +1,29 @@ -.ymap-tooltip { - background-color: var(--g-color-base-background); +.yandex-map-tooltip { + border-radius: 3px; + background-color: var(--g-color-infographics-tooltip-bg); + padding: 10px 14px; + font-size: 12px; + box-sizing: border-box; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); + + margin: 12px; + + &__title { + white-space: nowrap; + font-size: 13px; + font-weight: 600; + } + + &__row { + display: table-row; + } + + &__cell { + white-space: nowrap; + display: table-cell; + padding: 2px 7px 2px 0; + max-width: 370px; + text-overflow: ellipsis; + overflow: hidden; + } } \ No newline at end of file diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx index 8ff5232b99..67edd9ea16 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx @@ -1,22 +1,42 @@ import React from 'react'; +import ReactDom from 'react-dom'; import block from 'bem-cn-lite'; +import {formatNumber} from 'shared'; +import isEmpty from 'lodash/isEmpty'; import './Tooltip.scss'; -const b = block('ymap-tooltip'); +const b = block('yandex-map-tooltip'); -type Props = { - title?: string; +type HintProps = { + name?: string; + value?: number; + text?: string; }; -export const Tooltip = (props: Props) => { - const {title} = props; +export const Tooltip = (props: {context: {hint?: HintProps}}) => { + const hintContext = React.useContext(props.context); + + if (isEmpty(hintContext?.hint)) { + return null; + } + + const {name: title, value, text} = hintContext.hint; + const formattedValue = typeof value === 'number' ? formatNumber(value) : null; return (
-
+ {title &&
{title} +
} +
+ {formattedValue &&
+ {formattedValue} +
} + {text &&
+ {text} +
}
); diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts new file mode 100644 index 0000000000..e69de29bb2 From 6d41a852e0ef958a82f9b678a0ae6f7dd810ff46 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Wed, 14 May 2025 13:40:03 +0300 Subject: [PATCH 05/19] fix types --- package-lock.json | 115 +++++++++- package.json | 3 + .../ChartKit/helpers/chartkit-adapter.ts | 13 +- .../ChartKit/plugins/YandexMapV3/index.ts | 2 +- .../YandexMapV3/renderer/YandexMapWidget.tsx | 199 +++--------------- .../renderer/components/Map/Map.tsx | 134 +++++++----- .../renderer/components/Tooltip/Tooltip.tsx | 58 +++-- .../plugins/YandexMapV3/renderer/types.ts | 17 ++ .../plugins/YandexMapV3/renderer/utils.ts | 175 +++++++++++++-- .../plugins/YandexMapV3/renderer/yamap.ts | 29 +-- .../ChartKit/plugins/YandexMapV3/types.ts | 4 +- .../ChartKit/plugins/index.ts | 31 +-- .../libs/DatalensChartkit/ChartKit/types.ts | 4 + 13 files changed, 468 insertions(+), 316 deletions(-) create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts diff --git a/package-lock.json b/package-lock.json index 38d01c7b0d..29b7a78020 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,6 +100,7 @@ "@tanstack/react-virtual": "^3.8.1", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.4", + "@turf/circle": "^7.2.0", "@types/cookie-session": "^2.0.49", "@types/d3": "^7.4.0", "@types/d3-color": "^3.1.3", @@ -138,6 +139,8 @@ "@types/uuid": "^9.0.8", "@types/webpack-env": "^1.16.0", "@types/yup": "^0.29.13", + "@yandex/ymaps3-default-ui-theme": "^0.0.19", + "@yandex/ymaps3-types": "^1.0.15731128", "bem-cn-lite": "^4.0.0", "blueimp-md5": "^2.19.0", "classnames": "^2.5.1", @@ -10891,6 +10894,67 @@ "dev": true, "license": "MIT" }, + "node_modules/@turf/circle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-7.2.0.tgz", + "integrity": "sha512-1AbqBYtXhstrHmnW6jhLwsv7TtmT0mW58Hvl1uZXEDM1NCVXIR50yDipIeQPjrCuJ/Zdg/91gU8+4GuDCAxBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@turf/destination": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-7.2.0.tgz", + "integrity": "sha512-8DUxtOO0Fvrh1xclIUj3d9C5WS20D21F5E+j+X9Q+ju6fcM4huOqTg5ckV1DN2Pg8caABEc5HEZJnGch/5YnYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.2.0.tgz", + "integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.2.0.tgz", + "integrity": "sha512-kV4u8e7Gkpq+kPbAKNC21CmyrXzlbBgFjO1PhrHPgEdNqXqDawoZ3i6ivE3ULJj2rSesCjduUaC/wyvH/sNr2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/archy": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@types/archy/-/archy-0.0.32.tgz", @@ -11848,20 +11912,24 @@ } }, "node_modules/@types/react": { - "version": "17.0.40", + "version": "17.0.85", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.85.tgz", + "integrity": "sha512-5oBDUsRDsrYq4DdyHaL99gE1AJCfuDhyxqF6/55fvvOIRkp1PpKuwJ+aMiGJR+GJt7YqMNclPROTHF20vY2cXA==", "license": "MIT", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", + "@types/scheduler": "^0.16", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "17.0.13", + "version": "17.0.26", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz", + "integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==", "dev": true, "license": "MIT", - "dependencies": { - "@types/react": "*" + "peerDependencies": { + "@types/react": "^17.0.0" } }, "node_modules/@types/react-inspector": { @@ -12612,6 +12680,36 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@yandex/ymaps3-default-ui-theme": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/@yandex/ymaps3-default-ui-theme/-/ymaps3-default-ui-theme-0.0.19.tgz", + "integrity": "sha512-kjrlmrUQ9OnULbSnZ+AkwHeN/QG3B6rEUcA8MXs3ai33inyWU3n+ivsW2Ezbcw5e6+QiG4h5wg1c32qZJWI+ig==", + "dev": true, + "license": "Apache-2" + }, + "node_modules/@yandex/ymaps3-types": { + "version": "1.0.16477558", + "resolved": "https://registry.npmjs.org/@yandex/ymaps3-types/-/ymaps3-types-1.0.16477558.tgz", + "integrity": "sha512-02FP+IQ2KRiYPD3U3HDiOQxVaHBMR2hZVc1N51lgHx7MElawpuV/P0BU8ieFIQnW6jRk9t+AHib/y0zkJHPlmg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@types/react": "16-18", + "@types/react-dom": "16-18", + "@vue/runtime-core": "3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "@vue/runtime-core": { + "optional": true + } + } + }, "node_modules/abab": { "version": "2.0.6", "dev": true, @@ -30820,9 +30918,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/tsscmp": { "version": "1.0.6", diff --git a/package.json b/package.json index 81ffbb415e..88548d37a2 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "@tanstack/react-virtual": "^3.8.1", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.4", + "@turf/circle": "^7.2.0", "@types/cookie-session": "^2.0.49", "@types/d3": "^7.4.0", "@types/d3-color": "^3.1.3", @@ -183,6 +184,8 @@ "@types/uuid": "^9.0.8", "@types/webpack-env": "^1.16.0", "@types/yup": "^0.29.13", + "@yandex/ymaps3-default-ui-theme": "^0.0.19", + "@yandex/ymaps3-types": "^1.0.15731128", "bem-cn-lite": "^4.0.0", "blueimp-md5": "^2.19.0", "classnames": "^2.5.1", diff --git a/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts b/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts index ce5a035050..40d9b6a32f 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts @@ -66,7 +66,15 @@ export const getChartkitType = (data?: LoadedWidgetData): ChartKitType | undefin } case 'ymap': { - chartkitType = 'yandexmap'; + chartkitType = 'yandexmap_v3'; + const isWizardOrQl = get(data, 'isNewWizard') || get(data, 'isQL'); + if (isWizardOrQl) { + chartkitType = 'yandexmap'; + } else { + chartkitType = isEnabledFeature('EnableYandexMapV3CEWidget') + ? 'yandexmap_v3' + : 'yandexmap'; + } break; } @@ -138,7 +146,8 @@ export const getOpensourceChartKitData = ({ return data; } - case 'yandexmap': { + case 'yandexmap': + case 'yandexmap_v3': { const data = {...(loadedData as ChartKitProps<'yandexmap'>['data'])}; return data; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts index fb01647950..9b7dd611cd 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/index.ts @@ -3,6 +3,6 @@ import React from 'react'; import type {ChartKitPlugin} from '@gravity-ui/chartkit'; export const YandexMapV3Plugin: ChartKitPlugin = { - type: 'yandexmap', + type: 'yandexmap_v3', renderer: React.lazy(() => import('./renderer/YandexMapWidget')), }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx index 81318a55fa..8189ed0b8f 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import {CHARTKIT_ERROR_CODE, ChartKitError, ChartKitWidgetRef} from '@gravity-ui/chartkit'; +import type {ChartKitWidgetRef} from '@gravity-ui/chartkit'; +import {CHARTKIT_ERROR_CODE, ChartKitError} from '@gravity-ui/chartkit'; +import {Loader} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import type {PolygonGeometry, LngLat} from '@yandex/ymaps3-types'; import Performance from '../../../../modules/perfomance'; import {getRandomCKId} from '../../../helpers/getRandomCKId'; -import type {SingleItem, YandexMapWidgetProps} from '../types'; +import type {YandexMapWidgetProps} from '../types'; + import {isYmapsReady} from './yamap'; -import {Loader} from '@gravity-ui/uikit'; -import * as turf from "@turf/circle"; import './YandexMapWidget.scss'; @@ -17,90 +17,16 @@ const Map = React.lazy(() => import('./components/Map/Map')); const b = block('chartkit-ymap-widget'); -function reverseCoordinates(data: unknown): unknown { - if (Array.isArray(data)) { - if (Array.isArray(data[0])) { - return data.map(reverseCoordinates); - } - - return [...data].reverse(); - } - - return data as LngLat; -} - -function getCircleGeoJSON(center: LngLat, radiusMeters: number): PolygonGeometry { - const {geometry} = turf.circle(center as number[], radiusMeters, {units: 'meters'}); - return geometry as PolygonGeometry; -}; - -function getMapObject(item: SingleItem) { - switch (item.feature.geometry.type) { - case 'Circle': { - const center = reverseCoordinates(item.feature.geometry.coordinates); - return { - geometry: getCircleGeoJSON(center as LngLat, item.feature.geometry.radius), - style: { - simplificationRate: 0, - fill: item.options?.fillColor, - fillOpacity: item.options?.opacity, - }, - properties: { - hint: item.feature.properties, - }, - }; - } - case 'Rectangle': { - const [[left, bottom], [right, top]] = item.feature.geometry.coordinates; - return { - geometry: { - type: 'Polygon', - coordinates: [[[bottom, left], [top, left], [top, right], [bottom, right]]], - }, - style: { - simplificationRate: 0, - fill: item.options?.fillColor, - fillOpacity: item.options?.opacity, - }, - properties: { - hint: item.feature.properties, - }, - }; - } - case 'LineString': - case 'Polygon': { - return { - geometry: { - ...item.feature.geometry, - coordinates: reverseCoordinates(item.feature.geometry.coordinates), - }, - style: { - stroke: [{ - width: item.options?.strokeWidth, - }], - }, - properties: { - hint: item.feature.properties, - }, - }; - } - default: { - return null; - } - } -} - -export const YandexMapWidget = React.forwardRef( - (props, forwardedRef) => { +export const YandexMapWidget = React.forwardRef< + ChartKitWidgetRef | undefined, + YandexMapWidgetProps +>((props, forwardedRef) => { const { id, // onLoad, data: {data: originalData, config, libraryConfig}, - // _splitTooltip, } = props; - console.log('YandexMapWidget:', {originalData, config, libraryConfig}); - const [isLoading, setLoading] = React.useState(true); const generatedId = React.useMemo(() => `${id}_${getRandomCKId()}`, [originalData, config, id]); @@ -109,20 +35,25 @@ export const YandexMapWidget = React.forwardRef ({ - reflow() { - // debuncedHandleResize(); - }, + reflow() {}, }), [], ); - React.useEffect(() => { - isYmapsReady({ - apiKey: libraryConfig?.apiKey ?? '' - }).then(() => { + const loadYmap = React.useCallback(async () => { + try { + await isYmapsReady({ + apiKey: libraryConfig?.apiKey ?? '', + }); + setLoading(false); + } catch (e) { setLoading(false); - }) - }, []); + } + }, [libraryConfig?.apiKey]); + + React.useEffect(() => { + loadYmap(); + }, [loadYmap]); if (!originalData || (typeof originalData === 'object' && !Object.keys(originalData).length)) { throw new ChartKitError({ @@ -130,85 +61,25 @@ export const YandexMapWidget = React.forwardRef { - return originalData.reduce((acc, item) => { - if ('feature' in item) { - const mapObject = getMapObject(item); - if (mapObject) { - acc.push(mapObject); - } - } - - if ('collection' in item) { - item.collection.children.forEach(d => { - const mapObject = getMapObject(d); - if (mapObject) { - acc.push(mapObject); - } - }); - } - - return acc; - }, [] as any[]); - }, [originalData]); - - function getPointObject(item: SingleItem) { - return { - coordinates: reverseCoordinates(item.feature.geometry.coordinates), - properties: { - hint: item.feature.properties, - }, - }; + if (isLoading) { + return ( +
+ +
+ ); } - const points = React.useMemo(() => { - return originalData.reduce((acc, item) => { - if ('feature' in item && item.feature.geometry.type === 'Point') { - acc.push(getPointObject(item)); - } - - if ('collection' in item) { - item.collection.children.forEach(d => { - if (d.feature.geometry.type === 'Point') { - acc.push(getPointObject(d)); - } - }); - } - - return acc; - }, [] as any[]); - }, [originalData]); - - const clusteredPoints = React.useMemo(() => { - return originalData.reduce((acc, item) => { - if ('clusterer' in item) { - item.clusterer.forEach(d => { - if (d.feature.geometry.type === 'Point') { - acc.push(getPointObject(d)); - } - }); - } - - return acc; - }, [] as any[]); - }, [originalData]); - - const mapProps: any = { - location: { - center, - zoom: libraryConfig?.state?.zoom ?? 10, - }, - features, - points, - clusteredPoints, - }; + if (typeof ymaps3 === 'undefined') { + throw Error('Could not load yandex-map API'); + } return (
- {isLoading ? : } +
); }); +YandexMapWidget.displayName = 'YandexMapWidget'; + export default YandexMapWidget; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 8eb04ad9c2..d326550cf8 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -1,16 +1,29 @@ -import {YMapLocationRequest, GenericGeometry, LngLat, DrawingStyle} from '@yandex/ymaps3-types'; import React from 'react'; -import ReactDom from 'react-dom'; + +import type {Feature as ClusterFeature} from '@yandex/ymaps3-types/packages/clusterer'; import get from 'lodash/get'; +import ReactDom from 'react-dom'; +import type {YandexMapWidgetData} from '../../../types'; +import {getMapConfig} from '../../utils'; import {ClusterMarker} from '../ClusterMarker/ClusterMarker'; import {Tooltip} from '../Tooltip/Tooltip'; const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]); export const reactify = ymaps3React.reactify.bindTo(React, ReactDom); -export const {YMap, YMapFeatureDataSource, YMapLayer, YMapFeature, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer, YMapMarker} = reactify.module(ymaps3); -const {YMapHint, YMapHintContext} = reactify.module(await ymaps3.import('@yandex/ymaps3-hint@0.0.1')); +export const { + YMap, + YMapFeatureDataSource, + YMapLayer, + YMapFeature, + YMapDefaultSchemeLayer, + YMapDefaultFeaturesLayer, + YMapMarker, +} = reactify.module(ymaps3); +const {YMapHint, YMapHintContext} = reactify.module( + await ymaps3.import('@yandex/ymaps3-hint@0.0.1'), +); const {YMapDefaultMarker} = reactify.module(await import('@yandex/ymaps3-default-ui-theme')); const clustererModule = await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1'); @@ -19,78 +32,85 @@ const {YMapClusterer} = reactify.module(clustererModule); import '@yandex/ymaps3-default-ui-theme/dist/esm/index.css'; -export type Props = { - location: YMapLocationRequest; - features: { - geometry: GenericGeometry; - style?: DrawingStyle; - properties?: Record; - }[]; - points: any[]; - clusteredPoints: any[]; -}; +export type Props = YandexMapWidgetData; const clusterSource = 'clusterer-source'; export const Map = (props: Props) => { - const {location, features = [], points = [], clusteredPoints = []} = props; + const mapConfig = getMapConfig(props); + const {location, features = [], points = [], clusteredPoints = []} = mapConfig; - const getHint = React.useCallback((mapObject: unknown) => get(mapObject, 'properties.hint'), []); + const getHint = React.useCallback( + (mapObject: unknown) => get(mapObject, 'properties.hint'), + [], + ); const clusterPoints = React.useMemo(() => { - return clusteredPoints.map((p, index) => ({ - id: index, - geometry: {coordinates: p.coordinates}, - properties: p.properties - })); + return clusteredPoints.map( + (p, index) => + ({ + id: String(index), + geometry: {coordinates: p.coordinates}, + properties: p.properties, + }) as ClusterFeature, + ); }, [clusteredPoints]); const gridSizedMethod = clusterByGrid({gridSize: 64}); const marker = React.useCallback( (feature) => ( - + ), - [] - ); + [], + ); - const cluster = React.useCallback( + const cluster = React.useCallback( (coordinates, features) => ( - - - + + + ), - [] - ); + [], + ); return ( - - - - - - { - features.map((feature, index) => { - return (); - }) - } - { - points.map((point, index) => { - return ( - - ); - }) - } - - - - + + + + + + {features.map((feature, index) => { + return ( + + ); + })} + {points.map((point, index) => { + return ; + })} + + + + ); - }; +}; export default Map; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx index 67edd9ea16..10117cf785 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx @@ -1,43 +1,39 @@ import React from 'react'; -import ReactDom from 'react-dom'; import block from 'bem-cn-lite'; -import {formatNumber} from 'shared'; import isEmpty from 'lodash/isEmpty'; +import {formatNumber} from 'shared'; import './Tooltip.scss'; const b = block('yandex-map-tooltip'); type HintProps = { - name?: string; - value?: number; - text?: string; + name?: string; + value?: number; + text?: string; }; -export const Tooltip = (props: {context: {hint?: HintProps}}) => { - const hintContext = React.useContext(props.context); - - if (isEmpty(hintContext?.hint)) { - return null; - } - - const {name: title, value, text} = hintContext.hint; - const formattedValue = typeof value === 'number' ? formatNumber(value) : null; - - return ( -
- {title &&
- {title} -
} -
- {formattedValue &&
- {formattedValue} -
} - {text &&
- {text} -
} -
-
- ); -} \ No newline at end of file +type Props = {context: React.Context}; +type HintContext = React.Context<{hint?: HintProps}>; + +export const Tooltip = (props: Props) => { + const hintContext = React.useContext(props.context as HintContext); + + if (!hintContext?.hint || isEmpty(hintContext.hint)) { + return null; + } + + const {name: title, value, text} = hintContext.hint; + const formattedValue = typeof value === 'number' ? formatNumber(value) : null; + + return ( +
+ {title &&
{title}
} +
+ {formattedValue &&
{formattedValue}
} + {text &&
{text}
} +
+
+ ); +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts new file mode 100644 index 0000000000..8a8b14e8f3 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts @@ -0,0 +1,17 @@ +import type { + DrawingStyle, + GenericGeometry, + LngLat, + YMapLocationRequest, +} from '@yandex/ymaps3-types'; + +export type YMapConfig = { + location: YMapLocationRequest; + features: { + geometry: GenericGeometry; + style?: DrawingStyle; + properties?: Record; + }[]; + points: any[]; + clusteredPoints: any[]; +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 62a0a263b3..1bd1be1fb5 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -1,32 +1,161 @@ -import type { YMapLocationRequest } from '@yandex/ymaps3-types'; +import * as turf from '@turf/circle'; +import type {LngLat, PolygonGeometry} from '@yandex/ymaps3-types'; -type Args = { - -} +import type {SingleItem, YandexMapWidgetData} from '../types'; + +import type {YMapConfig} from './types'; -export async function initMap(args: Args) { - // The `ymaps3.ready` promise will be resolved when all the API components are loaded - await isYmapsReady(); +function reverseCoordinates(data: unknown): unknown { + if (Array.isArray(data)) { + if (Array.isArray(data[0])) { + return data.map(reverseCoordinates); + } - const {YMap, YMapDefaultSchemeLayer} = ymaps3; + return [...data].reverse(); + } - // Map creation - const map = new YMap( - // Pass the link to the HTMLElement of the container - document.getElementById('map'), + return data as LngLat; +} - // Pass the initialization parameters - { - location: { - // The map center coordinates - center: [25.229762, 55.289311], +function getCircleGeoJSON(center: LngLat, radiusMeters: number): PolygonGeometry { + const {geometry} = turf.circle(center as number[], radiusMeters, {units: 'meters'}); + return geometry as PolygonGeometry; +} + +function getMapObject(item: SingleItem) { + switch (item.feature.geometry.type) { + case 'Circle': { + const center = reverseCoordinates(item.feature.geometry.coordinates); + return { + geometry: getCircleGeoJSON(center as LngLat, item.feature.geometry.radius), + style: { + simplificationRate: 0, + fill: item.options?.fillColor, + fillOpacity: item.options?.opacity, + }, + properties: { + hint: item.feature.properties, + }, + }; + } + case 'Rectangle': { + const [[left, bottom], [right, top]] = item.feature.geometry.coordinates; + return { + geometry: { + type: 'Polygon', + coordinates: [ + [ + [bottom, left], + [top, left], + [top, right], + [bottom, right], + ], + ], + }, + style: { + simplificationRate: 0, + fill: item.options?.fillColor, + fillOpacity: item.options?.opacity, + }, + properties: { + hint: item.feature.properties, + }, + }; + } + case 'LineString': + case 'Polygon': { + return { + geometry: { + ...item.feature.geometry, + coordinates: reverseCoordinates(item.feature.geometry.coordinates), + }, + style: { + stroke: [ + { + width: item.options?.strokeWidth, + }, + ], + }, + properties: { + hint: item.feature.properties, + }, + }; + } + default: { + return null; + } + } +} + +function getPointObject(item: SingleItem) { + return { + coordinates: reverseCoordinates(item.feature.geometry.coordinates), + properties: { + hint: item.feature.properties, + }, + }; +} - // Zoom level - zoom: 10 +export function getMapConfig(args: YandexMapWidgetData): YMapConfig { + const {data: originalData = [], libraryConfig} = args; + const center = reverseCoordinates(libraryConfig?.state?.center); + const features = originalData.reduce((acc, item) => { + if ('feature' in item) { + const mapObject = getMapObject(item); + if (mapObject) { + acc.push(mapObject); } } - ); - // Add a layer to display the schematic map - map.addChild(new YMapDefaultSchemeLayer()); -} \ No newline at end of file + if ('collection' in item) { + item.collection.children.forEach((d) => { + const mapObject = getMapObject(d); + if (mapObject) { + acc.push(mapObject); + } + }); + } + + return acc; + }, [] as any[]); + + const points = originalData.reduce((acc, item) => { + if ('feature' in item && item.feature.geometry.type === 'Point') { + acc.push(getPointObject(item)); + } + + if ('collection' in item) { + item.collection.children.forEach((d) => { + if (d.feature.geometry.type === 'Point') { + acc.push(getPointObject(d)); + } + }); + } + + return acc; + }, [] as any[]); + + const clusteredPoints = originalData.reduce((acc, item) => { + if ('clusterer' in item) { + item.clusterer.forEach((d) => { + if (d.feature.geometry.type === 'Point') { + acc.push(getPointObject(d)); + } + }); + } + + return acc; + }, [] as any[]); + + const mapProps: any = { + location: { + center, + zoom: libraryConfig?.state?.zoom ?? 10, + }, + features, + points, + clusteredPoints, + }; + + return mapProps; +} diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts index 6f977983a1..b205206d8d 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts @@ -1,16 +1,17 @@ export function isYmapsReady(args: {apiKey: string; lang?: string}) { - const {apiKey, lang = 'en_EN'} = args; - return new Promise((resolve) => { - if (typeof ymaps3 !== 'undefined') { - ymaps3.ready.then(() => resolve(ymaps3)); - return; - } + const {apiKey, lang = 'en_EN'} = args; + return new Promise((resolve, reject) => { + if (typeof ymaps3 !== 'undefined') { + ymaps3.ready.then(() => resolve(ymaps3)); + return; + } - const script = document.createElement('script'); - script.src = `https://api-maps.yandex.ru/v3/?apikey=${apiKey}&lang=${lang}`; - script.onload = () => { - (ymaps3 as any).ready.then(() => resolve(ymaps3)); - }; - document.head.appendChild(script); - }); -} \ No newline at end of file + const script = document.createElement('script'); + script.src = `https://api-maps.yandex.ru/v3/?apikey=${apiKey}&lang=${lang}`; + script.onload = () => { + (ymaps3 as any).ready.then(() => resolve(ymaps3)); + }; + script.onerror = (error) => reject(error); + document.head.appendChild(script); + }); +} diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index 79cdcdf023..5ad7289568 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -1,8 +1,8 @@ import type {ChartKitOnLoadData, ChartKitProps, ChartKitType} from '@gravity-ui/chartkit'; +import type {StringParams} from '@gravity-ui/chartkit/highcharts'; import type {Language} from 'shared'; import type {OnChangeData} from '../../../types'; -import {StringParams} from '@gravity-ui/chartkit/highcharts'; type GeometryCircle = { type: 'Circle'; @@ -56,7 +56,7 @@ type ItemCollection = { type ItemClusterer = { clusterer: SingleItem[]; options: YmapItemOptions; -} +}; export type YandexMapWidgetDataItem = SingleItem | ItemCollection | ItemClusterer; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts index bac6870dde..55ee8a4cd2 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts @@ -8,8 +8,8 @@ import {HighchartsMapPlugin} from './HighchartsMap'; import {MarkupPlugin} from './Markup'; import {MetricPlugin} from './Metric'; import {TablePlugin} from './Table'; -// import {YandexMapPlugin} from './YandexMap'; -import {YandexMapV3Plugin as YandexMapPlugin} from './YandexMapV3'; +import {YandexMapPlugin} from './YandexMap'; +import {YandexMapV3Plugin} from './YandexMapV3'; export {MetricPlugin} from './Metric'; export {HighchartsMapPlugin} from './HighchartsMap'; @@ -19,15 +19,18 @@ export type {HighchartsMapWidgetData, HighchartsMapWidgetProps} from './Highchar export type {YandexMapWidgetData, YandexMapWidgetProps} from './YandexMap/types'; export type {MarkupWidgetData, MarkupWidgetProps} from './Markup/types'; -export const getChartkitPlugins = () => [ - YagrPlugin, - IndicatorPlugin, - MetricPlugin, - HighchartsMapPlugin, - YandexMapPlugin, - HighchartsPlugin, - D3Plugin, - MarkupPlugin, - TablePlugin, - AdvancedChartPlugin, -]; +export const getChartkitPlugins = () => { + return [ + YagrPlugin, + IndicatorPlugin, + MetricPlugin, + HighchartsMapPlugin, + YandexMapPlugin, + YandexMapV3Plugin, + HighchartsPlugin, + D3Plugin, + MarkupPlugin, + TablePlugin, + AdvancedChartPlugin, + ]; +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/types.ts index 945cfc7430..780511d13f 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/types.ts @@ -56,6 +56,10 @@ declare module '@gravity-ui/chartkit' { data: YandexMapWidgetData; widget: YandexMap; }; + yandexmap_v3: { + data: YandexMapWidgetData; + widget: unknown; + }; markup: { data: MarkupWidgetData; widget: never; From 7bcaa01515e60321fa2808ff091adee1296c43b6 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Wed, 14 May 2025 13:55:00 +0300 Subject: [PATCH 06/19] Add zoom control --- .../plugins/YandexMapV3/renderer/components/Map/Map.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index d326550cf8..534683f49d 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -20,11 +20,14 @@ export const { YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer, YMapMarker, + YMapControls, } = reactify.module(ymaps3); const {YMapHint, YMapHintContext} = reactify.module( await ymaps3.import('@yandex/ymaps3-hint@0.0.1'), ); -const {YMapDefaultMarker} = reactify.module(await import('@yandex/ymaps3-default-ui-theme')); +const {YMapDefaultMarker, YMapZoomControl} = reactify.module( + await import('@yandex/ymaps3-default-ui-theme'), +); const clustererModule = await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1'); const {clusterByGrid} = clustererModule; @@ -88,6 +91,9 @@ export const Map = (props: Props) => { + + + {features.map((feature, index) => { return ( Date: Wed, 14 May 2025 17:01:37 +0300 Subject: [PATCH 07/19] fix lint --- .../ClusterMarker/ClusterMarker.scss | 12 ++++---- .../ClusterMarker/ClusterMarker.tsx | 20 ++++++------- .../renderer/components/Tooltip/Tooltip.scss | 30 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss index 985a23869f..ead68e9eb8 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss @@ -1,11 +1,11 @@ .ymap-cluster-marker { - background-color: var(--g-color-base-background); + background-color: var(--g-color-base-background); - display: flex; - justify-content: center; - align-items: center; - height: 46px; + display: flex; + justify-content: center; + align-items: center; + height: 46px; width: 46px; border-radius: 50%; border: 6px solid var(--g-color-base-brand); -} \ No newline at end of file +} diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx index a97639e8ee..722f7665fa 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx @@ -7,17 +7,17 @@ import './ClusterMarker.scss'; const b = block('ymap-cluster-marker'); type Props = { - count: number; + count: number; }; export const ClusterMarker = (props: Props) => { - const {count} = props; + const {count} = props; - return ( -
-
- {count} -
-
- ); -} \ No newline at end of file + return ( +
+
+ {count} +
+
+ ); +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss index 7b2b1c6cc1..7b2adfdb99 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss @@ -1,29 +1,29 @@ .yandex-map-tooltip { - border-radius: 3px; - background-color: var(--g-color-infographics-tooltip-bg); - padding: 10px 14px; - font-size: 12px; - box-sizing: border-box; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); + border-radius: 3px; + background-color: var(--g-color-infographics-tooltip-bg); + padding: 10px 14px; + font-size: 12px; + box-sizing: border-box; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); - margin: 12px; + margin: 12px; - &__title { - white-space: nowrap; - font-size: 13px; - font-weight: 600; - } + &__title { + white-space: nowrap; + font-size: 13px; + font-weight: 600; + } - &__row { + &__row { display: table-row; } &__cell { - white-space: nowrap; + white-space: nowrap; display: table-cell; padding: 2px 7px 2px 0; max-width: 370px; text-overflow: ellipsis; overflow: hidden; } -} \ No newline at end of file +} From 55119a6a884237bba569918b3676441d3a2cee05 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 15 May 2025 12:12:32 +0300 Subject: [PATCH 08/19] Lang param for map --- .../YandexMapV3/renderer/YandexMapWidget.tsx | 2 ++ .../plugins/YandexMapV3/renderer/yamap.ts | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx index 8189ed0b8f..9545f6c30a 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -23,6 +23,7 @@ export const YandexMapWidget = React.forwardRef< >((props, forwardedRef) => { const { id, + lang, // onLoad, data: {data: originalData, config, libraryConfig}, } = props; @@ -44,6 +45,7 @@ export const YandexMapWidget = React.forwardRef< try { await isYmapsReady({ apiKey: libraryConfig?.apiKey ?? '', + lang, }); setLoading(false); } catch (e) { diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts index b205206d8d..98044ae8e6 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/yamap.ts @@ -1,5 +1,17 @@ +function getLangParam(lang?: string) { + switch (lang) { + case 'en': { + return 'en_EN'; + } + default: + case 'ru': { + return 'ru_RU'; + } + } +} + export function isYmapsReady(args: {apiKey: string; lang?: string}) { - const {apiKey, lang = 'en_EN'} = args; + const {apiKey, lang} = args; return new Promise((resolve, reject) => { if (typeof ymaps3 !== 'undefined') { ymaps3.ready.then(() => resolve(ymaps3)); @@ -7,7 +19,7 @@ export function isYmapsReady(args: {apiKey: string; lang?: string}) { } const script = document.createElement('script'); - script.src = `https://api-maps.yandex.ru/v3/?apikey=${apiKey}&lang=${lang}`; + script.src = `https://api-maps.yandex.ru/v3/?apikey=${apiKey}&lang=${getLangParam(lang)}`; script.onload = () => { (ymaps3 as any).ready.then(() => resolve(ymaps3)); }; From f554ba8aa3aea06c1729548b343d2d58c6361855 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 15 May 2025 12:51:08 +0300 Subject: [PATCH 09/19] fix inspector --- .../YandexMapV3/renderer/YandexMapWidget.tsx | 14 +++++++++++--- .../YandexMapV3/renderer/components/Map/Map.tsx | 11 ++++++++++- .../ChartKit/plugins/YandexMapV3/renderer/types.ts | 2 ++ .../ChartKit/plugins/YandexMapV3/renderer/utils.ts | 7 +++---- .../ChartKit/plugins/YandexMapV3/types.ts | 10 +++++++++- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx index 9545f6c30a..f4896391fc 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/YandexMapWidget.tsx @@ -24,7 +24,7 @@ export const YandexMapWidget = React.forwardRef< const { id, lang, - // onLoad, + onLoad, data: {data: originalData, config, libraryConfig}, } = props; @@ -51,12 +51,20 @@ export const YandexMapWidget = React.forwardRef< } catch (e) { setLoading(false); } - }, [libraryConfig?.apiKey]); + }, [libraryConfig?.apiKey, lang]); React.useEffect(() => { loadYmap(); }, [loadYmap]); + const handleMapReady = React.useCallback(() => { + const widgetRendering = Performance.getDuration(generatedId); + + if (onLoad && widgetRendering) { + onLoad({widget: props.data, widgetRendering}); + } + }, [generatedId, onLoad, props.data]); + if (!originalData || (typeof originalData === 'object' && !Object.keys(originalData).length)) { throw new ChartKitError({ code: CHARTKIT_ERROR_CODE.NO_DATA, @@ -77,7 +85,7 @@ export const YandexMapWidget = React.forwardRef< return (
- +
); }); diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 534683f49d..f51f12a251 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -35,14 +35,23 @@ const {YMapClusterer} = reactify.module(clustererModule); import '@yandex/ymaps3-default-ui-theme/dist/esm/index.css'; -export type Props = YandexMapWidgetData; +export type Props = YandexMapWidgetData & { + onReady?: () => void; +}; const clusterSource = 'clusterer-source'; export const Map = (props: Props) => { + const {onReady} = props; const mapConfig = getMapConfig(props); const {location, features = [], points = [], clusteredPoints = []} = mapConfig; + React.useEffect(() => { + if (onReady) { + setTimeout(onReady, 0); + } + }, [onReady]); + const getHint = React.useCallback( (mapObject: unknown) => get(mapObject, 'properties.hint'), [], diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts index 8a8b14e8f3..4c5a416b0a 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts @@ -1,4 +1,5 @@ import type { + BehaviorType, DrawingStyle, GenericGeometry, LngLat, @@ -14,4 +15,5 @@ export type YMapConfig = { }[]; points: any[]; clusteredPoints: any[]; + behaviors?: BehaviorType[]; }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 1bd1be1fb5..2ccd631d90 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -1,5 +1,5 @@ import * as turf from '@turf/circle'; -import type {LngLat, PolygonGeometry} from '@yandex/ymaps3-types'; +import type {BehaviorType, LngLat, PolygonGeometry} from '@yandex/ymaps3-types'; import type {SingleItem, YandexMapWidgetData} from '../types'; @@ -147,15 +147,14 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { return acc; }, [] as any[]); - const mapProps: any = { + return { location: { center, zoom: libraryConfig?.state?.zoom ?? 10, }, + behaviors: libraryConfig?.state?.behaviors as BehaviorType[], features, points, clusteredPoints, }; - - return mapProps; } diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index 5ad7289568..a257a1bc15 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -1,5 +1,6 @@ import type {ChartKitOnLoadData, ChartKitProps, ChartKitType} from '@gravity-ui/chartkit'; import type {StringParams} from '@gravity-ui/chartkit/highcharts'; +import type {BehaviorType, LngLat} from '@yandex/ymaps3-types'; import type {Language} from 'shared'; import type {OnChangeData} from '../../../types'; @@ -60,12 +61,19 @@ type ItemClusterer = { export type YandexMapWidgetDataItem = SingleItem | ItemCollection | ItemClusterer; +export type YandexMapControlType = 'zoomControl'; + export type YandexMapWidgetData = { data?: YandexMapWidgetDataItem[]; config?: Record; libraryConfig?: { apiKey?: string; - state?: Record; + state?: { + center?: LngLat; + zoom?: number; + controls: YandexMapControlType[]; + behaviors?: BehaviorType[]; + }; options?: Record; }; unresolvedParams?: StringParams; From 5517182189049c54828b87c58ff652307b393cf2 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 15 May 2025 13:54:24 +0300 Subject: [PATCH 10/19] dark theme --- .../YandexMapV3/renderer/components/Map/Map.tsx | 13 +++++++++++-- .../ChartKit/plugins/YandexMapV3/renderer/types.ts | 3 +++ .../ChartKit/plugins/YandexMapV3/renderer/utils.ts | 11 +++++++---- .../ChartKit/plugins/YandexMapV3/types.ts | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index f51f12a251..5014959333 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import {useThemeType} from '@gravity-ui/uikit'; import type {Feature as ClusterFeature} from '@yandex/ymaps3-types/packages/clusterer'; import get from 'lodash/get'; import ReactDom from 'react-dom'; @@ -21,6 +22,7 @@ export const { YMapDefaultFeaturesLayer, YMapMarker, YMapControls, + YMapScaleControl, } = reactify.module(ymaps3); const {YMapHint, YMapHintContext} = reactify.module( await ymaps3.import('@yandex/ymaps3-hint@0.0.1'), @@ -46,6 +48,8 @@ export const Map = (props: Props) => { const mapConfig = getMapConfig(props); const {location, features = [], points = [], clusteredPoints = []} = mapConfig; + const theme = useThemeType(); + React.useEffect(() => { if (onReady) { setTimeout(onReady, 0); @@ -93,15 +97,20 @@ export const Map = (props: Props) => { [], ); + const controls = new Set(mapConfig.controls ?? []); + return ( - + - + {controls.has('zoomControl') && } + + + {controls.has('scaleControl') && } {features.map((feature, index) => { return ( diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts index 4c5a416b0a..f9466949fe 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts @@ -6,6 +6,8 @@ import type { YMapLocationRequest, } from '@yandex/ymaps3-types'; +import type {YandexMapControlType} from '../types'; + export type YMapConfig = { location: YMapLocationRequest; features: { @@ -16,4 +18,5 @@ export type YMapConfig = { points: any[]; clusteredPoints: any[]; behaviors?: BehaviorType[]; + controls?: YandexMapControlType[]; }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 2ccd631d90..abb367deb7 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -1,5 +1,5 @@ import * as turf from '@turf/circle'; -import type {BehaviorType, LngLat, PolygonGeometry} from '@yandex/ymaps3-types'; +import type {LngLat, PolygonGeometry} from '@yandex/ymaps3-types'; import type {SingleItem, YandexMapWidgetData} from '../types'; @@ -98,7 +98,9 @@ function getPointObject(item: SingleItem) { export function getMapConfig(args: YandexMapWidgetData): YMapConfig { const {data: originalData = [], libraryConfig} = args; - const center = reverseCoordinates(libraryConfig?.state?.center); + const center = reverseCoordinates(libraryConfig?.state?.center) as LngLat; + const zoom = libraryConfig?.state?.zoom ?? 10; + const features = originalData.reduce((acc, item) => { if ('feature' in item) { const mapObject = getMapObject(item); @@ -150,9 +152,10 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { return { location: { center, - zoom: libraryConfig?.state?.zoom ?? 10, + zoom, }, - behaviors: libraryConfig?.state?.behaviors as BehaviorType[], + behaviors: libraryConfig?.state?.behaviors, + controls: libraryConfig?.state?.controls, features, points, clusteredPoints, diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index a257a1bc15..131a4adb9a 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -61,7 +61,7 @@ type ItemClusterer = { export type YandexMapWidgetDataItem = SingleItem | ItemCollection | ItemClusterer; -export type YandexMapControlType = 'zoomControl'; +export type YandexMapControlType = 'zoomControl' | 'scaleControl'; export type YandexMapWidgetData = { data?: YandexMapWidgetDataItem[]; From a57d1b67f0b8d81a21c79e548e95472e35845f15 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 15 May 2025 17:09:34 +0300 Subject: [PATCH 11/19] map bounds --- .../charts-engine/components/chart-generator.ts | 3 ++- .../renderer/components/Tooltip/Tooltip.scss | 10 ++++++++++ .../renderer/components/Tooltip/Tooltip.tsx | 16 +++++++++++++++- .../plugins/YandexMapV3/renderer/constants.ts | 3 +++ .../plugins/YandexMapV3/renderer/utils.ts | 11 +++++++---- .../ChartKit/plugins/YandexMapV3/types.ts | 3 ++- 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/server/components/charts-engine/components/chart-generator.ts b/src/server/components/charts-engine/components/chart-generator.ts index b183efbabb..4f82822085 100644 --- a/src/server/components/charts-engine/components/chart-generator.ts +++ b/src/server/components/charts-engine/components/chart-generator.ts @@ -271,6 +271,7 @@ export const chartGenerator = { const isD3Graph = type.indexOf('d3') > -1; const isTable = type.indexOf('table') > -1; + const isYmap = type.indexOf('ymap') > -1; if (type.indexOf('metric') > -1) { chart.statface_metric = chart.config.replace('#module', chartTemplate.module); } else if (type.indexOf('markup') > -1 || isTable) { @@ -293,7 +294,7 @@ export const chartGenerator = { chart.prepare = chart.prepare.replace('#apiVersion', apiVersion); chart.sources = chart.sources.replace('#apiVersion', apiVersion); - const chartsWithConfig = isD3Graph || isTable; + const chartsWithConfig = isD3Graph || isTable || isYmap; const {config: _, ...chartWithoutConfig} = chart; return {chart: chartsWithConfig ? chart : chartWithoutConfig, links, type}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss index 7b2adfdb99..f178de164d 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.scss @@ -26,4 +26,14 @@ text-overflow: ellipsis; overflow: hidden; } + + &__color { + position: relative; + top: -1px; + width: 12px; + height: 6px; + margin-right: 3px; + border-radius: 1px; + display: inline-block; + } } diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx index 10117cf785..65e1be0492 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Tooltip/Tooltip.tsx @@ -12,6 +12,10 @@ type HintProps = { name?: string; value?: number; text?: string; + data?: { + text?: string; + color?: string; + }[]; }; type Props = {context: React.Context}; @@ -24,7 +28,7 @@ export const Tooltip = (props: Props) => { return null; } - const {name: title, value, text} = hintContext.hint; + const {name: title, value, text, data} = hintContext.hint; const formattedValue = typeof value === 'number' ? formatNumber(value) : null; return ( @@ -34,6 +38,16 @@ export const Tooltip = (props: Props) => { {formattedValue &&
{formattedValue}
} {text &&
{text}
}
+ {data?.map((d, index) => ( +
+ {d.color && ( +
+ +
+ )} + {d.text &&
{d.text}
} +
+ ))} ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts index e69de29bb2..e5fce0c6d3 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/constants.ts @@ -0,0 +1,3 @@ +export const DEFAULT_ZOOM = 10; + +export const DEFAULT_CENTER = [55.76, 37.64]; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index abb367deb7..f16811b693 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -1,8 +1,9 @@ import * as turf from '@turf/circle'; -import type {LngLat, PolygonGeometry} from '@yandex/ymaps3-types'; +import type {LngLat, LngLatBounds, PolygonGeometry} from '@yandex/ymaps3-types'; import type {SingleItem, YandexMapWidgetData} from '../types'; +import {DEFAULT_CENTER, DEFAULT_ZOOM} from './constants'; import type {YMapConfig} from './types'; function reverseCoordinates(data: unknown): unknown { @@ -98,8 +99,9 @@ function getPointObject(item: SingleItem) { export function getMapConfig(args: YandexMapWidgetData): YMapConfig { const {data: originalData = [], libraryConfig} = args; - const center = reverseCoordinates(libraryConfig?.state?.center) as LngLat; - const zoom = libraryConfig?.state?.zoom ?? 10; + const center = reverseCoordinates(libraryConfig?.state?.center ?? DEFAULT_CENTER) as LngLat; + const zoom = libraryConfig?.state?.zoom ?? DEFAULT_ZOOM; + const bounds = reverseCoordinates(libraryConfig?.state?.bounds) as LngLatBounds; const features = originalData.reduce((acc, item) => { if ('feature' in item) { @@ -153,9 +155,10 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { location: { center, zoom, + bounds, }, behaviors: libraryConfig?.state?.behaviors, - controls: libraryConfig?.state?.controls, + controls: libraryConfig?.state?.controls ?? ['zoomControl'], features, points, clusteredPoints, diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index 131a4adb9a..af19caf353 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -1,6 +1,6 @@ import type {ChartKitOnLoadData, ChartKitProps, ChartKitType} from '@gravity-ui/chartkit'; import type {StringParams} from '@gravity-ui/chartkit/highcharts'; -import type {BehaviorType, LngLat} from '@yandex/ymaps3-types'; +import type {BehaviorType, LngLat, LngLatBounds} from '@yandex/ymaps3-types'; import type {Language} from 'shared'; import type {OnChangeData} from '../../../types'; @@ -73,6 +73,7 @@ export type YandexMapWidgetData = { zoom?: number; controls: YandexMapControlType[]; behaviors?: BehaviorType[]; + bounds?: LngLatBounds; }; options?: Record; }; From 8a73b27a9d90c33888b4e6b5243783ffaab5c2a0 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 15 May 2025 17:36:18 +0300 Subject: [PATCH 12/19] marker color --- .../renderer/components/Map/Map.tsx | 11 +++++++++- .../plugins/YandexMapV3/renderer/types.ts | 10 ++++++++- .../plugins/YandexMapV3/renderer/utils.ts | 22 +++++++++++++------ .../ChartKit/plugins/YandexMapV3/types.ts | 2 ++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 5014959333..38cf2146c7 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -123,7 +123,16 @@ export const Map = (props: Props) => { ); })} {points.map((point, index) => { - return ; + return ( + + ); })} diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts index f9466949fe..b136f179a2 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts @@ -1,3 +1,4 @@ +import type {MarkerColorProps} from '@yandex/ymaps3-default-ui-theme'; import type { BehaviorType, DrawingStyle, @@ -8,6 +9,13 @@ import type { import type {YandexMapControlType} from '../types'; +export type YMapPoint = { + coordinates: LngLat; + properties?: Record; + color?: MarkerColorProps; + zIndex?: number; +}; + export type YMapConfig = { location: YMapLocationRequest; features: { @@ -15,7 +23,7 @@ export type YMapConfig = { style?: DrawingStyle; properties?: Record; }[]; - points: any[]; + points: YMapPoint[]; clusteredPoints: any[]; behaviors?: BehaviorType[]; controls?: YandexMapControlType[]; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index f16811b693..f4e4435c55 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -4,12 +4,12 @@ import type {LngLat, LngLatBounds, PolygonGeometry} from '@yandex/ymaps3-types'; import type {SingleItem, YandexMapWidgetData} from '../types'; import {DEFAULT_CENTER, DEFAULT_ZOOM} from './constants'; -import type {YMapConfig} from './types'; +import type {YMapConfig, YMapPoint} from './types'; -function reverseCoordinates(data: unknown): unknown { +function reverseCoordinates(data: unknown): LngLat | LngLat[] { if (Array.isArray(data)) { if (Array.isArray(data[0])) { - return data.map(reverseCoordinates); + return data.map(reverseCoordinates) as LngLat[]; } return [...data].reverse(); @@ -88,12 +88,20 @@ function getMapObject(item: SingleItem) { } } -function getPointObject(item: SingleItem) { +function getPointObject(item: SingleItem): YMapPoint { + const iconColor = item.options.iconColor ?? ''; return { - coordinates: reverseCoordinates(item.feature.geometry.coordinates), + coordinates: reverseCoordinates(item.feature.geometry.coordinates) as LngLat, properties: { hint: item.feature.properties, }, + color: { + day: iconColor, + night: iconColor, + strokeDay: 'transparent', + strokeNight: 'transparent', + }, + zIndex: item.options.zIndex, }; } @@ -123,7 +131,7 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { return acc; }, [] as any[]); - const points = originalData.reduce((acc, item) => { + const points = originalData.reduce((acc, item) => { if ('feature' in item && item.feature.geometry.type === 'Point') { acc.push(getPointObject(item)); } @@ -137,7 +145,7 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { } return acc; - }, [] as any[]); + }, []); const clusteredPoints = originalData.reduce((acc, item) => { if ('clusterer' in item) { diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index af19caf353..dc913aba8a 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -32,9 +32,11 @@ type Point = { }; type YmapItemOptions = { + iconColor?: string; fillColor?: string; opacity?: number; strokeWidth?: number; + zIndex?: number; }; type GeometryType = GeometryCircle | GeometryRectangle | GeometryPolygon | GeometryPolyline | Point; From 0a7d9760672b5a13b00279037c02f8e7cc6b05d5 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Thu, 15 May 2025 19:43:53 +0300 Subject: [PATCH 13/19] Point marker radius and color --- .../features/features-list/YMapV3ForWizard.ts | 10 ++ src/shared/types/feature.ts | 2 + .../ChartKit/helpers/chartkit-adapter.ts | 9 +- .../renderer/components/Layer/Layer.tsx | 18 +++ .../renderer/components/Map/Map.tsx | 59 +++----- .../components/PointMarker/PointMarker.scss | 8 ++ .../components/PointMarker/PointMarker.tsx | 30 ++++ .../renderer/components/ymaps3.tsx | 30 ++++ .../plugins/YandexMapV3/renderer/types.ts | 25 ++-- .../plugins/YandexMapV3/renderer/utils.ts | 136 +++++++++--------- 10 files changed, 205 insertions(+), 122 deletions(-) create mode 100644 src/server/components/features/features-list/YMapV3ForWizard.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ymaps3.tsx diff --git a/src/server/components/features/features-list/YMapV3ForWizard.ts b/src/server/components/features/features-list/YMapV3ForWizard.ts new file mode 100644 index 0000000000..b9dc2139d9 --- /dev/null +++ b/src/server/components/features/features-list/YMapV3ForWizard.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.YMapV3ForWizard, + state: { + development: false, + production: false, + }, +}); diff --git a/src/shared/types/feature.ts b/src/shared/types/feature.ts index bbfe6791b0..df7d2e376a 100644 --- a/src/shared/types/feature.ts +++ b/src/shared/types/feature.ts @@ -96,6 +96,8 @@ export enum Feature { EnablePublicGallery = 'EnablePublicGallery', /** Setting in the table to preserve spaces and line breaks */ PreWrapTableSetting = 'PreWrapTableSetting', + /** Use yandex-map api v3 for wizard geo visualizations */ + YMapV3ForWizard = 'YMapV3ForWizard', } export type FeatureConfig = Record; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts b/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts index 40d9b6a32f..bd912d7779 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts @@ -28,6 +28,7 @@ export const getChartkitType = (data?: LoadedWidgetData): ChartKitType | undefin return undefined; } + const isWizardOrQl = get(data, 'isNewWizard') || get(data, 'isQL'); let chartkitType: ChartKitType | undefined; switch (type) { @@ -66,10 +67,10 @@ export const getChartkitType = (data?: LoadedWidgetData): ChartKitType | undefin } case 'ymap': { - chartkitType = 'yandexmap_v3'; - const isWizardOrQl = get(data, 'isNewWizard') || get(data, 'isQL'); if (isWizardOrQl) { - chartkitType = 'yandexmap'; + chartkitType = isEnabledFeature(Feature.YMapV3ForWizard) + ? 'yandexmap_v3' + : 'yandexmap'; } else { chartkitType = isEnabledFeature('EnableYandexMapV3CEWidget') ? 'yandexmap_v3' @@ -92,8 +93,6 @@ export const getChartkitType = (data?: LoadedWidgetData): ChartKitType | undefin } case 'table': { - const isWizardOrQl = get(data, 'isNewWizard') || get(data, 'isQL'); - if (isWizardOrQl || isEnabledFeature(Feature.NewTableWidgetForCE)) { chartkitType = 'table'; } diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx new file mode 100644 index 0000000000..39e7009639 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import type {YMapLayerConfig} from '../../types'; +import {PointMarker} from '../PointMarker/PointMarker'; + +type Props = YMapLayerConfig & {}; + +export const YandexMapLayer = (props: Props) => { + const {points, opacity} = props; + + return ( + + {points.map((point, index) => { + return ; + })} + + ); +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 38cf2146c7..390ba2bf04 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -3,39 +3,29 @@ import React from 'react'; import {useThemeType} from '@gravity-ui/uikit'; import type {Feature as ClusterFeature} from '@yandex/ymaps3-types/packages/clusterer'; import get from 'lodash/get'; -import ReactDom from 'react-dom'; import type {YandexMapWidgetData} from '../../../types'; import {getMapConfig} from '../../utils'; import {ClusterMarker} from '../ClusterMarker/ClusterMarker'; +import {YandexMapLayer} from '../Layer/Layer'; import {Tooltip} from '../Tooltip/Tooltip'; - -const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]); - -export const reactify = ymaps3React.reactify.bindTo(React, ReactDom); -export const { +import { YMap, + YMapClusterer, + YMapControls, + YMapDefaultFeaturesLayer, + YMapDefaultMarker, + YMapDefaultSchemeLayer, + YMapFeature, YMapFeatureDataSource, + YMapHint, + YMapHintContext, YMapLayer, - YMapFeature, - YMapDefaultSchemeLayer, - YMapDefaultFeaturesLayer, YMapMarker, - YMapControls, YMapScaleControl, -} = reactify.module(ymaps3); -const {YMapHint, YMapHintContext} = reactify.module( - await ymaps3.import('@yandex/ymaps3-hint@0.0.1'), -); -const {YMapDefaultMarker, YMapZoomControl} = reactify.module( - await import('@yandex/ymaps3-default-ui-theme'), -); - -const clustererModule = await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1'); -const {clusterByGrid} = clustererModule; -const {YMapClusterer} = reactify.module(clustererModule); - -import '@yandex/ymaps3-default-ui-theme/dist/esm/index.css'; + YMapZoomControl, + clusterByGrid, +} from '../ymaps3'; export type Props = YandexMapWidgetData & { onReady?: () => void; @@ -46,7 +36,11 @@ const clusterSource = 'clusterer-source'; export const Map = (props: Props) => { const {onReady} = props; const mapConfig = getMapConfig(props); - const {location, features = [], points = [], clusteredPoints = []} = mapConfig; + const { + location, + layers: [{features = [], clusteredPoints = []}], + } = mapConfig; + const controls = new Set(mapConfig.controls ?? []); const theme = useThemeType(); @@ -97,8 +91,6 @@ export const Map = (props: Props) => { [], ); - const controls = new Set(mapConfig.controls ?? []); - return ( @@ -122,18 +114,9 @@ export const Map = (props: Props) => { /> ); })} - {points.map((point, index) => { - return ( - - ); - })} + {mapConfig.layers.map((layer, index) => ( + + ))} { + const {coordinates, properties, zIndex, color, opacity, radius = 2} = props; + const size = radius * 10; + + return ( + +
+
+ ); +}; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ymaps3.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ymaps3.tsx new file mode 100644 index 0000000000..ca58138068 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ymaps3.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import ReactDom from 'react-dom'; + +const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]); +const reactify = ymaps3React.reactify.bindTo(React, ReactDom); +export const { + YMap, + YMapFeatureDataSource, + YMapLayer, + YMapContainer, + YMapFeature, + YMapDefaultSchemeLayer, + YMapDefaultFeaturesLayer, + YMapMarker, + YMapControls, + YMapScaleControl, +} = reactify.module(ymaps3); +export const {YMapHint, YMapHintContext} = reactify.module( + await ymaps3.import('@yandex/ymaps3-hint@0.0.1'), +); +export const {YMapDefaultMarker, YMapZoomControl} = reactify.module( + await import('@yandex/ymaps3-default-ui-theme'), +); + +const clustererModule = await ymaps3.import('@yandex/ymaps3-clusterer@0.0.1'); +export const {clusterByGrid} = clustererModule; +export const {YMapClusterer} = reactify.module(clustererModule); + +import '@yandex/ymaps3-default-ui-theme/dist/esm/index.css'; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts index b136f179a2..70ddf64cfd 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts @@ -1,4 +1,3 @@ -import type {MarkerColorProps} from '@yandex/ymaps3-default-ui-theme'; import type { BehaviorType, DrawingStyle, @@ -12,19 +11,27 @@ import type {YandexMapControlType} from '../types'; export type YMapPoint = { coordinates: LngLat; properties?: Record; - color?: MarkerColorProps; + color?: string; zIndex?: number; + radius?: number; }; -export type YMapConfig = { - location: YMapLocationRequest; - features: { - geometry: GenericGeometry; - style?: DrawingStyle; - properties?: Record; - }[]; +export type YMapFeature = { + geometry: GenericGeometry; + style?: DrawingStyle; + properties?: Record; +}; + +export type YMapLayerConfig = { + opacity?: number; + features: YMapFeature[]; points: YMapPoint[]; clusteredPoints: any[]; +}; + +export type YMapConfig = { + location: YMapLocationRequest; behaviors?: BehaviorType[]; controls?: YandexMapControlType[]; + layers: YMapLayerConfig[]; }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index f4e4435c55..6f8e7b78eb 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -4,7 +4,7 @@ import type {LngLat, LngLatBounds, PolygonGeometry} from '@yandex/ymaps3-types'; import type {SingleItem, YandexMapWidgetData} from '../types'; import {DEFAULT_CENTER, DEFAULT_ZOOM} from './constants'; -import type {YMapConfig, YMapPoint} from './types'; +import type {YMapConfig, YMapFeature, YMapPoint} from './types'; function reverseCoordinates(data: unknown): LngLat | LngLat[] { if (Array.isArray(data)) { @@ -34,9 +34,7 @@ function getMapObject(item: SingleItem) { fill: item.options?.fillColor, fillOpacity: item.options?.opacity, }, - properties: { - hint: item.feature.properties, - }, + properties: getMapOpjectProperties(item), }; } case 'Rectangle': { @@ -58,9 +56,7 @@ function getMapObject(item: SingleItem) { fill: item.options?.fillColor, fillOpacity: item.options?.opacity, }, - properties: { - hint: item.feature.properties, - }, + properties: getMapOpjectProperties(item), }; } case 'LineString': @@ -77,9 +73,7 @@ function getMapObject(item: SingleItem) { }, ], }, - properties: { - hint: item.feature.properties, - }, + properties: getMapOpjectProperties(item), }; } default: { @@ -88,20 +82,25 @@ function getMapObject(item: SingleItem) { } } +function getMapOpjectProperties(item: SingleItem) { + const props = item.feature.properties ?? {}; + const result: Record = {}; + + if (['name', 'value', 'text', 'data'].some((field) => field in props)) { + result.hint = item.feature.properties; + } + + return result; +} + function getPointObject(item: SingleItem): YMapPoint { const iconColor = item.options.iconColor ?? ''; return { coordinates: reverseCoordinates(item.feature.geometry.coordinates) as LngLat, - properties: { - hint: item.feature.properties, - }, - color: { - day: iconColor, - night: iconColor, - strokeDay: 'transparent', - strokeNight: 'transparent', - }, + properties: getMapOpjectProperties(item), + color: iconColor, zIndex: item.options.zIndex, + radius: Number(item.feature.properties?.radius ?? 2), }; } @@ -111,54 +110,6 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { const zoom = libraryConfig?.state?.zoom ?? DEFAULT_ZOOM; const bounds = reverseCoordinates(libraryConfig?.state?.bounds) as LngLatBounds; - const features = originalData.reduce((acc, item) => { - if ('feature' in item) { - const mapObject = getMapObject(item); - if (mapObject) { - acc.push(mapObject); - } - } - - if ('collection' in item) { - item.collection.children.forEach((d) => { - const mapObject = getMapObject(d); - if (mapObject) { - acc.push(mapObject); - } - }); - } - - return acc; - }, [] as any[]); - - const points = originalData.reduce((acc, item) => { - if ('feature' in item && item.feature.geometry.type === 'Point') { - acc.push(getPointObject(item)); - } - - if ('collection' in item) { - item.collection.children.forEach((d) => { - if (d.feature.geometry.type === 'Point') { - acc.push(getPointObject(d)); - } - }); - } - - return acc; - }, []); - - const clusteredPoints = originalData.reduce((acc, item) => { - if ('clusterer' in item) { - item.clusterer.forEach((d) => { - if (d.feature.geometry.type === 'Point') { - acc.push(getPointObject(d)); - } - }); - } - - return acc; - }, [] as any[]); - return { location: { center, @@ -167,8 +118,53 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { }, behaviors: libraryConfig?.state?.behaviors, controls: libraryConfig?.state?.controls ?? ['zoomControl'], - features, - points, - clusteredPoints, + layers: originalData.map((item) => { + const points = []; + const clusteredPoints: any[] = []; + const features: YMapFeature[] = []; + + if ('feature' in item && item.feature.geometry.type === 'Point') { + points.push(getPointObject(item)); + } + + if ('collection' in item) { + item.collection.children.forEach((d) => { + if (d.feature.geometry.type === 'Point') { + points.push(getPointObject(d)); + } + }); + } + + if ('clusterer' in item) { + item.clusterer.forEach((d) => { + if (d.feature.geometry.type === 'Point') { + clusteredPoints.push(getPointObject(d)); + } + }); + } + + if ('feature' in item) { + const mapObject = getMapObject(item); + if (mapObject) { + features.push(mapObject); + } + } + + if ('collection' in item) { + item.collection.children.forEach((d) => { + const mapObject = getMapObject(d); + if (mapObject) { + features.push(mapObject); + } + }); + } + + return { + opacity: item.options.opacity, + points, + clusteredPoints, + features, + }; + }), }; } From 63eadfb24d12e456149ef6d94041fff06feca2e1 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Fri, 16 May 2025 12:42:34 +0300 Subject: [PATCH 14/19] fix type --- .../plugins/YandexMapV3/renderer/types.ts | 2 +- .../plugins/YandexMapV3/renderer/utils.ts | 42 +++++++++---------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts index 70ddf64cfd..335e3dbb2c 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/types.ts @@ -26,7 +26,7 @@ export type YMapLayerConfig = { opacity?: number; features: YMapFeature[]; points: YMapPoint[]; - clusteredPoints: any[]; + clusteredPoints: YMapPoint[]; }; export type YMapConfig = { diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 6f8e7b78eb..cf658dab16 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -1,5 +1,5 @@ import * as turf from '@turf/circle'; -import type {LngLat, LngLatBounds, PolygonGeometry} from '@yandex/ymaps3-types'; +import type {GenericGeometry, LngLat, LngLatBounds, PolygonGeometry} from '@yandex/ymaps3-types'; import type {SingleItem, YandexMapWidgetData} from '../types'; @@ -23,7 +23,7 @@ function getCircleGeoJSON(center: LngLat, radiusMeters: number): PolygonGeometry return geometry as PolygonGeometry; } -function getMapObject(item: SingleItem) { +function getMapFeatureObject(item: SingleItem): YMapFeature | null { switch (item.feature.geometry.type) { case 'Circle': { const center = reverseCoordinates(item.feature.geometry.coordinates); @@ -65,7 +65,7 @@ function getMapObject(item: SingleItem) { geometry: { ...item.feature.geometry, coordinates: reverseCoordinates(item.feature.geometry.coordinates), - }, + } as GenericGeometry, style: { stroke: [ { @@ -119,12 +119,19 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { behaviors: libraryConfig?.state?.behaviors, controls: libraryConfig?.state?.controls ?? ['zoomControl'], layers: originalData.map((item) => { - const points = []; - const clusteredPoints: any[] = []; + const points: YMapPoint[] = []; + const clusteredPoints: YMapPoint[] = []; const features: YMapFeature[] = []; - if ('feature' in item && item.feature.geometry.type === 'Point') { - points.push(getPointObject(item)); + if ('feature' in item) { + if (item.feature.geometry.type === 'Point') { + points.push(getPointObject(item)); + } + + const mapObject = getMapFeatureObject(item); + if (mapObject) { + features.push(mapObject); + } } if ('collection' in item) { @@ -132,6 +139,11 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { if (d.feature.geometry.type === 'Point') { points.push(getPointObject(d)); } + + const mapObject = getMapFeatureObject(d); + if (mapObject) { + features.push(mapObject); + } }); } @@ -143,22 +155,6 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { }); } - if ('feature' in item) { - const mapObject = getMapObject(item); - if (mapObject) { - features.push(mapObject); - } - } - - if ('collection' in item) { - item.collection.children.forEach((d) => { - const mapObject = getMapObject(d); - if (mapObject) { - features.push(mapObject); - } - }); - } - return { opacity: item.options.opacity, points, From e436fb871df3e90f00a683b901a7a473d5f6e70b Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Fri, 16 May 2025 18:13:15 +0300 Subject: [PATCH 15/19] cluster point(pie) --- .../ChartKit/helpers/gravity-charts/utils.ts | 2 +- .../ClusterMarker/ClusterMarker.scss | 23 +++++--- .../ClusterMarker/ClusterMarker.tsx | 58 +++++++++++++++++-- .../renderer/components/Layer/Layer.tsx | 46 ++++++++++++++- .../renderer/components/Map/Map.tsx | 56 +----------------- .../components/PointMarker/PointMarker.scss | 5 +- .../components/PointMarker/PointMarker.tsx | 10 +++- .../renderer/components/ymaps3.tsx | 2 + .../plugins/YandexMapV3/renderer/types.ts | 5 +- .../plugins/YandexMapV3/renderer/utils.ts | 29 +++++++--- 10 files changed, 153 insertions(+), 83 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/utils.ts index ddfad83ef3..e8badb294f 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/utils.ts @@ -11,7 +11,7 @@ const Opacity = { UNSELECTED: 0.5, }; -const SVG_NAMESPACE_URI = 'http://www.w3.org/2000/svg'; +export const SVG_NAMESPACE_URI = 'http://www.w3.org/2000/svg'; export function getPointActionParams( point: ChartSeriesData, diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss index ead68e9eb8..464b329892 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss @@ -1,11 +1,18 @@ .ymap-cluster-marker { - background-color: var(--g-color-base-background); - - display: flex; - justify-content: center; - align-items: center; - height: 46px; - width: 46px; border-radius: 50%; - border: 6px solid var(--g-color-base-brand); + margin-top: -50%; + margin-left: -50%; + padding: 5px; + overflow: hidden; + + &__content { + display: flex; + justify-content: center; + align-items: center; + background-color: var(--g-color-base-background); + border-radius: 50%; + width: 100%; + height: 100%; + padding: 2px; + } } diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx index 722f7665fa..279cd2cc27 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx @@ -1,23 +1,69 @@ import React from 'react'; +import type {LngLat} from '@yandex/ymaps3-types'; import block from 'bem-cn-lite'; +import {groupBy} from 'lodash'; + +import type {ClusterFeature} from '../ymaps3'; +import {YMapMarker} from '../ymaps3'; import './ClusterMarker.scss'; const b = block('ymap-cluster-marker'); type Props = { - count: number; + coordinates: LngLat; + properties?: Record; + source: string; + opacity?: number; + radius?: number; + points: ClusterFeature[]; }; export const ClusterMarker = (props: Props) => { - const {count} = props; + const {coordinates, properties, source, opacity, radius = 2, points} = props; + const {properties: {color, zIndex} = {}} = points[0]; + const size = radius * 20; + + const backgroundImage = React.useMemo(() => { + const segments = Object.entries(groupBy(points, (p) => p.properties?.color)).map( + ([pointColor, items]) => { + return { + color: pointColor, + angle: (360 * items.length) / points.length, + }; + }, + ); + + return `conic-gradient(${segments + .map((s, index) => { + const startAngle = index === 0 ? 0 : segments[index - 1].angle; + return `${s.color} ${startAngle}deg ${s.angle}deg`; + }) + .join(', ')})`; + }, [points]); return ( -
-
- {count} + +
+
+ {points.length} +
-
+ ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx index 39e7009639..89f0c88d1f 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx @@ -1,18 +1,62 @@ import React from 'react'; import type {YMapLayerConfig} from '../../types'; +import {ClusterMarker} from '../ClusterMarker/ClusterMarker'; import {PointMarker} from '../PointMarker/PointMarker'; +import {YMapClusterer, YMapFeatureDataSource, YMapLayer, clusterByGrid} from '../ymaps3'; type Props = YMapLayerConfig & {}; export const YandexMapLayer = (props: Props) => { - const {points, opacity} = props; + const {id: layerId, points, opacity, clusteredPoints} = props; + const clusterSource = `${layerId}_clusterer-source`; + const gridSizedMethod = clusterByGrid({gridSize: 64}); + + const marker = React.useCallback( + (feature) => { + return ( + + ); + }, + [clusterSource, opacity], + ); + + const cluster = React.useCallback( + (coordinates, features) => { + const {id: pointId} = features[0]; + return ( + + ); + }, + [clusterSource], + ); return ( {points.map((point, index) => { return ; })} + + + ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 390ba2bf04..20c967afca 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -1,44 +1,34 @@ import React from 'react'; import {useThemeType} from '@gravity-ui/uikit'; -import type {Feature as ClusterFeature} from '@yandex/ymaps3-types/packages/clusterer'; import get from 'lodash/get'; import type {YandexMapWidgetData} from '../../../types'; import {getMapConfig} from '../../utils'; -import {ClusterMarker} from '../ClusterMarker/ClusterMarker'; import {YandexMapLayer} from '../Layer/Layer'; import {Tooltip} from '../Tooltip/Tooltip'; import { YMap, - YMapClusterer, YMapControls, YMapDefaultFeaturesLayer, - YMapDefaultMarker, YMapDefaultSchemeLayer, YMapFeature, - YMapFeatureDataSource, YMapHint, YMapHintContext, - YMapLayer, - YMapMarker, YMapScaleControl, YMapZoomControl, - clusterByGrid, } from '../ymaps3'; export type Props = YandexMapWidgetData & { onReady?: () => void; }; -const clusterSource = 'clusterer-source'; - export const Map = (props: Props) => { const {onReady} = props; const mapConfig = getMapConfig(props); const { location, - layers: [{features = [], clusteredPoints = []}], + layers: [{features = []}], } = mapConfig; const controls = new Set(mapConfig.controls ?? []); @@ -55,42 +45,6 @@ export const Map = (props: Props) => { [], ); - const clusterPoints = React.useMemo(() => { - return clusteredPoints.map( - (p, index) => - ({ - id: String(index), - geometry: {coordinates: p.coordinates}, - properties: p.properties, - }) as ClusterFeature, - ); - }, [clusteredPoints]); - const gridSizedMethod = clusterByGrid({gridSize: 64}); - - const marker = React.useCallback( - (feature) => ( - - ), - [], - ); - - const cluster = React.useCallback( - (coordinates, features) => ( - - - - ), - [], - ); - return ( @@ -117,14 +71,6 @@ export const Map = (props: Props) => { {mapConfig.layers.map((layer, index) => ( ))} - - - ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.scss index aa523ca2e6..024373f65d 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.scss @@ -1,8 +1,9 @@ .ymap-point-marker { + background-color: 5px solid var(--g-color-base-brand); display: flex; justify-content: center; align-items: center; - height: 46px; - width: 46px; border-radius: 50%; + margin-top: -50%; + margin-left: -50%; } diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.tsx index cba85cd4c6..da43a85f7e 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/PointMarker/PointMarker.tsx @@ -10,17 +10,23 @@ import './PointMarker.scss'; const b = block('ymap-point-marker'); type Props = YMapPoint & { + source?: string; color?: string; opacity?: number; radius?: number; }; export const PointMarker = (props: Props) => { - const {coordinates, properties, zIndex, color, opacity, radius = 2} = props; + const {source, coordinates, properties, zIndex, color, opacity, radius = 2} = props; const size = radius * 10; return ( - +
; @@ -23,10 +25,11 @@ export type YMapFeature = { }; export type YMapLayerConfig = { + id: string; opacity?: number; features: YMapFeature[]; points: YMapPoint[]; - clusteredPoints: YMapPoint[]; + clusteredPoints: ClusterFeature[]; }; export type YMapConfig = { diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index cf658dab16..9887cbcc24 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -3,6 +3,7 @@ import type {GenericGeometry, LngLat, LngLatBounds, PolygonGeometry} from '@yand import type {SingleItem, YandexMapWidgetData} from '../types'; +import type {ClusterFeature} from './components/ymaps3'; import {DEFAULT_CENTER, DEFAULT_ZOOM} from './constants'; import type {YMapConfig, YMapFeature, YMapPoint} from './types'; @@ -84,7 +85,11 @@ function getMapFeatureObject(item: SingleItem): YMapFeature | null { function getMapOpjectProperties(item: SingleItem) { const props = item.feature.properties ?? {}; - const result: Record = {}; + const result: Record = { + color: item.options.iconColor ?? '', + zIndex: item.options.zIndex, + radius: Number(item.feature.properties?.radius ?? 2), + }; if (['name', 'value', 'text', 'data'].some((field) => field in props)) { result.hint = item.feature.properties; @@ -94,11 +99,10 @@ function getMapOpjectProperties(item: SingleItem) { } function getPointObject(item: SingleItem): YMapPoint { - const iconColor = item.options.iconColor ?? ''; return { coordinates: reverseCoordinates(item.feature.geometry.coordinates) as LngLat, properties: getMapOpjectProperties(item), - color: iconColor, + color: item.options.iconColor ?? '', zIndex: item.options.zIndex, radius: Number(item.feature.properties?.radius ?? 2), }; @@ -118,9 +122,9 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { }, behaviors: libraryConfig?.state?.behaviors, controls: libraryConfig?.state?.controls ?? ['zoomControl'], - layers: originalData.map((item) => { + layers: originalData.map((item, index) => { const points: YMapPoint[] = []; - const clusteredPoints: YMapPoint[] = []; + const clusteredPoints: ClusterFeature[] = []; const features: YMapFeature[] = []; if ('feature' in item) { @@ -148,14 +152,25 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { } if ('clusterer' in item) { - item.clusterer.forEach((d) => { + item.clusterer.forEach((d, pointIndex) => { if (d.feature.geometry.type === 'Point') { - clusteredPoints.push(getPointObject(d)); + clusteredPoints.push({ + id: String(pointIndex), + geometry: { + type: 'Point', + coordinates: reverseCoordinates( + d.feature.geometry.coordinates, + ) as LngLat, + }, + properties: getMapOpjectProperties(d), + type: 'Feature', + }); } }); } return { + id: `layer-${index}`, opacity: item.options.opacity, points, clusteredPoints, From ec6c149c9c74c9d07fad6c280d23f7403e106172 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Fri, 16 May 2025 18:31:32 +0300 Subject: [PATCH 16/19] fix cluster pie colors --- .../renderer/components/ClusterMarker/ClusterMarker.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx index 279cd2cc27..b02c5e6dd7 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx @@ -26,19 +26,21 @@ export const ClusterMarker = (props: Props) => { const size = radius * 20; const backgroundImage = React.useMemo(() => { + let angle = 0; const segments = Object.entries(groupBy(points, (p) => p.properties?.color)).map( ([pointColor, items]) => { + angle = angle + (360 * items.length) / points.length; return { color: pointColor, - angle: (360 * items.length) / points.length, + angle, }; }, ); return `conic-gradient(${segments .map((s, index) => { - const startAngle = index === 0 ? 0 : segments[index - 1].angle; - return `${s.color} ${startAngle}deg ${s.angle}deg`; + const prevAngle = index === 0 ? 0 : segments[index - 1].angle; + return `${s.color} ${prevAngle}deg ${s.angle}deg`; }) .join(', ')})`; }, [points]); From 6ba8e605a550bfefa7a128b8561b7abb3e14766a Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Fri, 16 May 2025 18:46:56 +0300 Subject: [PATCH 17/19] polyline --- .../renderer/components/Layer/Layer.tsx | 24 +++++++++++++++++-- .../renderer/components/Map/Map.tsx | 16 +------------ .../plugins/YandexMapV3/renderer/utils.ts | 1 + 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx index 89f0c88d1f..5ce9531b6c 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Layer/Layer.tsx @@ -3,12 +3,18 @@ import React from 'react'; import type {YMapLayerConfig} from '../../types'; import {ClusterMarker} from '../ClusterMarker/ClusterMarker'; import {PointMarker} from '../PointMarker/PointMarker'; -import {YMapClusterer, YMapFeatureDataSource, YMapLayer, clusterByGrid} from '../ymaps3'; +import { + YMapClusterer, + YMapFeature, + YMapFeatureDataSource, + YMapLayer, + clusterByGrid, +} from '../ymaps3'; type Props = YMapLayerConfig & {}; export const YandexMapLayer = (props: Props) => { - const {id: layerId, points, opacity, clusteredPoints} = props; + const {id: layerId, points, opacity, clusteredPoints, features} = props; const clusterSource = `${layerId}_clusterer-source`; const gridSizedMethod = clusterByGrid({gridSize: 64}); @@ -57,6 +63,20 @@ export const YandexMapLayer = (props: Props) => { method={gridSizedMethod} features={clusteredPoints} /> + {features.map((feature, index) => { + const style = { + ...feature.style, + stroke: feature.style?.stroke?.map((s) => ({...s, opacity})), + }; + return ( + + ); + })} ); }; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx index 20c967afca..c7384ae002 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/Map/Map.tsx @@ -12,7 +12,6 @@ import { YMapControls, YMapDefaultFeaturesLayer, YMapDefaultSchemeLayer, - YMapFeature, YMapHint, YMapHintContext, YMapScaleControl, @@ -26,10 +25,7 @@ export type Props = YandexMapWidgetData & { export const Map = (props: Props) => { const {onReady} = props; const mapConfig = getMapConfig(props); - const { - location, - layers: [{features = []}], - } = mapConfig; + const {location} = mapConfig; const controls = new Set(mapConfig.controls ?? []); const theme = useThemeType(); @@ -58,16 +54,6 @@ export const Map = (props: Props) => { {controls.has('scaleControl') && } - {features.map((feature, index) => { - return ( - - ); - })} {mapConfig.layers.map((layer, index) => ( ))} diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 9887cbcc24..4c1c50a409 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -68,6 +68,7 @@ function getMapFeatureObject(item: SingleItem): YMapFeature | null { coordinates: reverseCoordinates(item.feature.geometry.coordinates), } as GenericGeometry, style: { + zIndex: item.options?.zIndex, stroke: [ { width: item.options?.strokeWidth, From a0b55c7e9fc25bd72dc74568f063d05b4b579628 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Fri, 16 May 2025 19:57:39 +0300 Subject: [PATCH 18/19] fix --- .../ClusterMarker/ClusterMarker.scss | 13 +++++- .../ClusterMarker/ClusterMarker.tsx | 15 ++++--- .../plugins/YandexMapV3/renderer/utils.ts | 43 ++++++++++++++++--- .../ChartKit/plugins/YandexMapV3/types.ts | 5 ++- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss index 464b329892..709d6fd532 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.scss @@ -1,9 +1,18 @@ .ymap-cluster-marker { - border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; margin-top: -50%; margin-left: -50%; - padding: 5px; + border-radius: 50%; overflow: hidden; + border: 1px solid var(--g-color-base-background); + + &__pie { + padding: 5px; + width: 100%; + height: 100%; + } &__content { display: flex; diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx index b02c5e6dd7..455ae68fd1 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/components/ClusterMarker/ClusterMarker.tsx @@ -22,7 +22,7 @@ type Props = { export const ClusterMarker = (props: Props) => { const {coordinates, properties, source, opacity, radius = 2, points} = props; - const {properties: {color, zIndex} = {}} = points[0]; + const {properties: {zIndex} = {}} = points[0]; const size = radius * 20; const backgroundImage = React.useMemo(() => { @@ -55,15 +55,20 @@ export const ClusterMarker = (props: Props) => {
-
- {points.length} +
+
+ {points.length} +
diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 4c1c50a409..57ee073c82 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -1,13 +1,14 @@ import * as turf from '@turf/circle'; import type {GenericGeometry, LngLat, LngLatBounds, PolygonGeometry} from '@yandex/ymaps3-types'; +import get from 'lodash/get'; -import type {SingleItem, YandexMapWidgetData} from '../types'; +import type {SingleItem, YandexMapWidgetData, YmapItemOptions} from '../types'; import type {ClusterFeature} from './components/ymaps3'; import {DEFAULT_CENTER, DEFAULT_ZOOM} from './constants'; import type {YMapConfig, YMapFeature, YMapPoint} from './types'; -function reverseCoordinates(data: unknown): LngLat | LngLat[] { +function reverseCoordinates(data: unknown): LngLat | LngLat[] | LngLat[][] { if (Array.isArray(data)) { if (Array.isArray(data[0])) { return data.map(reverseCoordinates) as LngLat[]; @@ -24,7 +25,7 @@ function getCircleGeoJSON(center: LngLat, radiusMeters: number): PolygonGeometry return geometry as PolygonGeometry; } -function getMapFeatureObject(item: SingleItem): YMapFeature | null { +function getMapFeatureObject(item: SingleItem, layerOptions?: YmapItemOptions): YMapFeature | null { switch (item.feature.geometry.type) { case 'Circle': { const center = reverseCoordinates(item.feature.geometry.coordinates); @@ -60,8 +61,7 @@ function getMapFeatureObject(item: SingleItem): YMapFeature | null { properties: getMapOpjectProperties(item), }; } - case 'LineString': - case 'Polygon': { + case 'LineString': { return { geometry: { ...item.feature.geometry, @@ -74,6 +74,29 @@ function getMapFeatureObject(item: SingleItem): YMapFeature | null { width: item.options?.strokeWidth, }, ], + fillOpacity: layerOptions?.opacity, + }, + properties: getMapOpjectProperties(item), + }; + } + case 'Polygon': { + return { + geometry: { + type: 'Polygon', + coordinates: reverseCoordinates( + item.feature.geometry.coordinates, + ) as LngLat[][], + }, + style: { + zIndex: item.options?.zIndex, + stroke: [ + { + width: item.options?.strokeWidth ?? 1, + color: item.options?.strokeColor, + }, + ], + fill: item.options?.fillColor ?? layerOptions?.fillColorEmptyPolygon, + fillOpacity: layerOptions?.fillOpacity, }, properties: getMapOpjectProperties(item), }; @@ -139,6 +162,16 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { } } + if ('polygonmap' in item) { + const polygons = get(item, 'polygonmap.polygons.features'); + polygons.forEach((d: any) => { + const mapObject = getMapFeatureObject({...d, feature: d}, item.options); + if (mapObject) { + features.push(mapObject); + } + }); + } + if ('collection' in item) { item.collection.children.forEach((d) => { if (d.feature.geometry.type === 'Point') { diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index dc913aba8a..eb5cb284bb 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -31,12 +31,15 @@ type Point = { coordinates: [number, number]; }; -type YmapItemOptions = { +export type YmapItemOptions = { iconColor?: string; fillColor?: string; opacity?: number; strokeWidth?: number; + strokeColor?: string; zIndex?: number; + fillOpacity?: number; + fillColorEmptyPolygon?: string; }; type GeometryType = GeometryCircle | GeometryRectangle | GeometryPolygon | GeometryPolyline | Point; From 174e497dce2ebaef44e0a010417a4c12817d8771 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Mon, 19 May 2025 11:59:18 +0300 Subject: [PATCH 19/19] [wip] heatmap --- .../plugins/YandexMapV3/renderer/utils.ts | 18 +++++++++++++----- .../ChartKit/plugins/YandexMapV3/types.ts | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts index 57ee073c82..6f8384e582 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/renderer/utils.ts @@ -110,8 +110,8 @@ function getMapFeatureObject(item: SingleItem, layerOptions?: YmapItemOptions): function getMapOpjectProperties(item: SingleItem) { const props = item.feature.properties ?? {}; const result: Record = { - color: item.options.iconColor ?? '', - zIndex: item.options.zIndex, + color: item.options?.iconColor ?? '', + zIndex: item.options?.zIndex, radius: Number(item.feature.properties?.radius ?? 2), }; @@ -126,8 +126,8 @@ function getPointObject(item: SingleItem): YMapPoint { return { coordinates: reverseCoordinates(item.feature.geometry.coordinates) as LngLat, properties: getMapOpjectProperties(item), - color: item.options.iconColor ?? '', - zIndex: item.options.zIndex, + color: item.options?.iconColor ?? 'var(--g-color-base-brand)', + zIndex: item.options?.zIndex, radius: Number(item.feature.properties?.radius ?? 2), }; } @@ -172,6 +172,14 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { }); } + if ('heatmap' in item) { + item.heatmap.forEach((d) => { + if (d.geometry.type === 'Point') { + points.push(getPointObject({...d, feature: d})); + } + }); + } + if ('collection' in item) { item.collection.children.forEach((d) => { if (d.feature.geometry.type === 'Point') { @@ -205,7 +213,7 @@ export function getMapConfig(args: YandexMapWidgetData): YMapConfig { return { id: `layer-${index}`, - opacity: item.options.opacity, + opacity: item.options?.opacity, points, clusteredPoints, features, diff --git a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts index eb5cb284bb..23c2d8d746 100644 --- a/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts +++ b/src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMapV3/types.ts @@ -44,12 +44,14 @@ export type YmapItemOptions = { type GeometryType = GeometryCircle | GeometryRectangle | GeometryPolygon | GeometryPolyline | Point; +type MapFeature = { + geometry: GeometryType; + properties?: Record; +}; + export type SingleItem = { - feature: { - geometry: GeometryType; - properties?: Record; - }; - options: YmapItemOptions; + feature: MapFeature; + options?: YmapItemOptions; }; type ItemCollection = { @@ -64,7 +66,12 @@ type ItemClusterer = { options: YmapItemOptions; }; -export type YandexMapWidgetDataItem = SingleItem | ItemCollection | ItemClusterer; +type Heatmap = { + heatmap: MapFeature[]; + options?: YmapItemOptions; +}; + +export type YandexMapWidgetDataItem = SingleItem | ItemCollection | ItemClusterer | Heatmap; export type YandexMapControlType = 'zoomControl' | 'scaleControl';