Interactivity
Adding interactivity to graphics can greatly aid the discovery of patterns in data. Below, some ways to interact with graphics are described.
Hovering and clicking
The first and most straightforward way of interacting with data is clicking
or hovering over the marks that data has been mapped to. All marks, except for
the Label (for now), can be made interactive by adding a click
, hover
,
mouseover
or mouseout
event. Hovering or clicking is often used as a way
to request more (detailed) information about a certain data point. For example,
take this bar chart, where more information is shown about each fruit by hovering
over a bar:
<template>
<div>
<div v-if="hoverRow" style="float: right;">
<h3>{{ hoverRow.fruit }}</h3>
<h5>quantity: {{ hoverRow.quantity }}</h5>
<p>
{{ metadata.description[hoverRow.fruit] }}
</p>
</div>
<vgg-graphic
:width="300"
:height="300"
:data="{
fruit: ['apple', 'banana', 'coconut', 'durian'],
quantity: [5, 20, 10, 8]
}"
>
<vgg-section
:x1="0"
:x2="300"
:y1="0"
:y2="275"
:axes="{
right: { scale: { domain: 'quantity', domainMin: 0 } },
bottom: { scale: 'fruit' }
}"
>
<vgg-map v-slot="{ row, i }">
<vgg-rectangle
:x="{ val: row.fruit, scale: 'fruit' }"
:w="{ band: { domain: 'fruit', padding: 0.2 } }"
:y1="0"
:y2="{ val: row.quantity, scale: { domain: 'quantity', domainMin: 0 } }"
:fill="hoverI === i ? 'green' : 'black'"
@hover="handleHover($event, row, i)"
/>
</vgg-map>
</vgg-section>
</vgg-graphic>
</div>
</template>
<script>
export default {
data () {
return {
hoverI: null,
hoverRow: null,
metadata: {
description: {
apple: 'The natural enemy of the doctor',
banana: 'Watch out: slippery after consumption',
coconut: 'An indispensable ingredient in Indonesian cuisine',
durian: 'The rich person\'s jackfruit'
}
}
}
}
},
methods: {
handleHover (e, row, i) {
if (e) {
this.hoverI = i
this.hoverRow = row
} else {
this.hoverI = null
this.hoverRow = null
}
}
}
}
</script>
Selecting
If hovering and clicking are ways to interact with individual marks, selecting is a way to interact with multiple marks at the same time. To enable selections:
- Enable the desired selection tool on the appropriate Section
- Add listeners for
select
anddeselect
events on the desired marks - Synchronise the outlines (
selectionBounds
) of the selection tool with a polygon or other mark, to make the shape created with the selection tool visible.
Four selection tools are currently supported: the rectangle
, swipeX
, swipeY
,
and polygon
, where the swipeX
and swipeY
tools are just the rectangle with
respectively the y and x dimensions fixed to the entire span of the Section.
<template>
<vgg-graphic
:width="500"
:height="500"
:data="data"
>
<vgg-section
:x1="50"
:x2="450"
:y1="50"
:y2="450"
:axes="{
left: { scale: 'b' },
bottom: { scale: 'a' }
}"
:select="'rectangle'"
:selection-bounds.sync="selectionBounds"
>
<vgg-map v-slot="{ row, i }">
<vgg-point
:x="{ val: row.a, scale: 'a' }"
:y="{ val: row.b, scale: 'b' }"
:fill="selectedPoints[i] ? 'blue' : 'black'"
@select="handleSelect(i, true)"
@deselect="handleSelect(i, false)"
/>
</vgg-map>
</vgg-section>
<vgg-polygon
v-if="selectionBounds.length > 1"
:points="selectionBounds"
:fill="'red'"
:opacity="0.2"
/>
</vgg-graphic>
</template>
<script>
export default {
data () {
return {
selectionBounds: [],
selectedPoints: {},
data: {
a: new Array(50).fill(0).map((_, i) => Math.random() * i),
b: new Array(50).fill(0).map((_, i) => Math.random() * i)
}
}
},
methods: {
handleSelect (i, add) {
if (add) {
this.$set(this.selectedPoints, i, true)
} else {
this.$delete(this.selectedPoints, i)
}
}
}
}
</script>
Zooming
TODO
Brushing
TODO
A Caveat on Mapping
Generally, if rendering an additional mark when an interaction is performed, it is best to place the new mark component outside of any <vgg-map>
components.
This is because in most cases only a single new mark is needed for a single row of data, there is thus no need to map all the rows. Placing the new mark within the map component may cause significant lagging.
This is fast:
<vgg-map v-slot="{ row }">
<vgg-point
@hover="handleHover($event, row)"
:x="row[x]"
:y="row[y]"
:radius="3"
/>
</vgg-map>
<vgg-point
v-if="hoverRow"
:x="hoverRow[x]"
:y="hoverRow[y]"
fill="red"
:radius="3"
/>
But this is slow:
<vgg-map v-slot="{ row }">
<vgg-point
@hover="handleHover($event, row)"
:x="row[x]"
:y="row[y]"
:radius="3"
/>
<vgg-point
v-if="hoverRow"
:x="hoverRow[x]"
:y="hoverRow[y]"
fill="red"
:radius="3"
/>
</vgg-map>