Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-reference/extensions/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This module contains the following extensions:
- [Fp64Extension](./fp64-extension.md)
- [MaskExtension](./mask-extension.md)
- [PathStyleExtension](./path-style-extension.md)
- [StrokeStyleExtension](./stroke-style-extension.md)
- [TerrainExtension](./terrain-extension.md)

For instructions on authoring your own layer extensions, visit [developer guide](../../developer-guide/custom-layers/layer-extensions.md).
Expand Down
156 changes: 156 additions & 0 deletions docs/api-reference/extensions/stroke-style-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@

# StrokeStyleExtension

The `StrokeStyleExtension` adds the capability to render dashed strokes on layers that use SDF-based stroke rendering. It supports:

- [ScatterplotLayer](../layers/scatterplot-layer.md) - dashed circle strokes
- TextBackgroundLayer (used internally by [TextLayer](../layers/text-layer.md)) - dashed rectangle strokes (including rounded corners)

```js
import {ScatterplotLayer} from '@deck.gl/layers';
import {StrokeStyleExtension} from '@deck.gl/extensions';

const layer = new ScatterplotLayer({
id: 'scatterplot-layer',
data,
stroked: true,
filled: true,
getPosition: d => d.coordinates,
getRadius: d => d.radius,
getFillColor: [255, 200, 0],
getLineColor: [0, 0, 0],
getDashArray: [3, 2],
dashGapPickable: false,
extensions: [new StrokeStyleExtension({dash: true})]
});
```

## Installation

To install the dependencies from NPM:

```bash
npm install deck.gl
# or
npm install @deck.gl/core @deck.gl/layers @deck.gl/extensions
```

```js
import {StrokeStyleExtension} from '@deck.gl/extensions';
new StrokeStyleExtension({dash: true});
```

To use pre-bundled scripts:

```html
<script src="https://unpkg.com/deck.gl@^9.0.0/dist.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/@deck.gl/core@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/extensions@^9.0.0/dist.min.js"></script>
```

```js
new deck.StrokeStyleExtension({dash: true});
```

## Constructor

```js
new StrokeStyleExtension({dash});
```

* `dash` (boolean) - add capability to render dashed strokes. Default `true`.

## Layer Properties

When added to a layer via the `extensions` prop, the `StrokeStyleExtension` adds the following properties to the layer:

#### `getDashArray` ([Accessor&lt;number[2]&gt;](../../developer-guide/using-layers.md#accessors)) {#getdasharray}

The dash array to draw each stroke with: `[dashSize, gapSize]` relative to the width of the stroke.

* If an array is provided, it is used as the dash array for all objects.
* If a function is provided, it is called on each object to retrieve its dash array. Return `[0, 0]` to draw the stroke as a solid line.
* Default: `[0, 0]` (solid line)

#### `dashGapPickable` (boolean, optional) {#dashgappickable}

* Default: `false`

Only effective if `getDashArray` is specified. If `true`, gaps between solid strokes are pickable. If `false`, only the solid strokes are pickable.

## Supported Layers

### ScatterplotLayer

For `ScatterplotLayer`, the dash pattern is calculated based on the **angle around the circle's circumference**. The stroke must be enabled via `stroked: true`.

```js
new ScatterplotLayer({
data,
stroked: true,
filled: true,
getPosition: d => d.coordinates,
getRadius: 100,
getLineColor: [0, 0, 0],
getLineWidth: 2,
getDashArray: [3, 2], // 3 units solid, 2 units gap
extensions: [new StrokeStyleExtension({dash: true})]
});
```

When a circle is both stroked and filled, the fill color shows through the gaps in the dash pattern. When only stroked (no fill), fragments in gaps are discarded.

### TextBackgroundLayer

For `TextBackgroundLayer`, the dash pattern is calculated based on the **perimeter position around the rectangle**. This includes proper handling of rounded corners when `borderRadius` is set.

```js
new TextBackgroundLayer({
data,
getPosition: d => d.coordinates,
getBoundingRect: d => d.bounds,
getLineColor: [0, 0, 0],
getLineWidth: 2,
borderRadius: 8, // Dashes will flow smoothly around corners
getDashArray: [4, 2],
extensions: [new StrokeStyleExtension({dash: true})]
});
```

The perimeter calculation accounts for:
- Straight edges (reduced by corner radii)
- Corner arcs (quarter circles based on border radius)
- Different radii for each corner (when `borderRadius` is specified as `[topRight, bottomRight, bottomLeft, topLeft]`)

## Comparison with PathStyleExtension

| Feature | PathStyleExtension | StrokeStyleExtension |
|---------|-------------------|---------------------|
| **Target Layers** | PathLayer, PolygonLayer, GeoJsonLayer | ScatterplotLayer, TextBackgroundLayer |
| **getDashArray** | ✓ | ✓ |
| **dashGapPickable** | ✓ | ✓ |
| **getOffset** | ✓ | ✗ |
| **dashJustified** | ✓ | ✗ |
| **highPrecisionDash** | ✓ | ✗ |
| **Rendering Method** | Path geometry extrusion | SDF (Signed Distance Field) |

Use `PathStyleExtension` for path-based layers and `StrokeStyleExtension` for SDF-based layers.

## Remarks

### How It Works

Unlike `PathStyleExtension` which works with path geometry, `StrokeStyleExtension` works with layers that render strokes using Signed Distance Fields (SDF) in the fragment shader:

- **ScatterplotLayer**: Uses angle-based position calculation. The position along the stroke is determined by the angle from the circle's center (0 to 2π radians).
- **TextBackgroundLayer**: Uses perimeter-based position calculation. The position is traced clockwise around the rectangle's perimeter, including arc lengths for rounded corners.

### Performance

The extension uses shader injection to add dash calculations. Each supported layer type receives only its specific shader code at compile time, so there is no runtime overhead from supporting multiple layer types.

## Source

[modules/extensions/src/stroke-style](https://github.com/visgl/deck.gl/tree/master/modules/extensions/src/stroke-style)
1 change: 1 addition & 0 deletions docs/table-of-contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@
"api-reference/extensions/fp64-extension",
"api-reference/extensions/mask-extension",
"api-reference/extensions/path-style-extension",
"api-reference/extensions/stroke-style-extension",
"api-reference/extensions/terrain-extension"
]
},
Expand Down
154 changes: 154 additions & 0 deletions examples/stroke-style-test/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Deck} from '@deck.gl/core';
import {ScatterplotLayer, _TextBackgroundLayer as TextBackgroundLayer} from '@deck.gl/layers';
import {StrokeStyleExtension} from '@deck.gl/extensions';

const INITIAL_VIEW_STATE = {
latitude: 37.78,
longitude: -122.4,
zoom: 12,
bearing: 0,
pitch: 0
};

// Sample data for ScatterplotLayer - circles at different positions
const SCATTERPLOT_DATA = [
// Dashed stroke, filled
{
position: [-122.41, 37.79],
radius: 300,
dashArray: [3, 2],
fillColor: [255, 200, 0, 180],
lineColor: [0, 100, 200]
},
{
position: [-122.4, 37.79],
radius: 250,
dashArray: [5, 2],
fillColor: [100, 255, 100, 180],
lineColor: [0, 0, 0]
},
{
position: [-122.39, 37.79],
radius: 200,
dashArray: [2, 1],
fillColor: [255, 100, 100, 180],
lineColor: [50, 50, 150]
},
// Solid stroke for comparison
{
position: [-122.38, 37.79],
radius: 200,
dashArray: [0, 0],
fillColor: [200, 200, 255, 180],
lineColor: [100, 0, 100]
},
// Dashed stroke, stroked only (no fill)
{
position: [-122.41, 37.78],
radius: 300,
dashArray: [4, 3],
fillColor: [0, 0, 0, 0],
lineColor: [255, 0, 0]
},
{
position: [-122.4, 37.78],
radius: 250,
dashArray: [6, 2],
fillColor: [0, 0, 0, 0],
lineColor: [0, 150, 0]
}
];

// Sample data for TextBackgroundLayer - rectangles at different positions
const TEXT_BG_DATA = [
// Sharp corners
{
position: [-122.41, 37.77],
bounds: [-60, -20, 60, 20],
borderRadius: 0,
dashArray: [4, 2],
lineColor: [0, 0, 0]
},
// Rounded corners
{
position: [-122.4, 37.77],
bounds: [-50, -25, 50, 25],
borderRadius: 8,
dashArray: [3, 2],
lineColor: [0, 100, 200]
},
// Very rounded corners
{
position: [-122.39, 37.77],
bounds: [-40, -20, 40, 20],
borderRadius: 15,
dashArray: [5, 3],
lineColor: [200, 0, 100]
},
// Solid stroke for comparison
{
position: [-122.38, 37.77],
bounds: [-40, -20, 40, 20],
borderRadius: 8,
dashArray: [0, 0],
lineColor: [100, 100, 100]
},
// Different aspect ratios
{
position: [-122.41, 37.76],
bounds: [-80, -15, 80, 15],
borderRadius: 10,
dashArray: [6, 2],
lineColor: [50, 100, 50]
},
{
position: [-122.39, 37.76],
bounds: [-30, -40, 30, 40],
borderRadius: 12,
dashArray: [3, 1],
lineColor: [100, 50, 150]
}
];

new Deck({
initialViewState: INITIAL_VIEW_STATE,
controller: true,
layers: [
// ScatterplotLayer with dashed strokes
new ScatterplotLayer({
id: 'scatterplot-dashed',
data: SCATTERPLOT_DATA,
getPosition: d => d.position,
getRadius: d => d.radius,
getFillColor: d => d.fillColor,
getLineColor: d => d.lineColor,
getDashArray: d => d.dashArray,
stroked: true,
filled: true,
lineWidthMinPixels: 4,
pickable: true,
autoHighlight: true,
extensions: [new StrokeStyleExtension({dash: true})]
}),

// TextBackgroundLayer with dashed strokes
new TextBackgroundLayer({
id: 'text-bg-dashed',
data: TEXT_BG_DATA,
getPosition: d => d.position,
getBoundingRect: d => d.bounds,
getBorderRadius: d => d.borderRadius,
getLineColor: d => d.lineColor,
getFillColor: [255, 255, 255, 200],
getLineWidth: 2,
getDashArray: d => d.dashArray,
pickable: true,
autoHighlight: true,
extensions: [new StrokeStyleExtension({dash: true})]
})
]
});
13 changes: 13 additions & 0 deletions examples/stroke-style-test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>StrokeStyleExtension Test</title>
<style>
body {margin: 0; width: 100vw; height: 100vh; overflow: hidden;}
</style>
</head>
<body>
</body>
<script type="module" src="app.js"></script>
</html>
18 changes: 18 additions & 0 deletions examples/stroke-style-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "deckgl-example-stroke-style-test",
"version": "0.0.0",
"private": true,
"license": "MIT",
"scripts": {
"start": "vite",
"build": "vite build"
},
"dependencies": {
"@deck.gl/core": "^9.0.0",
"@deck.gl/layers": "^9.0.0",
"@deck.gl/extensions": "^9.0.0"
},
"devDependencies": {
"vite": "^4.0.0"
}
}
17 changes: 17 additions & 0 deletions examples/stroke-style-test/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {defineConfig} from 'vite';
import {resolve} from 'path';

const ROOT = resolve(__dirname, '../..');

export default defineConfig({
resolve: {
alias: {
'@deck.gl/core': resolve(ROOT, 'modules/core/src'),
'@deck.gl/layers': resolve(ROOT, 'modules/layers/src'),
'@deck.gl/extensions': resolve(ROOT, 'modules/extensions/src')
}
},
server: {
open: true
}
});
5 changes: 5 additions & 0 deletions modules/extensions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {default as BrushingExtension} from './brushing/brushing-extension';
export {default as DataFilterExtension} from './data-filter/data-filter-extension';
export {default as Fp64Extension} from './fp64/fp64-extension';
export {default as PathStyleExtension} from './path-style/path-style-extension';
export {default as StrokeStyleExtension} from './stroke-style/stroke-style-extension';
export {default as FillStyleExtension} from './fill-style/fill-style-extension';
export {default as ClipExtension} from './clip/clip-extension';
export {default as CollisionFilterExtension} from './collision-filter/collision-filter-extension';
Expand All @@ -25,6 +26,10 @@ export type {
PathStyleExtensionProps,
PathStyleExtensionOptions
} from './path-style/path-style-extension';
export type {
StrokeStyleExtensionProps,
StrokeStyleExtensionOptions
} from './stroke-style/stroke-style-extension';
export type {
FillStyleExtensionProps,
FillStyleExtensionOptions
Expand Down
Loading
Loading