# 扩展:图表/交互式图表教程

This page is a translated version of the page Extension:Graph/Interactive Graph Tutorial and the translation is 5% complete.
Other languages:
English • ‎Ripoarisch • ‎Türkçe • ‎español • ‎français • ‎polski • ‎中文 • ‎日本語

In this tutorial we will create an interactive graph that will display historical fertility rates per country, with a slider to pick the year, and a map to show rate distribution around the world. If you want to investigate the source code of this graph only, it's available as a separate wiki page. Use Interactive Graph Sandbox for experimentations. You might also be interested in the complete Vega documentation.

## Drawing Shapes

We start with drawing a few elements (marks). The graph code is surrounded by the `<graph mode=interactive>...</graph>` tag, even though the graph is not yet interactive. Open Interactive Graph Sandbox and copy the following code without the <graph> tag to experiment.

Hint: Use "edit source" to copy the graph spec to the Vega Editor for experimentation.

```{
// We want to use Vega 2, and specify image size
"version": 2, "width": 300, "height": 80,
// Set padding to the same value on all sides
// By default the background is transparent
"background": "#edf1f7",
"marks": [
{
// Draw a horizontal line
"name": "scrollLine",
"type": "rule",
"properties": {
"enter": {
"x": {"value": 0},
"y": {"value": 40},
"x2": {"value": 300},
"stroke": {"value": "#000"},
"strokeWidth": {"value": 2}
}
}
},
{
// Draw a triangle shape with a hover effect
// naming objects allows us to reference them later
"name": "handle",
"type": "path",
"properties": {
"enter": {
"x": {"value": 200},
"y": {"value": 40},
// path syntax is the same as SVG's path tag
"path": {"value": "m-5.5,-10l0,20l11.5,-10l-11.5,-10z"},
"stroke": {"value": "#880"},
"strokeWidth": {"value": 2.5}
},
"update": {"fill": {"value": "#fff"}},
// Change fill color of the object on mouse hover
"hover": {"fill": {"value": "#f00"}}
}
}
]
}
```

## IsDragging signal

To make our handle object dragable, we first need to figure out if it was clicked or not. For that, lets add a signal that becomes true when an object is clicked (isDragging), and a text mark to show (debug) the result:

```"signals": [
{
"name": "isDragging",
"init": false,
"streams": [
{"type": "@handle:mousedown","expr": "true"},
{"type": "mouseup","expr": "false"}
]
},
],

// in "marks"
{
"name": "debugIsDragging",
"type": "text",
"properties": {
"enter": {
"x": {"value": 250},
"y": {"value": 0},
"fill": {"value": "black"}
},
"update": {"text": {"signal": "isDragging"}}
}
}
```

## Handle Position signal

Now that we know when the object is being dragged, we add a `mousemove` signal that only changes its value when isDragging signal is true. We also attach the new signal to the handle's "x" coordinate via "update" section:

```// in "signals"
{
"name": "handlePosition",
"init": 200,
"streams": [
{
"type": "mousemove[isDragging]",
"expr": "eventX()"
}
]
}

{
"name": "handle",
...
"update": {
"x": {"signal": "handlePosition"},
...
},
},
{
"name": "debugHandlePosition",
"type": "text",
"properties": {
"enter": {
"x": {"value": 250},
"y": {"value": 14},
"fill": {"value": "black"}
},
"update": {"text": {"signal": "handlePosition"}}
}
}
```

## Scaling Handle Position signal

Having pixel position of the handler is not very good - we would much rather have a position that is meaningful to our graph, e.g. a year. Vega scales provide a useful mechanism for converting between our data (e.g. years) and the screen coordinates, and back (invert). In this step, we add "yearsScale" linear scale for values 1960..2013, mapping it to the whole width of the graph (excluding padding). We also add a new scaledHandlePosition signal that translates from the mouse X position to the meaningful value in years.

```// add "scales" root value:
"scales": [
{
"type": "linear",
"zero": false,
"domain": [1960, 2013],
"range": "width"
}
],

{
"name": "scaledHandlePosition",
"expr": "handlePosition",
}

{
"name": "debugScaledHandlePosition",
"type": "text",
"properties": {
"enter": {
"x": {"value": 250},
"y": {"value": 28},
"fill": {"value": "black"}
},
"update": {"text": {"signal": "scaledHandlePosition"}}
}
}
```

## Year value cleanup

As you might have noticed, the handle could be moved beyond the length of our graph, producing incorrect results. Also, the scaled value is not an integer that we expect of the year value. To fix this, we can introduce one more post-processing signal called "currentYear" to convert it to an integer and limit it to the needed range. We also initialize it to a reasonable value, and we tie both the "yearLabel" and the position of the handle bar back to this value. Note that the handle's position needs to be in screen coordinates, so we have to use the "yearsScale" to convert the value back:

```    // New signal:
{
"name": "currentYear",
"init": 2000,
"expr": "clamp(parseInt(scaledHandlePosition),1960,2013)"
}

// Update yearLabel mark:
// New mark to draw the year
{
"name": "yearLabel",
"type": "text",
"properties": {
"enter": {
"x": {"value": 0},
"y": {"value": 25},
"fontSize": {"value": 32},
"fontWeight": {"value": "bold"},
"fill": {"value": "steelblue"}
},
"update": {"text": {"signal": "currentYear"}}
}
},

// Update handle mark:
{
"name": "handle",
"properties": {
"update": {
},
}
}
```

## Final cleanup

Now we can remove all the debugging marks. We also don't need separate handlePosition and scaledHandlePosition signals because scaling can happen in the same step:

```{
// We want to use Vega 2, and specify image size
"version": 2, "width": 300, "height": 80,
// Set padding to the same value on all sides
// By default the background is transparent
"background": "#edf1f7",

"signals": [
{
"name": "isDragging",
"init": false,
"streams": [
{"type": "@handle:mousedown","expr": "true"},
{"type": "mouseup","expr": "false"}
]
},
{
"name": "scaledHandlePosition",
"streams": [
{
"type": "mousemove[isDragging]",
"expr": "eventX()",
}
]
},
{
"name": "currentYear",
"init": 2000,
"expr": "clamp(parseInt(scaledHandlePosition),1960,2013)"
}
],

"scales": [
{
"type": "linear",
"zero": false,
"domain": [1960, 2013],
"range": "width"
}
],

"marks": [
{
// draw the year label in the upper left corner
"name": "yearLabel",
"type": "text",
"properties": {
"enter": {
"x": {"value": 0},
"y": {"value": 25},
"fontSize": {"value": 32},
"fontWeight": {"value": "bold"},
"fill": {"value": "steelblue"}
},
"update": {"text": {"signal": "currentYear"} }
}
},
{
// Draw a horizontal line
"name": "scrollLine",
"type": "rule",
"properties": {
"enter": {
"x": {"value": 0},
"y": {"value": 40},
"x2": {"value": 300},
"stroke": {"value": "#000"},
"strokeWidth": {"value": 2}
}
}
},
{
// Draw a triangle shape with a hover effect
// naming objects allows us to reference them later
"name": "handle",
"type": "path",
"properties": {
"enter": {
"y": {"value": 40},
// path syntax is the same as SVG's path tag
"path": {"value": "m-5.5,-10l0,20l11.5,-10l-11.5,-10z"},
"stroke": {"value": "#880"},
"strokeWidth": {"value": 2.5}
},
"update": {