Polygon Mark

The vgg-polygon mark is used to plot polygonal elements.

<vgg-polygon
  :points="points"
  fill="008080"
  stroke="none"
/>

Properties

Positioning

Prop Required Types Default Description Unit(s)
x depends Array undefined Array of x-coordinates for each polygon point Local coordinates
y depends Array undefined Array of y-coordinates for each polygon point Local coordinates
points depends Array undefined Array of coordinate pairs [x, y] for each polygon point Local coordinates
geometry depends Object undefined GeoJSON object of type Polygon or MultiPolygon Local coordinates

Other aesthetics

Prop Required Types Default Description Unit(s)
stroke false String undefined Stroke color Named color, hex, rgb, hsl
stroke-width false Number undefined Stroke width Screen pixel
stroke-opacity false Number undefined Stroke opacity Number between 0 to 1
fill false String '#000000' Fill color Named color, hex, rgb, hsl
fill-opacity false Number undefined Fill opacity Number between 0 and 1
opacity false Number undefined Mark opacity Number between 0 and 1

These are analogous to the CSS properties of the same names.

Events

Event Description
click Triggered when user clicks on mark
hover Triggered when user hovers over mark
mouseover Triggered when user's mouse is above mark
mouseout Triggered when user's mouse leaves mark
select Triggered when mark is selected
deselect Triggered when mark is removed from selection

For more information on these events, see the Interactivity documentation.

Usage

Positioning

To render the Polygon mark, you will need to provide one of the following props:

  • x and y or
  • points or
  • geometry

geometry accepts GeoJSON Polygon, MultiPolygon, LineString and MultiLineString objects only. To render other geometry types, see the overview on Geo marks.

Data is passed to the x, y and geometry props via row mapping, which renders one mark per data row. For a more in-depth explanation on how mapping works, see the Map section under Core components.

points may not be used with vgg-map and can only be scaled with the scale-x and scale-y props of its parent Section.

Example

The following graphic shows the total attention on Twitter paid to the fashion industry across the African continent.

Geotagged tweets are aggregated into hexagonal bins and their absolute counts recorded in a csv.

hex,total
ID621,0
ID622,0
ID649,0
ID820,9721
ID821,128
ID822,1725
ID823,1489
...

Each hexagonal bin is defined by a GeoJSON Polygon or Multipolygon object. Shown here is bin ID621 which has a total count of 0 (see corresponding hex in csv above).

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              2971330.946649554,
              -5548087.999411765
            ],
            [
              2975532.7286153506,
              -5550513.899360795
            ],
            [
              2971511.3663789546,
              -5550055.0168350125
            ],
            [
              2971330.946649554,
              -5548087.999411765
            ]
          ]
        ]
      },
      "properties": {
        "hex": "ID621"
      }
    }
    ...
  ]
}

To load the data, we will use d3.csv and d3.json from the d3-fetch module. The geo function defined below returns a Promise object containing both the attributes (defined in the csv) and the geometries (defined in the json), which is then exported for use in a Vue component.

import { csv, json } from 'd3-fetch'

export function geo () {
  let hexAttr = '/africa.csv'
  let hexGeom = '/hex-africa.json'

  let urls = [hexAttr, hexGeom]
  let promises = []

  urls.forEach(url => {
    let ext = url.replace(/^.*\./, '')

    return ext === 'csv' ? promises.push(csv(url))
      : ext === 'json' ? promises.push(json(url))
        : 'unsupported file type'
  })

  return Promise.all(promises).then(values => {
    let attr = Object.freeze(values[0].map(d => {
      return {
        hex: d.hex,
        value: +d['total'] // the 'total' column is here mutated to 'value'
      }
    }))

    let geom = Object.freeze(Object.assign({}, values[1]))

    return { attr: attr, geom: geom }
  })
}

In a Vue component, the geo function above is imported and the Promise object containing our attributes and geometries is loaded within the loadData method. Using a join utility, attribute data is merged into the properties section of the geometries and passed to the Vue component's data prop.

<script>
import { geo } from './geoData.js' // imports function containing the Promise object
import { equijoin } from './geoData.js' // imports the join utility

export default {
  name: 'GeoShape',

  data () {
    return {
      data: {} // result of the join is passed here
    }
  },

  mounted () {
    this.loadData()
  },

  methods: {
    loadData () {
      geo().then(data => {
        // joins attributes to geometries on the 'hex' column of each table
        let joinedData = equijoin(data.attr, data.geom, 'hex', 'hex',
          (attr, feat) => {
            let featCopy = JSON.parse(JSON.stringify(feat))

            // select attributes to retain in output
            featCopy.properties.value = attr.value
            return featCopy
          }
        )
        this.data = Object.freeze(joinedData)
      })
    }
  }
}
</script>

As a result of the join, the geodata acquires a new property value (total count).

Before

geometry hex
geometry Object 'ID621'
... ...

After

geometry hex value
geometry Object 'ID621' 0
... ... ...

Finally, we define the mark using the vgg-polygon component. The color of the bins is scaled by the 'value' column according to the blues scheme. Domain is capped at 5000, where NA: 1 indicates that values greater than 5000 are mapped to the range's upper bound of 1.

<vgg-map v-slot="{ row }">
  <vgg-polygon
    :geometry="row.geometry"
    :fill="{
      val: row.value,
      scale: { type: 'blues', domain: 'value', domainMax: 5000, NA: 1 }
    }"
  />
</vgg-map>

Shown below is the Vue component in its entirety:

<template>

  <vgg-graphic
    v-if="dataLoaded"
    :width="600"
    :height="600"
    :data="data"
    :transform="{
      reproject: {
        from: '+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs',
        to: 'WGS84'
      }
    }"
  >

    <vgg-section
      :x1="0"
      :x2="500"
      :y1="0"
      :y2="500"
      :scale-geo="{}"
    >

      <vgg-map v-slot="{ row }">

        <vgg-polygon
          :geometry="row.geometry"
          :fill="{
            val: row.value,
            scale: { type: 'blues', domain: 'value', domainMax: 5000, NA: 1 }
          }"
          :stroke="'#d3d3d3'"
          :stroke-width="0.2"
        />

      </vgg-map>

    </vgg-section>

  </vgg-graphic>

</template>

<script>
import { geo, equijoin } from './geoData.js'

export default {
  name: 'GeoShape',

  data () {
    return {
      data: {}
    }
  },

  computed: {
    dataLoaded () {
      return this.data && Object.keys(this.data).length !== 0
    }
  },

  mounted () {
    this.loadData()
  },

  methods: {
    loadData () {
      geo().then(data => {
        let joinedData = equijoin(data.attr, data.geom, 'hex', 'hex',
          (attr, feat) => {
            let featCopy = JSON.parse(JSON.stringify(feat))

            // select attributes to retain in output
            featCopy.properties.value = attr.value
            return featCopy
          }
        )
        this.data = Object.freeze(joinedData)
      })
    }
  }
}
</script>