| ← Lab 2 | Lab 3 | Lab 4 → |
- Highlight and Tooltips
- Linked Selection and Filters
- Let's Make an Animated Bar Chart
- D3 Behaviours Sandbox
In this exercise, we added two simple interactions with our bar charts for basic interaction with the charts.
Firstly, we implemented a way to highlight the Bar Charts when the mouse cursor was hovered over the bars.
Secondly, we added a simple tooltip text that is displayed to show additional information about the bars when the cursor is hovered over the bars.
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <script>hljs.highlightAll();</script>barChart.js
export default class BarChart {// ...
#updateBars() { this.bars = this.bars .data(this.data, (d) => d[0]) // ... .on("mouseover", (event, datum) => { // Highlight the bar on cursor hover d3.select(event.target) .classed("highlighted", true) }) .on("mouseout", (event, datum) => { // Remove the highlight on cursor out d3.select(event.target) .classed("highlighted", false) }); // Add tooltips // Adds a title element to all bars this.bars.selectAll("title") .data(d => [d]) .join("title") .text(d => `${d[0]}: ${d[1]}`); }// ...
}
main.js
"use strict";import BarChart from './visualizations/barChart_tut10.js';
/***** Exercise: Linked Selection and Filters *****/ let data = await d3.csv("data/movies_mock.csv", d => { return { year: +d.release_year, revenues: parseFloat(d.revenues), genre: d.genre } });
let bar1 = new BarChart("#bar1", 800, 400, [10, 40, 65, 10]), bar2 = new BarChart("#bar2", 800, 400, [10, 40, 65, 10]), bar3 = new BarChart("#bar3", 800, 400, [10, 40, 65, 10]);
let sortYears = (a, b) => a[0] - b[0]; let yearRevenues = d3.flatRollup(data, v => d3.sum(v, d => d.revenues), d => d.year).sort(sortYears), yearCount = d3.flatRollup(data, v => v.length, d => d.year).sort(sortYears), genreCount = d3.flatRollup(data, v => v.length, d => d.genre);
bar1.setLabels("Year", "Total Revenues") .render(yearRevenues); bar2.setLabels("Year", "Total Number of Releases") .render(yearCount); bar3.setLabels("Genre", "Total Number of Releases") .render(genreCount);
let highlightYear = (e, d) => { let year = d[0]; bar1.highlightBars([year]); bar2.highlightBars([year]); }
let rmvHighlightYear = (e, d) => { bar1.highlightBars(); bar2.highlightBars(); }
bar1.setBarHover(highlightYear).setBarOut(rmvHighlightYear); bar2.setBarHover(highlightYear).setBarOut(rmvHighlightYear);
let filterGenre = (e, d) => { let genre = d[0]; let filteredData = data.filter(d => d.genre === genre), yearRevenuesFiltered = d3.flatRollup(filteredData, v => d3.sum(v, d => d.revenues), d => d.year).sort(sortYears), yearCountFiltered = d3.flatRollup(filteredData, v => v.length, d => d.year).sort(sortYears);
bar1.setLabels("Year", `Revenues: ${genre}`) .render(yearRevenuesFiltered); bar2.setLabels("Year", `Number of Releases: ${genre}`) .render(yearCountFiltered);}
bar3.setBarClick(filterGenre);
barChart.js
export default class BarChart { // Attributes// ... // Add Object attributes for storing callback references barClick = () => {}; barHover = () => {}; barOut = () => {}; // ... #updateBars() { this.bars = this.bars // ... // ... this.#updateEvents(); // ... } #updateEvents() { // Rebind these callbacks to events this.bars .on("mouseover", this.barHover) .on("mouseout", this.barOut) .on("click", (e, d) => { console.log(`Bar Clicked: ${d}`); this.barClick(e, d); }); } // ... setBarClick(f = () => {}) { // Register new callback this.barClick = f; // Rebind callback to event this.#updateEvents(); // Return this for chaining return this; } setBarHover(f = () => {}) { // Register new callback this.barHover = f; // Rebind callback to event this.#updateEvents(); // Return this for chaining return this; } setBarOut(f = () => {}) { // Register new callback this.barOut = f; // Rebind callback to event this.#updateEvents(); // Return this for chaining return this; } highlightBars(keys = []) { // Reset Highlight for all bars this.bars.classed("highlighted", false); // Filter bars and set new highlights this.bars.filter(d => keys.includes(d[0])) .classed("highlighted", true); return this; // to allow chaining }
}
Adds an animated element for the bar chart using D3 Transitions.
There is a visual feedback when bar3 is clicked, showing an animation in bar1 and bar2.
barChart.js
export default class BarChart { // ...#updateBars() { // Bind and join rectangles to data this.bars = this.bars .data(this.data, (d) => d[0]) .join( // Initial placement of new rectangles enter => enter.append("rect") .attr("x", (d) => this.scaleX(d[0])) .attr("y", (d) => this.scaleY(0)) // Aligned at Bottom .attr("width", this.scaleX.bandwidth()) .attr("height", 0), // No height // Leave existing rectangles untouched update => update, exit => exit.transition().duration(300) .attr("y", d => this.scaleY(0)) // Aligned at bottom .attr("height", 0) // No Height .remove() // Destroy rectangle when finished ) .classed("bar", true); // Animate Placement and sizing (enter + update only) this.bars.transition().duration(500) .attr("x", (d) => this.scaleX(d[0])) .attr("y", (d) => this.scaleY(d[1])) .attr("width", this.scaleX.bandwidth()) .attr("height", (d) => this.scaleY(0) - this.scaleY(d[1])); }
}