Pattern & Image Fills

Premium feature

Pattern & image fills is a Premium feature

Available on the Premium and OEM plans. Patterns (fill.pattern) and imagery (fill.image) are two separate licensed features. Both work without a key for evaluation, with a watermark on the map; call ApexMaps.setLicense(key) to remove it.

A flat fill is one color per feature, and there are three places that is not enough: print and photocopy collapse a five-step ramp to about three, two classes of a diverging ramp can read as one to a reader with a color-vision deficiency, and when the classes are kinds rather than amounts a sequential ramp asserts an order that is not in the data. fill.pattern adds a second channel over the color the scale already resolved, rather than replacing it. fill.image clips a picture to each region's own outline.

Pattern fills

series: [
  {
    type: 'choropleth',
    data,
    scale: { type: 'quantile', classes: 5 },
    fill: { pattern: { type: 'dots', size: 10 } },
  },
]

Eight tiles via type: dots, squares, checks (filled), lines, grid, diagonal, crosshatch (stroked at strokeWidth), and custom with your own path drawn in a size by size box.

OptionDefaultWhat it does
type'dots'One of the eight tiles above
size10Spacing between marks, in screen pixels, not the size of one mark
colorautoInk color. Defaults to white on a dark background, a darkened tint otherwise
backgroundclass colorTile background. Defaults to the color the scale resolved
strokeWidthsize / 5Ink weight for the line tiles
angle0Rotate the tile, in degrees
opacity1Ink opacity
pathTile geometry for type: 'custom', drawn in a size by size box

A decision per feature

Pass a function to vary the tile per feature, which is the form the qualitative case wants. It is handed the color the scale resolved, so a tile can tint itself from the data instead of being told a color twice:

fill: {
  pattern: ({ classIndex, color, value, datum, key, name, properties }) => ({
    type: TILES[classIndex],
    angle: ANGLES[classIndex],
  }),
}

Returning null leaves that feature on its flat color.

What it does without being configured

  • The ink picks itself. color defaults to white on a background dark enough to carry it, and to a darkened tint of the background otherwise, so a whole sequential ramp stays legible without a per-class review.
  • The tile background defaults to the class color, so texture is added to the encoding rather than substituted for it.
  • size is the spacing between marks, not the size of one, and the marks are deliberately small against it (a dot covers about a twelfth of its tile). Tighten it and the ink averages with the fill into a shade that is on no scale, so a reader matching a region to a swatch matches a color the legend never shows.
  • The texture holds its size on screen. A tile is rescaled by the inverse of the camera each frame, on the same reasoning that gives borders non-scaling-stroke, so an untouched pattern does not grow with the zoom.
  • Legend swatches draw the class's real tile, off the same builder as the map, so a patterned map does not tell the reader through plain swatches that the texture means nothing.
  • No-data and legend-muted features are never textured. An absence has to keep reading as an absence.

Image fills

fill.image clips a picture to each region's own outline. The function form of src is the useful one: it is how each region gets its own image, a flag, a crest, a satellite tile, a portrait.

series: [
  {
    type: 'choropleth',
    data,
    fill: {
      image: {
        src: ({ key, name, value, datum }) => `/scenes/${key.toLowerCase()}.jpg`,
        fit: 'cover', // 'contain' fits it all in; 'fill' stretches
        background: '#eef1f6', // shows under a 'contain' fit, and while loading
      },
    },
  },
]
OptionDefaultWhat it does
srcImage URL, or a function run per feature. Return null to leave that feature on its flat color
fit'cover''cover' fills the box and crops; 'contain' fits the whole image in; 'fill' stretches. The outline clips it either way
backgroundflat colorPainted under the image: shows through a 'contain' fit and while loading
opacity1Image opacity

fit: 'cover' computes its crop from the source's measured aspect ratio rather than from preserveAspectRatio, because a referenced SVG (the most likely source for a per-region flag or crest) brings its own aspect handling that takes precedence per spec and would otherwise letterbox inside the region.

When a paint replaces a color

SVG paints texture and imagery by reference, so the fill on a painted feature is no longer a color string. ApexMaps writes the flat color alongside on data-fill, and hover on a painted feature dims with a brightness() filter rather than by darkening the fill, because the pattern behind it is shared by its whole class and rewriting it would highlight every feature in that class at once. Everything that reasoned about a flat color, a drilldown developing the child level, an export, keeps working.