Enhance your Dash visualizations by adding dynamic content that responds to hover interactions. This article demonstrates how to extract and display detailed information from hover events using Dash callbacks and pandas data manipulation.
Key Insights
- To show hover-based information in a Dash app, define a new HTML output area (such as a div with an ID like "info panel") and use Dash's callback mechanism to update this area based on hoverData from a graph component.
- Instead of extracting values directly from simple components like sliders or dropdowns, you access complex nested data in hoverData, such as the 'hovertext' and related data from a pandas DataFrame, using index access and methods like .squeeze() to simplify data structures.
- Noble Desktop’s example illustrates how to use hover interactions to display detailed car information, including manufacturer, model, horsepower, fuel efficiency, and engine size, by dynamically reading and formatting data in response to user cursor movements.
Note: These materials offer prospective students a preview of how our classes are structured. Students enrolled in this course will receive access to the full set of materials, including video lectures, project-based assignments, and instructor feedback.
So how do we begin to think about how we can make this change? Well, if we look at our page, what we're really talking about here is not a new interface element, but instead tying into something that Dash is already outputting, the hover information from each of these elements on our figure. What we need to define instead is an output area. Before the output area was always this graph, now we need to define a new output area.
So I'm gonna go back up to where we're defining our layout. I'm gonna add a new thing below our graph, and it is an HTML div, and we're gonna say its ID is info panel. This is just a name that we're giving to it so that we can tie into that later, just like we gave a name to the graph and to the slider and to the dropdown.
Now that we've got that, let's define a new callback. We'll say app.callback, and for app.callback, we define an output and an input. And our output is going to be to the info panel.
And what we're gonna output there to the info panel, you remember up here, we outputted to the graph. We said, hey, any change, anything this function returns goes as the new fe versus hp-graph element. And for that element, its figure property is what we were replacing.
Here, we're going to go to this area we defined, this div, the info panel, but we're going to go not for its figure. It doesn't have a figure. It doesn't have anything, in fact, but for its children property.
And children property just means the elements inside that element. We're gonna define each time in this function that this callback will decorate. We will define a function that returns a value and that value will be the new children each time for the info panel.
That's the output of this function. The input is going to be from the fe versus hp-graph. And previously, our input was the value property.
The engine size slider and for the manufacturer dropdown, the value property. What's the current value of that? But this, again, is something that the dash itself is handling. Dash itself is handling this interactivity of this hover information here.
And it's not of the value of the graph, the way these things are all sort of one-dimensional and have like a value. Instead, it's a property called hover data. Hover data, like so.
All right, so we're saying here, call my function when the fe versus hp-graph has hover data. And output my function's return value as the children of the info panel. That's what we're saying with these two arguments to app.callback. Okay, now we define a function.
We'll call it display hover info, but we'll use proper snake case, like good Pythonistas. And I'll call it hover data. There's nothing magical about that name.
In fact, just to make sure we understand that, I'm going to call this current hover data. Cursor data, sure. That's a name.
Okay, let's print just like we did before. Let's see what this thing is. And we'll return an HTML H1 saying hi.
Again, that means that this hi will get displayed as the children of the info panel. All right, I'm going to save this, and I'm moving my cursor around, and then I'm moving it over a spot, and I'm moving in a couple more spots. Let's see what got printed out.
Whoa, an awful lot. Now, this hover data has a lot of information in it. Let's see if we can figure out what it is.
It's got this points property. It's a dictionary, which if you start to read these brackets, you can get the hang of it. It's a dictionary, and its points value is this list that is just one point, in fact.
It could be more, but we only have one cursor, so it's just one point. There are other elements in dash that might have two points. But in this case, just one point, because it's the cursor.
There's only one cursor. So let's try sort of walking this object, walking through this data and figuring out what it is. What if we print out our current cursor data, current cursor data, points, points property, and index zero? And then we gotta go interact with that page.
It's very mad at me. It's very mad at me for a good reason. I've got an error, callback error updating it.
None type object is not subscriptable. What this error means is that this current cursor at points was a none value. It did not have a zero index, because it was none.
It was like we just did none at zero. That's an error. So let's only do this printing if current cursor data, then it should have all these values.
It interacts with it, interacts with it, no errors anymore. And now here's the points, points at zero. It's this cursor information here.
Let's dive a little bit more into it. It has, let's call that something. Let's call that our point.
Point equals, let's spell it right, current cursor data at points, zero. That's our one point, our cursor point. Let's print out point at hover text and see what it says.
No errors, great. And hover text is this right here. Grandam, 3SEP, Silhouette, 3SEP, Contour, all of these are the hover text property.
Let's actually see them in action. They move the cursor around, hover over some things, maybe hover over nothing at all. Let's see what happens.
Metro, SC, Civic, SC, Civic. Try some other ones. Here's the Dodge Viper over here.
Yep, there's Viper. So there are some times when the current cursor data is not really a thing. It doesn't have a hover text because you're not hovering over anything.
But my cursor's over here. In which case we're saying, hey, don't even run this code. If current cursor data is a thing, sure.
If it's not a thing, skip right by this code and return the H1 either way, which if we go over here, there it is. That's just what's being outputted. This is the output area for our function.
When it returns, it's going as the children over here of this info panel we defined, which has no particular style to it. That's okay for now. We got the hover text.
What about, can we get the cars manufacturer and model? All right. Well, the model actually is the hover text. Let's see that.
Print model. And with Sienna there, and there it is. All right, so we've got the model.
How can we use that to get other information on this car? Well, we could instead say, okay, let's get it from our data. Let's say our row is in the car's data frame. I want ones where the car's model is our model.
Let's print out the row. See what that is. All right, I've interacted with it a little bit.
One row's 16 columns. So that's the Chevrolet Cavalier, Toyota RAV4. This is all different parts of the data frame.
Each one of them is a row. However, it's actually a row in a data frame. We can see that.
What's the type of row? It's a data frame. So what we wanna do is make it into a proper row. If I try to say print row at manufacturer, what gets printed? It's not just Mercury.
It's not just Toyota. It's not just Ford. It's actually this right here.
It's a column within our data frame. A column with only one item, but a column. What we wanna do is get this row to actually be a row.
Right now, it's a data frame. To make it a row, we could do this.iloc zero. Give me the first element in this one row data frame.
There's only one row in it. So it's just the first and only row. But it's a nice little Pandas's method for when you are trying to access something and it's in a bigger data structure than you want.
It's like I only have one row. It's not really a whole spreadsheet. It's just a row.
And I don't want it to be a spreadsheet anymore. I just wanna pull that row out of it. There's a amusingly named method.
It's called squeeze. If you call this on a data frame with only one row, then you get a series, just one row. If you call it on a row with only one element in it, then it becomes just that element.
If something could fit in a smaller package, instead of a data frame, it could squeeze into a row. Instead of a row, it could squeeze into a cell. Whatever it is, if there's nothing but one thing in it, we can lower the dimensionality of it.
That's what squeeze does. Takes a two-dimensional thing, makes it one-dimensional. Or a one-dimensional thing, it makes it zero-dimensional, just a value, not a list.
Takes two-dimensional list, makes it into one-dimensional list, takes a one-dimensional list with one value, makes it just a value. Squeeze, it's really great, really useful a lot of the time. All right, now row manufacturer should get us just the name of the manufacturer.
Let's check. Yep, just the name, no longer a row like this, but just a string, the name of the manufacturer. Great, we could get our other values.
What I wanna do now is instead of returning html.hi, let's return html.div, and to div, we'll pass some children. And we'll make a little bit of information here. I'm gonna rename row to car.
It's our one car. All the information on the car, the manufacturer, the model, horsepower, et cetera. All right, so first thing in our div will be heading, slightly smaller heading, an h3, and we'll put a string in there.
And I'm gonna make it an fstring so we can interpolate in some data. We're gonna say, not row, I renamed it, car manufacturer space car model. Let's see what that looks like.
Let's see if that worked. It should output it down here. Ooh, we have an error.
We have all kinds of errors. Let's see what's happening. Let's check out our terminal.
I do know what's happening. Actually, no, I didn't. I thought something else was happening.
But what's actually happening here is I've made a terrible mistake. This fstring, I make this mistake almost every time I do an fstring with a dictionary in it, is I start out with double quotes, and I also use double quotes for the keys. When I use this double quote here, it signifies to Python, hey, this is our string.
I mean, it gets very mad. We can fix this pretty easily. I'm gonna change these double quotes in here to single quotes.
Let's try that. And we have a syntax error, stops the server. We'll start it up again.
We still have an error. This is probably more of the error I thought I was gonna get. Nothing is printing out here.
And nothing, oh, no, I'm wrong. Here it is down here. All right, these are just old errors.
So down there, we've got all of these make and model being printed out. Manufacturer and model. Because that's the string we put out there.
It's got the car manufacturer data, and then space, and then the car model data. All right, so we've got our kind of title. Let's put some paragraphs in there, some more standard size font.
The horsepower is car, add single quotes this time, horsepower. Don't forget your commas. We're in our div list here, list of children for the div.
And we'll do an F string of fuel efficiency is car at fuel efficiency. And finally, let's mix things up a little bit and have engine size with a proper unit, the liters that the engine size is. Engine size, car at engine size.
Oop, I did double quotes again. And L for liter, right after car engine size. Let me change these to single quotes as well.
And I've, oop, I changed one too many. There we go, double quotes, single quotes, single quote, double quote. And here's single quote, nope, double quote.
Quotes are hard. All right, let's check that out. Let's see if I got that right.
Get rid of that old error. Now down here, we're getting the full information for each one of these. Dodge Viper, horsepower, fuel efficiency, and ridiculously large engine size.
Great, so that didn't take too much work. But we can always define with dash. The power of dash is we can define how the user interacts with something.
We can define an output of anything we want, an input of anything we want, and just put in the logic, any information we need to have here, and then this right here. Yeah, I think this is a great example of some of the things you can do with dash. Now, there are so much more we could do.
This is one exploration of data. There's only one thing we could do within this dataset. This dashboard could be fuel efficiency versus engine size, engine size versus horsepower.
How do those relate to the price of the car? All kinds of relationships, and just in terms of scatter plots with all kinds of filters we could do and other types of interactivity. So there is an immense amount of stuff we can do, and I hope this did a good job of demonstrating that.