数据可视化图表库跨平台【免费下载链接】LiveCharts2Beautiful, interactive charts, maps, and gauges. One API for every .NET UI framework.项目地址https://gitcode.com/gh_mirrors/li/LiveCharts2点击查看免费下载本文以仓库docs/samples/maps/markersOnMap/示例文档为骨架结合其模板渲染链路、ViewModelsSamples中的真实实现、GeoVisualElement与GeoMapChart的底层源码以及快照测试完整讲解如何在 LiveCharts2 的GeoMap上把任意视觉元素标记圆点、气泡、标注等锚定到真实的经度纬度坐标并让标记自动跟随地图的平移、缩放与球体旋转。读完本文你将能复刻官方 MarkersOnMap 示例并掌握Project/Unproject等坐标互转 API为业务地图叠加任意自定义覆盖物。一、示例文档从哪来模板渲染链路仓库中docs/samples/maps/markersOnMap/template.md本身并不是一篇手写的 Markdown而是 LiveCharts2 文档系统使用的 Scriban 模板占位文件其正文只有一行渲染指令{{ render ~/shared/genericSampleSimple.md }}按照 docs/readme.md 的说明docs 目录下的 Markdown 会先经过 Scriban一种类似 Liquid 的模板语言预处理{{ render ... }}会被替换为对应文件的真实内容。这个指令链接着 docs/shared/genericSampleSimple.md后者再依次渲染docs/shared/genericSampleSimpleHeader.md生成文章标题Markers On Map与示例结果图render_current_directory_view_model注入当前示例目录下的 ViewModel 源码即 samples/ViewModelsSamples/Maps/MarkersOnMap/ViewModel.csrender_current_directory_view注入对应平台的视图源码.axaml/.xaml/.razor/ 纯代码docs/shared/relatedTo.md追加相关文章导航。也就是说该示例文档的正文完全来自仓库中可运行的示例代码。因此本文直接以这些真实源码为准——它们既是文档内容也是可以在本仓库直接查看、运行的最小可复现实例。二、示例要解决的问题把标记钉在地图真实坐标上MarkersOnMap 的场景非常典型一张热力底图 若干城市坐标点标记必须锚定在真实经纬度上而不是手工换算的屏幕像素。官方示例横跨东西半球选择了五座城市见 ViewModel.cs城市经度纬度New York纽约-74.0040.71Tokyo东京139.6935.69Madrid马德里-3.7040.42São Paulo圣保罗-46.63-23.55Sydney悉尼151.21-33.87之所以选择这些散布于各半球的城市注释里写得很清楚让标记能可见地跟随平移pan、缩放zoom和正交投影旋转orthographic rotation——如果所有点都挤在一起就看不出标记与地图的联动效果了。示例运行结果如下文档自带的结果图即 docs/samples/maps/markersOnMap/result.png浅灰底色的世界地图上参与热力系列的国土按数值着色五个橙红色圆形标记分别落在美国东部纽约、欧洲西南马德里、巴西东部圣保罗、东亚东京与澳大利亚东南悉尼。三、核心机制GeoVisualElement——经纬度锚点的底层实现标记能钉在经纬度上靠的是LiveChartsCore.Geo命名空间下的 GeoVisualElement.cs。它本质是一个包装器包住一个普通Visual或已过时的VisualElement并暴露Longitude/Latitude两个经纬度属性源码 L77-L81。关键逻辑在它的Invalidate(Chart)重写中源码 L84-L109public override void Invalidate(Chart chart) { if (chart is not GeoMapChart geoMap) throw new InvalidOperationException( ${nameof(GeoVisualElement)} can only be used on a {nameof(GeoMapChart)}.); var pixel geoMap.Project(Longitude, Latitude); if (pixel is null) { // 坐标不在可见区域如正交投影的背面半球时 // 本帧把内部视觉移出画布避免它残留在上一次的屏幕位置。 Visual.RemoveFromUI(chart); return; } SetLocation(pixel.Value.X, pixel.Value.Y); Visual.Invalidate(chart); Visual.RemoveOldPaints(chart.View); }这段源码揭示了三件事也是理解整个示例的钥匙只能用在 GeoMapChart 上构造约束明确GeoVisualElement专属于地图不能用于 Cartesian / Pie 等其他图表每帧重新投影每次 Measure 都会用地图当前投影器把(Longitude, Latitude)换算成屏幕像素因此平移、缩放、球体旋转时标记天然跟随无需手动更新坐标不可见即移除当坐标处于投影不可见区域例如正交投影的背面半球时Project返回null包装器立刻把内部视觉移出画布避免幽灵标记残留在最后一次可见位置。另外还有两处容易被忽略的细节SetLocation对于新式Visual是写visual.DrawnElement.X / Y源码 L124-L127所以内部视觉绝不能在自己的Measure里设置位置否则会覆盖掉投影坐标显式调用Visual.RemoveOldPaints(chart.View)是为了镜像Chart.AddVisual中的旧画笔清理逻辑如果用户运行期把填充色从绿换成红旧画笔不会残留在画布上。四、第一步准备热力底图HeatLandSeries 与短名索引标记要落在地图上首先得有地图。示例的底图用HeatLandSeries实现ViewModel.cspublic HeatLandSeries[] Series { get; } [ new HeatLandSeries { Lands [ new HeatLand { Name usa, Value 15 }, new HeatLand { Name jpn, Value 14 }, new HeatLand { Name esp, Value 10 }, new HeatLand { Name bra, Value 12 }, new HeatLand { Name aus, Value 11 }, ], }, ];HeatLand.Name使用的是国家/地区的短名shortName它与内置世界地图 GeoJSON 的索引文件 docs/_assets/word-map-index.json 一一对应——该文件里可检索到usa、jpn、esp、bra、aus等条目。换句话说给哪个国家赋了值哪个国家就会被热力渐变着色Value则决定颜色深浅。这套映射机制在地图总览文档 docs/geomapchart/overview.md 中有完整说明。五、第二步自定义标记视觉 CityMarkerVisual标记本体是一个继承自Visual的自定义视觉定义在 CityMarkerVisual.cspublic class CityMarkerVisual : Visual { private const float Size 14f; protected override IDrawnElement DrawnElement { get; } new CircleGeometry { Width Size, Height Size, Fill new SolidColorPaint(new SKColor(255, 87, 51)), // OrangeRed Stroke new SolidColorPaint(SKColors.White), StrokeThickness 2, TranslateTransform new(-Size / 2f, -Size / 2f), }; protected override void Measure(Chart chart) { } }这段代码里藏着四个反直觉但必须遵守的要点Measure必须留空投影坐标由GeoVisualElement写入DrawnElement.X / Y如果在这里自己定位就会覆盖锚点坐标标记将不再跟随地图TranslateTransform负责居中投影器给出的是包围盒左上角像素偏移-Size/2后圆形才真正圆心对准经纬度点StrokeThickness必须写在几何体上源码注释明确说明几何体自带 Paint 时以几何体上设置的描边厚度为准写在意料之外的 Paint 上会被静默忽略画出 1px 的细线声明为IDrawnElement而非CircleGeometrynet462不支持协变返回且Measure并不需要派生类型这是出于兼容性的选择。快照测试中使用了另一种等价写法——GeometryVisualCircleGeometry直接内联构造见 tests/SnapshotTests/MapsTests.cs同样是Translate(-size/2f, -size/2f)居中 OrangeRed 填充 白色描边。两种写法效果一致前者便于复用、后者适合一次性声明。六、第三步组装标记并绑定到 GeoMap6.1 工厂方法产出锚定坐标的包装器ViewModel 用一个静态工厂方法把坐标和视觉绑定成GeoVisualElementViewModel.csprivate static GeoVisualElement CityMarker(double longitude, double latitude) new(new CityMarkerVisual()) { Longitude longitude, Latitude latitude, };五座城市各生成一个实例存入IChartElement[] VisualElements属性供视图消费。6.2 各平台视图的绑定方式重要差异值得特别注意的是GeoMap.VisualElements目前并不是所有平台都可直接 XAML 绑定的依赖属性。源码注释见 Avalonia 视图说明GeoMap 的源生成器尚未把它暴露为可绑定的DependencyProperty因此在 XAML 系平台需要退而求其次、在 code-behind 里赋值var vm (ViewModel)DataContext!; var geoMap this.FindGeoMap(geoMap)!; geoMap.VisualElements vm.VisualElements;Avalonia、WPF、.NET MAUI 均采用这一模式而Blazor因为没有依赖属性绑定约束可以在 razor 里直接传参View.razorGeoMap refChart Seriesvm.Series VisualElementsvm.VisualElements MapProjectionLiveChartsCore.Geo.MapProjection.Mercator /GeoMapWinForms / Eto.Forms 则在代码中直接构建控件WinForms 视图、Eto 视图var chart new GeoMap { Series vm.Series, VisualElements vm.VisualElements, MapProjection MapProjection.Mercator, ... };6.3 投影选择Mercator所有平台的示例视图都设置了MapProjectionMercator。投影枚举定义在 MapProjection.cs共三种Default仅把经纬度平移到控件坐标适合非地理用途地图Mercator扁平世界地图保角但极地放大Google Maps / Bing Maps 同款Orthographic3D 球体视图一次只见一个半球可旋转查看另一侧。Mercator 是最直观展示标记跟随地图的投影若换成 Orthographic标记会在球体转到背面时自动消失由第三节的pixel is null分支保证这正是示例注释所说的可见地跟踪旋转。七、底层原理标记如何在每一帧钉住地图把GeoVisualElement放入VisualElements后剩下的工作全部由 GeoMapChart.cs 的 Measure 管线完成注册与清理Measure()先用InitializeVisualsCollector()快照上一帧元素随后遍历MapView.VisualElements里IsVisible的元素逐一AddVisual(visual)源码 L627-L636帧末CollectVisuals()会把仍在待删集合中的元素即从VisualElements移除、或IsVisiblefalse的元素从画布删除。这就是动态增删标记能即时生效的原因投影与视口变换GeoVisualElement.Invalidate调用GeoMapChart.Project(longitude, latitude)源码 L253-L259其内部先检查投影器可见性再经ApplyViewportTransform源码 L279-L286叠加当前ZoomLevel与PanOffset变换——这正是标记能跟随滚轮缩放与拖拽平移的原因旋转球体正交投影下CenterLongitude/CenterLatitude改变视角直接赋值即瞬移RotateTo(longitude, latitude, durationMs 800)则用QuadraticInOut缓动做动画过渡源码 L225-L242且做了经度差归一化——170°→-170° 会走反经线捷径而不是绕地球 340°。动画期间标记随每次投影自动更新位置。八、测试与快照验证仓库用快照测试把 MarkersOnMap 的效果钉死为黄金图像确保后续改动不破坏行为。见 tests/SnapshotTests/MapsTests.csMarkersOnMapL207-L226800×600 的SKGeoMapMercator 投影注入五座城市的GeoVisualElement标记断言渲染结果与MapsTests_MarkersOnMap.png一致MarkersOnMap_RemovedBetweenFramesL306-L332同一图表实例上先渲染两个标记随后把VisualElements置空再渲染一帧断言第二帧画布上标记确实被清除——专门验证上述InitializeVisualsCollector → CollectVisuals生命周期清理路径。快照图就保存在仓库中tests/SnapshotTests/Snapshots/MapsTests_MarkersOnMap.png800×600五个标记同屏九、进阶能力坐标与像素互转、命中检测、交互除了画标记这套机制还派生出一系列实用能力详见 docs/geomapchart/overview.md 的 Custom overlays at lat/lon 小节像素 → 坐标CoreChart.Unproject(LvcPoint)把点击的屏幕像素反算成经纬度返回null表示点击落在投影可见区之外如正交圆盘之外可据此区分点到空白与点到真实坐标坐标 → 像素CoreChart.Project(lon, lat)返回null表示该坐标当前不可见如球体背面可用于自行定位元素或做命中测试同步查询需要即时命中检测例如自定义手势时调用geoMap.GetPointsAt(new LvcPointD(x, y))即可无需订阅事件交互模式InteractionMode控制滚轮缩放与拖拽平移的开关None/Pan/Zoom/Both正交投影默认禁用平移、启用滚轮缩放。若需要完全自定义底图加载自己的 GeoJSON参考 CustomMap 示例 的加载配方若想给标记加上更多形态气泡、图标、标签等通用视觉元素的用法见 VisualElements 示例。赞分享数据可视化图表库跨平台【免费下载链接】LiveCharts2Beautiful, interactive charts, maps, and gauges. One API for every .NET UI framework.项目地址https://gitcode.com/gh_mirrors/li/LiveCharts2点击查看免费下载相关推荐folium MousePosition 插件实战在地图上实时显示鼠标经纬度坐标folium MousePosition 插件实战在地图上实时显示鼠标经纬度坐标 导读 本指南围绕 folium 的 MousePosition 插件展开讲数据可视化数据分析GIS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考