From the API reference for def:
def(name, value): Defines a local variable on this mark. Local variables are initialized once per mark (i.e., per parent panel instance), and can be used to store local state for the mark. Here are a few reasons you might want to use def:
1. To store local state. For example, say you were visualizing employment statistics, and your root panel had an array of occupations. In a child panel, you might want to initialize a local scale, and reference it from a property function:
.def("y", function(d) pv.Scale.linear(0, pv.max(d.values)).range(0, h))
.height(function(d) this.y()(d))
In this example, this.y() returns the defined local scale. We then invoke the scale function, passing in the datum, to compute the height. Note that defs are similar to fixed properties: they are only evaluated once per parent panel, and this.y() returns a function, rather than automatically evaluating this function as a property.
2. To store temporary state for interaction. Say you have an array of bars, and you want to color the bar differently if the mouse is over it. Use def to define a local variable, and event handlers to override this variable interactively:
.def("i", -1)
.event("mouseover", function() this.i(this.index))
.event("mouseout", function() this.i(-1))
.fillStyle(function() this.i() == this.index ? "red" : "blue")
Notice that this.i() can be used both to set the value of i (when an argument is specified), and to get the value of i (when no arguments are specified). In this way, it's like other property methods.
3. To specify fixed properties efficiently. Sometimes, the value of a property may be locally a constant, but dependent on parent panel data which is variable. In this scenario, you can use def to define a property; it will only get computed once per mark, rather than once per datum.
new pv.Panel()
.width(150)
.height(150)
.add(pv.Bar)
.def("i", -1)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.bottom(0)
.width(20)
.height(function(d) d * 80)
.left(function() this.index * 25)
.fillStyle(function() this.i() == this.index ? "red" : "black")
.event("mouseover", function() this.i(this.index))
.event("mouseout", function() this.i(-1))
.title(function() this.index)
.root.render();
|
In this example, the local variable, or “def”, is named i. It stores the index of the mark under the mouse. Breaking it down:
When you declare a def, a method gets added to the mark, much like a property. It behaves similarly to property methods: calling the method with no arguments (in this example, this.i()) _gets_ its value, while calling the method with an argument _sets_ the value.
For the purpose of comparison, what would happen without a def?
new pv.Panel()
.width(150)
.height(150)
.add(pv.Bar)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.bottom(0)
.width(20)
.height(function(d) d * 80)
.left(function() this.index * 25)
.fillStyle("black")
.event("mouseover", function() this.fillStyle("red"))
.event("mouseout", function() this.fillStyle("black"))
.title(function() this.index)
.root.render();
|
There’s another trick you can use to achieve this recoloring effect. But, it is strongly discouraged because it takes advantage of how the rendering system currently detects which part of the tree to rebuild. It is included for explanatory purposes but this is an anti-pattern not to be copied.
The trick is make the bar a singleton, and replicate a new parent panel instead:
new pv.Panel()
.width(150)
.height(150)
.add(pv.Panel)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.left(function() this.index * 25)
.add(pv.Bar)
.bottom(0)
.width(20)
.height(function(d) d * 80)
.fillStyle("black")
.event("mouseover", function() this.fillStyle("red")) // DANGER!
.event("mouseout", function() this.fillStyle("black")) // DANGER!
.title(function() this.index)
.root.render();
|
It works because the event handler returns a reference to the bar whose fill style was changed (but not the parent panel). That bar is then redrawn, but—importantly—the other copies of that bar in other panels are not redrawn. If the rendering engine knew all of the dependencies between properties, and could be smarter about which part of the tree to re-evaluate, this hack would break. So don't do it.
Yet another reason to avoid this strategy is that it’s more stateful and less declarative. For example, if you wanted to use this hack to change multiple properties on mouseover, you have to be careful to exactly undo those changes on mouseout. On the other hand, if you use a local variable (such as i, above) to store the index of the active mark, you can define all the properties declaratively, and there's never an issue of crufty state.
new pv.Panel()
.width(150)
.height(150)
.add(pv.Panel)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.left(function() this.index * 25)
.add(pv.Bar)
.bottom(0)
.width(20)
.height(function(d) d * 80)
.def("fillStyle", "black")
.event("mouseover", function() this.fillStyle("red")) // override
.event("mouseout", function() this.fillStyle(undefined)) // restore
.title(function() this.index)
.root.render();
|