In this post I present Mint Bubbles, a force-directed bubble chart visualization of exported Mint data. I explain how to use force-directed layouts to produce awesome interactive visualizations with d3, and also provide details on some of the specific tricks used to create Mint Bubbles.
Getting Your Data
Exporting your data from Mint is easy. Log into Mint and go to the Transactions tab:
Scroll to the bottom pagination section. In barely-legible super-tiny type at bottom right, there’s a link to export all your transactions:
Clicking that link will download a file called
You can see the code for this demo here.
To see a visualization of your data, drag the
transactions.csv file from
Mint onto the
drag your data here area below. You can also use
my data from the last three months or so.
drop your data here
Behind The Bubbles
I’d just started using Mint for financial tracking, and this seemed like an awesome way to visualize my personal spending patterns. To help figure out the mechanics of the NYT visualization, I consulted this article by Jim Vallandingham. He explains in detail how to create similar visualizations using d3’s force-directed layouts, which model your data as a set of particles moving about in space.
Unlike my previous visualizations, I wanted this visualization
to allow you to play with your data. Enter the HTML5 File API, which
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
dragover are analogous to
mouseover. For those events, it suffices to call
trapEvent(), which prevents the browser’s default action from happening.
For instance, Chrome on Mac OS will just download the
if you drag it into a browser tab, which is not what I want here.
drop is the interesting event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
FileReader.readAsText() to read in the
objects representing the transactions. This parsing is triggered
which fires once file I/O has completed.
onprogress are used to monitor file I/O progress via the
HTML5 progress element
transactions.csv files are typically small, and since the “uploading” is
actually a client-local copy into browser memory, you’ll probably never see
that progress bar.
I group the transactions by category:
1 2 3 4 5 6 7 8 9 10 11 12
amount stores the total amount; note the use of
+(tx['Amount']) to convert
CSV string values into numbers.
txs is used for the transaction list.
I then convert these into nodes to be used by
1 2 3 4 5 6 7 8 9
Defining The Layout
Before building the visualization itself, I define a color gradient based on bubble radius, picking the colors using the excellent Color Scheme Designer:
1 2 3 4 5 6
Now on to the visualization. First, I need to create the SVG element:
1 2 3 4
Next, I define the behavior and styling of the bubbles:
1 2 3 4 5 6 7 8 9 10
fill(d.R) uses the color gradient
fill to make smaller bubbles lighter and
larger bubbles darker.
As for the force-directed layout, I start with some basic properties:
1 2 3 4 5 6
force.tick(): Runs the force layout simulation one step.
Force-directed layouts model your data as a set of particles in space. Those particles are subject to various forces:
- Gravity: in d3, this is actually an attractive force pulling particles towards the center of the visualization.
- Friction: this slows down movement.
- Tension: if nodes are connected via links (edges), they will resist being moved apart.
- Charge: similar to electric charge, same-signed charges repel and opposite-signed charges attract.
A layout can describe some or all of these forces. Resolving the forces is a simple iterative process:
1 2 3 4 5 6 7 8 9
In addition to the above forces, visualizations using
define their own forces via the
ontick handler. I use this to apply two
- Size Sorting: similar to granular convection, larger bubbles will rise while smaller bubbles sink.
- Collision Detection: I prevent bubbles from intersecting, since that makes it easier to select them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
Alpha and Size Sorting
e.alpha? This is described cryptically in the
Internally, the layout uses a cooling parameter alpha which controls the layout temperature: as the physical simulation converges on a stable layout, the temperature drops, causing nodes to move more slowly.
A look at the code for d3.layout.force() provides some insight into what’s happening here:
1 2 3 4 5 6 7 8
Let’s look at the size sorting code again:
1 2 3 4
floatPoint(d.R) computes a “desired height” for the node
d towards that height, using
e.alpha to slow down the
sorting adjustment as the layout “cools” into its final state.
I’m currently working on a post for the main Quantified Self blog, in which I’m planning to feature another cool visualization for personal data. Aside from that, I’m hoping to use an upcoming post to dissect my Mint data in more detail. Keep posted!