canvas-globe 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,39 @@ All notable changes to this package are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.4.0] - 2026-09-19
10
+
11
+ ### Added
12
+
13
+ - Added opt-in cached XYZ overview tiles with Web Mercator conversion, globe and flat-map
14
+ reprojection, provider callbacks, request ceilings, graceful failures, CORS-aware export, and
15
+ attribution painted into every frame.
16
+
17
+ ### Fixed
18
+
19
+ - Reprojected equirectangular textures correctly in Mercator and Natural Earth map views instead
20
+ of stretching them to the projected viewport.
21
+
22
+ ## [1.3.0] - 2026-09-19
23
+
24
+ ### Added
25
+
26
+ - Added an interactive screen-space hex-bin density layer for globe and map
27
+ modes, including weighted aggregation, colour ramps, source-marker hit
28
+ payloads, framework event types, and 5,000-marker performance coverage.
29
+ - Added an Angular SSR and hydration starter with prerendered HTML, a stable
30
+ canvas shell, client hydration, and project-creator support.
31
+ - Added a reproducible nine-library globe comparison harness for timing,
32
+ bundle size, package metadata, server-import, and accessibility-surface
33
+ measurements.
34
+
35
+ ### Fixed
36
+
37
+ - Prevented the Angular component from creating a CanvasGlobe instance during
38
+ server rendering.
39
+ - Corrected public TypeScript declarations for story-step overrides and theme
40
+ options.
41
+
9
42
  ## [1.2.0] - 2026-09-18
10
43
 
11
44
  ### Added
package/README.md CHANGED
@@ -35,17 +35,19 @@
35
35
 
36
36
  [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz_small.svg)](https://stackblitz.com/github/Shree-hari/canvas-globe?startScript=start)
37
37
 
38
- **Start with your stack:** [Vanilla JS](starters/vanilla-vite) | [React + Vite](starters/react-vite) | [Next.js](starters/nextjs-app-router) | [Vue](starters/vue-vite) | [SvelteKit](starters/sveltekit) | [Web Component](starters/web-component-vite)
38
+ **Start with your stack:** [Vanilla JS](starters/vanilla-vite) | [React + Vite](starters/react-vite) | [Next.js](starters/nextjs-app-router) | [Nuxt](starters/nuxt-ssr) | [Vue](starters/vue-vite) | [SvelteKit](starters/sveltekit) | [Angular SSR](starters/angular-ssr) | [Web Component](starters/web-component-vite)
39
39
 
40
40
  **Install with shadcn:** `npx shadcn@latest add https://canvasglobe.swiftools.com/r/canvas-globe.json`
41
41
 
42
42
  `canvas-globe` is a zero-dependency JavaScript library for an interactive
43
43
  **3D globe** and **flat world map** on Canvas 2D. It works with
44
44
  vanilla JavaScript, React, Vue, Angular, Svelte, or a Web Component and requires no WebGL, map API
45
- key, tile service, or runtime network request.
45
+ key, tile service, or runtime network request. Optional XYZ tiles are available when you explicitly
46
+ configure a provider.
46
47
 
47
- - **Zero dependencies:** no WebGL, D3, map tiles, or API keys
48
- - **Zero network calls:** country geometry ships inside the package
48
+ - **Zero dependencies:** no required WebGL, D3, map tiles, or API keys
49
+ - **Zero required network calls:** country geometry ships inside the package
50
+ - **Optional XYZ tiles:** opt into a cached raster provider for globe and overview-map backgrounds
49
51
  - **Interactive:** drag, zoom, pinch, hover, and click
50
52
  - **Marker support:** weighted markers, avatars, pulse rings, and clustering
51
53
  - **Great-circle arcs:** animated routes clipped at the horizon
@@ -139,19 +141,19 @@ your existing application rather than installing a second React copy.
139
141
  For a plain `<script>` installation, use the versioned UMD build:
140
142
 
141
143
  ```html
142
- <script src="https://cdn.jsdelivr.net/npm/canvas-globe@1.2.0/dist/canvas-globe.umd.js"></script>
144
+ <script src="https://cdn.jsdelivr.net/npm/canvas-globe@1.4.0/dist/canvas-globe.umd.js"></script>
143
145
  ```
144
146
 
145
147
  The same npm release is also available from UNPKG:
146
148
 
147
149
  ```html
148
- <script src="https://unpkg.com/canvas-globe@1.2.0/dist/canvas-globe.umd.js"></script>
150
+ <script src="https://unpkg.com/canvas-globe@1.4.0/dist/canvas-globe.umd.js"></script>
149
151
  ```
150
152
 
151
153
  Modern browsers can import the package through an ESM CDN:
152
154
 
153
155
  ```js
154
- import { createGlobe } from "https://esm.sh/canvas-globe@1.2.0";
156
+ import { createGlobe } from "https://esm.sh/canvas-globe@1.4.0";
155
157
  ```
156
158
 
157
159
  Pin an exact version in production so a future release cannot change a deployed page unexpectedly.
@@ -171,7 +173,7 @@ import { createGlobe } from "canvas-globe";
171
173
  Or drop the UMD build on a page with no build step at all:
172
174
 
173
175
  ```html
174
- <script src="https://cdn.jsdelivr.net/npm/canvas-globe@1.2.0/dist/canvas-globe.umd.js"></script>
176
+ <script src="https://cdn.jsdelivr.net/npm/canvas-globe@1.4.0/dist/canvas-globe.umd.js"></script>
175
177
  <canvas id="globe" style="width:520px;aspect-ratio:1"></canvas>
176
178
  <script>
177
179
  CanvasGlobe.createGlobe(document.getElementById("globe"), {
@@ -371,6 +373,7 @@ included effects. See `types/fx.d.ts` for every option and callback signature.
371
373
  | `orbits` | `0` | Decorative rings: a count (0-6) or explicit specs |
372
374
  | `texture` | Not set | Equirectangular image painted onto the sphere |
373
375
  | `textureQuality` | `"auto"` | Pixel step for the texture pass; higher is faster |
376
+ | `tileLayer` | Not set | Optional cached XYZ overview tiles; see [Tile layers](#tile-layers) |
374
377
  | `focus` | Not set | Frame one country: `"IN"` or `{ country, isolate, dim, outlineWidth }` |
375
378
  | `countryMedia` | Not set | Media clipped to each country, keyed by ISO, id or name |
376
379
  | `scene` | Not set | Whole composition: preset plus the layers a job needs |
@@ -381,6 +384,7 @@ included effects. See `types/fx.d.ts` for every option and callback signature.
381
384
  | `timeline` | Not set | `{ at }`: hides markers whose `date` has not arrived |
382
385
  | `transparentBackground` | `false` | Skip the ocean fill so exports keep an alpha channel |
383
386
  | `heatmap` | `false` | Additive density blobs: `{ radius, intensity, color }` |
387
+ | `hexBins` | `false` | Interactive screen-space density cells; see [Hex bins](#hex-bins) |
384
388
  | `spikes` | `false` | Bars off the surface, sized by `count`: `{ height, width }` |
385
389
  | `labels` | `false` | `"markers"` \| `"countries"` \| `"both"`, with collision avoidance |
386
390
  | `legend` | Not set | `{ title, items }` or `{ title, scale, position }` |
@@ -443,6 +447,76 @@ With `cluster: true`, dense areas collapse into a single bubble and your callbac
443
447
  `{ cluster: true, count, markers, lat, lon }` instead. Clustering happens in screen space, so it
444
448
  re-balances automatically as you zoom.
445
449
 
450
+ ## Hex bins
451
+
452
+ Use hex bins when individual markers are too dense to read. CanvasGlobe aggregates the visible,
453
+ projected markers into a pointy-top hexagonal grid, so the density view updates naturally as the
454
+ globe rotates, the map pans, or the user zooms.
455
+
456
+ ```js
457
+ const globe = createGlobe(canvas, {
458
+ markers: demandPoints,
459
+ hexBins: {
460
+ radius: 19,
461
+ value: "sum",
462
+ colorRange: ["#dbeafe", "#2563eb", "#172554"],
463
+ padding: 1.5,
464
+ showCount: true,
465
+ },
466
+ tooltip: (target, kind) => kind === "hex-bin"
467
+ ? `${target.markerCount} locations, ${target.value} total requests`
468
+ : target.name,
469
+ onClick: (target) => {
470
+ if (target.hexBin) console.log(target.markers);
471
+ },
472
+ });
473
+ ```
474
+
475
+ Each interactive bin returned to `tooltip`, `onHover`, and `onClick` contains
476
+ `{ hexBin: true, markerCount, count, value, markers, lat, lon }`. The default `value: "sum"` adds
477
+ each marker's `count`; use `value: "count"` to colour by the number of markers. Individual markers
478
+ are hidden by default while the layer is active. Set `hideMarkers: false` to keep them visible.
479
+
480
+ Available options are `radius`, `minValue`, `value`, `color`, `colorRange`, `opacity`, `stroke`,
481
+ `strokeWidth`, `padding`, `showCount`, `labelColor`, and `hideMarkers`. The same configuration works
482
+ in globe and flat-map modes. Since binning happens after projection, `radius` is measured in screen
483
+ pixels rather than geographic degrees.
484
+
485
+ ## Tile layers
486
+
487
+ CanvasGlobe does not contact a tile service by default. Set `tileLayer` only when a raster basemap
488
+ adds real value to the product. The same cached tile mosaic is reprojected for the globe,
489
+ equirectangular, Mercator, and Natural Earth views.
490
+
491
+ ```js
492
+ const globe = createGlobe(canvas, {
493
+ tileLayer: {
494
+ url: "https://tiles.example.com/{z}/{x}/{y}.png",
495
+ zoom: 2,
496
+ attribution: "Map data and imagery: Example Maps",
497
+ onError(error, tile) {
498
+ console.warn("Tile unavailable", tile, error);
499
+ },
500
+ },
501
+ });
502
+ ```
503
+
504
+ `url` accepts `{z}`, `{x}`, `{y}`, and `{-y}` placeholders. For private, signed, generated, or
505
+ offline imagery, use `getTile({ x, y, z })` and return a URL, canvas, image, bitmap, or a Promise of
506
+ one. Loaded XYZ coordinates are cached for the lifetime of the layer, and failed tiles leave the
507
+ built-in CanvasGlobe background visible instead of breaking the render loop.
508
+
509
+ This is an overview layer, not a slippy-map engine. `zoom` defaults to 2. The default `maxTiles: 64`
510
+ allows up to zoom 3 and prevents a configuration mistake from starting hundreds of requests. Raise
511
+ the ceiling explicitly only when the provider terms, page weight, and device budget allow it.
512
+
513
+ Always supply the attribution required by your provider. CanvasGlobe paints it into the canvas, so
514
+ it remains present in image and video exports. Remote images use anonymous CORS by default because
515
+ globe reprojection and export must read their pixels. The provider must return a compatible
516
+ `Access-Control-Allow-Origin` header. You may set `crossOrigin: null`, but a cross-origin response
517
+ without CORS cannot be reprojected or exported. You are responsible for the provider's licence,
518
+ usage policy, access token, and request limits.
519
+
446
520
  ### How accurate is marker placement?
447
521
 
448
522
  The projection maths is exact: a marker's pixel position matches the closed-form projection to
package/codemeta.json CHANGED
@@ -3,9 +3,9 @@
3
3
  "@type": "SoftwareSourceCode",
4
4
  "name": "CanvasGlobe",
5
5
  "identifier": "canvas-globe",
6
- "description": "CanvasGlobe helps developers add interactive 3D globes and flat world maps to JavaScript and React apps with Canvas 2D, without WebGL, map tiles, API keys, or runtime network calls.",
6
+ "description": "CanvasGlobe helps developers add interactive 3D globes and flat world maps to JavaScript and React apps with Canvas 2D, without WebGL, required map tiles, API keys, or required runtime network calls.",
7
7
  "url": "https://canvasglobe.swiftools.com/",
8
- "version": "1.2.0",
8
+ "version": "1.4.0",
9
9
  "codeRepository": "https://github.com/Shree-hari/canvas-globe",
10
10
  "issueTracker": "https://github.com/Shree-hari/canvas-globe/issues",
11
11
  "downloadUrl": "https://www.npmjs.com/package/canvas-globe",
@@ -112,8 +112,8 @@
112
112
  { "name": "lat-range", "description": "Serialized latitude range.", "type": { "text": "[number, number]" } }
113
113
  ],
114
114
  "events": [
115
- { "name": "geo-hover", "description": "Fires when the hovered marker changes.", "type": { "text": "CustomEvent<{ marker: Marker | ClusterMarker | null; pos: { x: number; y: number } | null }>" } },
116
- { "name": "geo-click", "description": "Fires when a marker is clicked.", "type": { "text": "CustomEvent<{ marker: Marker | ClusterMarker; pos: { x: number; y: number } }>" } },
115
+ { "name": "geo-hover", "description": "Fires when the hovered marker changes.", "type": { "text": "CustomEvent<{ marker: Marker | ClusterMarker | HexBinMarker | null; pos: { x: number; y: number } | null }>" } },
116
+ { "name": "geo-click", "description": "Fires when a marker is clicked.", "type": { "text": "CustomEvent<{ marker: Marker | ClusterMarker | HexBinMarker; pos: { x: number; y: number } }>" } },
117
117
  { "name": "geo-country-hover", "description": "Fires when the hovered country changes.", "type": { "text": "CustomEvent<{ country: CountryShape | null; pos: { x: number; y: number } | null }>" } },
118
118
  { "name": "geo-country-click", "description": "Fires when a country is clicked.", "type": { "text": "CustomEvent<{ country: CountryShape; pos: { x: number; y: number } }>" } },
119
119
  { "name": "geo-render", "description": "Fires after CanvasGlobe renders a frame.", "type": { "text": "CustomEvent<{ globe: GeoGlobe }>" } }