Skip to content

Instantly share code, notes, and snippets.

@MichaelCurrie
Last active July 9, 2020 12:18
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save MichaelCurrie/5e2da378a53ea624082cb55e78fdfa05 to your computer and use it in GitHub Desktop.
Save MichaelCurrie/5e2da378a53ea624082cb55e78fdfa05 to your computer and use it in GitHub Desktop.
Bubble chart
a, a:visited, a:active {
color: #444;
}
.container {
max-width: 940px;
margin: auto;
}
.button {
min-width: 130px;
padding: 4px 5px;
cursor: pointer;
text-align: center;
font-size: 13px;
border: 1px solid #e0e0e0;
text-decoration: none;
}
.button.active {
background: #000;
color: #fff;
}
#vis {
width: 940px;
height: 700px;
clear: both;
margin-bottom: 10px;
margin-top: 30px;
}
#toolbar {
margin-top: 10px;
}
.bubble_group_label {
font-size: 21px;
fill: #aaa;
cursor: default;
}
.tooltip {
position: absolute;
top: 100px;
left: 100px;
-moz-border-radius:5px;
border-radius: 5px;
border: 2px solid #000;
background: #fff;
opacity: .9;
color: black;
padding: 10px;
width: 300px;
font-size: 12px;
z-index: 10;
}
.tooltip .title {
font-size: 13px;
}
.tooltip .name {
font-weight:bold;
}
.footer {
text-align: center;
}
.world_map {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
/* Bubble chart
*
* Based on Jim Vallandingham's work
* Organization and style inspired by:
* https://bost.ocks.org/mike/chart/
*
*/
function createBubbleChart() {
/* bubbleChart creation function. Returns a function that will
* instantiate a new bubble chart given a DOM element to display
* it in and a dataset to visualize.
*/
// Tooltip object for mouseover functionality, width 200
var tooltip = floatingTooltip('bubble_chart_tooltip', 200);
// These will be set in the `bubbleChart` function
var svg = null, inner_svg = null;
var bubbles = null;
var forceSim = null;
var fillColorScale = null;
var radiusScale = null;
var nodes = [];
var margin = null;
var width = null;
var height = null;
var dataExtents = {};
// For scatterplots (initialized if applicable)
var xAxis = null;
var yAxis = null;
var xScale = null;
var yScale = null;
// For the map
var bubbleMercProjection = d3.geoAlbers();
function getFillColorScale() {
// Obtain a color mapping from keys to color values specified in our parameters file
// Get the keys and values from the parameters file
var color_groupsKeys = Object.keys(BUBBLE_PARAMETERS.fill_color.color_groups)
var color_groupsValues = []
for (var i=0; i<color_groupsKeys.length; i++) {
var key = color_groupsKeys[i]
color_groupsValues.push(BUBBLE_PARAMETERS.fill_color.color_groups[key])
}
// Generate the key -> value mapping for colors
var fillColorScale = d3.scaleOrdinal()
.domain(color_groupsKeys)
.range(color_groupsValues);
return fillColorScale;
}
function createNodes(rawData) {
/*
* This data manipulation function takes the raw data from
* the CSV file and converts it into an array of node objects.
* Each node will store data and visualization values to visualize
* a bubble.
*
* rawData is expected to be an array of data objects, read in from
* one of d3's loading functions like d3.csv.
*
* This function returns the new node array, with a node in that
* array for each element in the rawData input.
*/
// Use map() to convert raw data into node data.
var myNodes = rawData.map(function (data_row) {
node = {
id: data_row.id,
scaled_radius: radiusScale(+data_row[BUBBLE_PARAMETERS.radius_field]),
actual_radius: +data_row[BUBBLE_PARAMETERS.radius_field],
fill_color_group: data_row[BUBBLE_PARAMETERS.fill_color.data_field],
// Put each node initially in a random location
x: Math.random() * width,
y: Math.random() * height
};
for(var key in data_row) {
// Skip loop if the property is from prototype
if (!data_row.hasOwnProperty(key)) continue;
node[key] = data_row[key];
}
return node;
});
// Sort them to prevent occlusion of smaller nodes.
myNodes.sort(function (a, b) { return b.actual_radius - a.actual_radius; });
return myNodes;
}
function getGridTargetFunction(mode) {
// Given a mode, return an anonymous function that maps nodes to target coordinates
if (mode.type != "grid") {
throw "Error: getGridTargetFunction called with mode != 'grid'";
}
return function (node) {
// Given a mode and node, return the correct target
if(mode.size == 1) {
// If there is no grid, our target is the default center
target = mode.gridCenters[""];
} else {
// If the grid size is greater than 1, look up the appropriate target
// coordinate using the relevant node_tag for the mode we are in
node_tag = node[mode.dataField]
target = mode.gridCenters[node_tag];
}
return target;
}
}
function showLabels(mode) {
/*
* Shows labels for each of the positions in the grid.
*/
var currentLabels = mode.labels;
var bubble_group_labels = inner_svg.selectAll('.bubble_group_label')
.data(currentLabels);
var grid_element_half_height = height / (mode.gridDimensions.rows * 2);
bubble_group_labels.enter().append('text')
.attr('class', 'bubble_group_label')
.attr('x', function (d) { return mode.gridCenters[d].x; })
.attr('y', function (d) { return mode.gridCenters[d].y - grid_element_half_height; })
.attr('text-anchor', 'middle') // centre the text
.attr('dominant-baseline', 'hanging') // so the text is immediately below the bounding box, rather than above
.text(function (d) { return d; });
// GRIDLINES FOR DEBUGGING PURPOSES
/*
var grid_element_half_height = height / (mode.gridDimensions.rows * 2);
var grid_element_half_width = width / (mode.gridDimensions.columns * 2);
for (var key in currentMode.gridCenters) {
if (currentMode.gridCenters.hasOwnProperty(key)) {
var rectangle = inner_svg.append("rect")
.attr("class", "mc_debug")
.attr("x", currentMode.gridCenters[key].x - grid_element_half_width)
.attr("y", currentMode.gridCenters[key].y - grid_element_half_height)
.attr("width", grid_element_half_width*2)
.attr("height", grid_element_half_height*2)
.attr("stroke", "red")
.attr("fill", "none");
var ellipse = inner_svg.append("ellipse")
.attr("class", "mc_debug")
.attr("cx", currentMode.gridCenters[key].x)
.attr("cy", currentMode.gridCenters[key].y)
.attr("rx", 15)
.attr("ry", 10);
}
}*/
}
function tooltipContent(d) {
/*
* Helper function to generate the tooltip content
*
* Parameters: d, a dict from the node
* Returns: a string representing the formatted HTML to display
*/
var content = ''
// Loop through all lines we want displayed in the tooltip
for(var i=0; i<BUBBLE_PARAMETERS.tooltip.length; i++) {
var cur_tooltip = BUBBLE_PARAMETERS.tooltip[i];
var value_formatted;
// If a format was specified, use it
if ("format_string" in cur_tooltip) {
value_formatted =
d3.format(cur_tooltip.format_string)(d[cur_tooltip.data_field]);
} else {
value_formatted = d[cur_tooltip.data_field];
}
// If there was a previous tooltip line, add a newline separator
if (i > 0) {
content += '<br/>';
}
content += '<span class="name">' + cur_tooltip.title + ': </span>';
content += '<span class="value">' + value_formatted + '</span>';
}
return content;
}
function showTooltip(d) {
/*
* Function called on mouseover to display the
* details of a bubble in the tooltip.
*/
// Change the circle's outline to indicate hover state.
d3.select(this).attr('stroke', 'black');
// Show the tooltip
tooltip.showTooltip(tooltipContent(d), d3.event);
}
function hideTooltip(d) {
/*
* Hide tooltip
*/
// Reset the circle's outline back to its original color.
var originalColor = d3.rgb(fillColorScale(d.fill_color_group)).darker()
d3.select(this).attr('stroke', originalColor);
// Hide the tooltip
tooltip.hideTooltip();
}
function ticked() {
bubbles.each(function (node) {})
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function showAxis(mode) {
/*
* Show the axes.
*/
// Set up axes
xAxis = xScale; //d3.scaleBand().rangeRound([0, width]).padding(0.1);
yAxis = yScale; //d3.scaleLinear().rangeRound([height, 0]);
inner_svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xAxis))
inner_svg.append("text")
.attr("class", "axis axis--x label")
.attr("transform", "translate(" + (width/2) + " , " + (height) + ")")
// so the text is immediately below the bounding box, rather than above
.attr('dominant-baseline', 'hanging')
.attr("dy", "1.5em")
.style("text-anchor", "middle")
.text(mode.xDataField);
inner_svg.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yAxis).ticks(10))//, "%"))
inner_svg.append("text")
.attr("class", "axis axis--y label")
// We need to compose a rotation with a translation to place the y-axis label
.attr("transform", "translate(" + 0 + ", " + (height/2) + ")rotate(-90)")
.attr("dy", "-3em")
.attr("text-anchor", "middle")
.text(mode.yDataField);
}
function createBubbles() {
// Bind nodes data to what will become DOM elements to represent them.
inner_svg.selectAll('.bubble')
.data(nodes, function (d) { return d.id; })
// Create new circle elements each with class `bubble`.
// There will be one circle.bubble for each object in the nodes array.
.enter()
.append('circle').attr('r', 0) // Initially, their radius (r attribute) will be 0.
.classed('bubble', true)
.attr('fill', function (d) { return fillColorScale(d.fill_color_group); })
.attr('stroke', function (d) { return d3.rgb(fillColorScale(d.fill_color_group)).darker(); })
.attr('stroke-width', 2)
.on('mouseover', showTooltip)
.on('mouseout', hideTooltip);
bubbles = d3.selectAll('.bubble');
// Fancy transition to make bubbles appear, ending with the correct radius
bubbles.transition()
.duration(2000)
.attr('r', function (d) { return d.scaled_radius; });
}
function addForceLayout(isStatic) {
if (forceSim) {
// Stop any forces currently in progress
forceSim.stop();
}
// Configure the force layout holding the bubbles apart
forceSim = d3.forceSimulation()
.nodes(nodes)
.velocityDecay(0.3)
.on("tick", ticked);
if (!isStatic) {
// Decide what kind of force layout to use: "collide" or "charge"
if(BUBBLE_PARAMETERS.force_type == "collide") {
var bubbleCollideForce = d3.forceCollide()
.radius(function(d) { return d.scaled_radius + 0.5; })
.iterations(4)
forceSim
.force("collide", bubbleCollideForce)
}
if(BUBBLE_PARAMETERS.force_type == "charge") {
function bubbleCharge(d) {
return -Math.pow(d.scaled_radius, 2.0) * (+BUBBLE_PARAMETERS.force_strength);
}
forceSim
.force('charge', d3.forceManyBody().strength(bubbleCharge));
}
}
}
function createCanvas(parentDOMElement) {
// Create a SVG element inside the provided selector with desired size.
svg = d3.select(parentDOMElement)
.append('svg')
.attr('width', BUBBLE_PARAMETERS.width)
.attr('height', BUBBLE_PARAMETERS.height);
// Specify margins and the inner width and height
margin = {top: 20, right: 20, bottom: 50, left: 80},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
// Create an inner SVG panel with padding on all sides for axes
inner_svg = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
}
//////////////////////////////////////////////////////////////
var bubbleChart = function bubbleChart(parentDOMElement, rawData) {
/*
* Main entry point to the bubble chart. This function is returned
* by the parent closure. It prepares the rawData for visualization
* and adds an svg element to the provided selector and starts the
* visualization creation process.
*
* parentDOMElement is expected to be a DOM element or CSS selector that
* points to the parent element of the bubble chart. Inside this
* element, the code will add the SVG continer for the visualization.
*
* rawData is expected to be an array of data objects as provided by
* a d3 loading function like d3.csv.
*/
// Capture all the maximums and minimums in the numeric fields, which
// will be used in any scatterplots.
for (var numeric_field_index in BUBBLE_PARAMETERS.numeric_fields) {
var numeric_field = BUBBLE_PARAMETERS.numeric_fields[numeric_field_index];
dataExtents[numeric_field] = d3.extent(rawData, function (d) { return +d[numeric_field]; });
}
// Scale bubble radii using ^(0.5)
// We size bubbles based on area instead of radius
var maxRadius = dataExtents[BUBBLE_PARAMETERS.radius_field][1];
radiusScale = d3.scalePow()
.exponent(0.5)
.range([2, 30]) // Range between 2 and 25 pixels
.domain([0, maxRadius]); // Domain between 0 and the largest bubble radius
fillColorScale = getFillColorScale();
// Initialize the "nodes" with the data we've loaded
nodes = createNodes(rawData);
// Initialize svg and inner_svg, which we will attach all our drawing objects to.
createCanvas(parentDOMElement);
// Create a container for the map before creating the bubbles
// Then we will draw the map inside this container, so it will appear behind the bubbles
inner_svg.append("g")
.attr("class", "world_map_container");
// Create the bubbles and the force holding them apart
createBubbles();
};
bubbleChart.switchMode = function (buttonID) {
/*
* Externally accessible function (this is attached to the
* returned chart function). Allows the visualization to toggle
* between display modes.
*
* buttonID is expected to be a string corresponding to one of the modes.
*/
// Get data on the new mode we have just switched to
currentMode = new ViewMode(buttonID, width, height);
// Remove current labels
inner_svg.selectAll('.bubble_group_label').remove();
// Remove current debugging elements
inner_svg.selectAll('.mc_debug').remove(); // DEBUG
// Remove axes components
inner_svg.selectAll('.axis').remove();
// Remove map
inner_svg.selectAll('.world_map').remove();
// SHOW LABELS (if we have more than one category to label)
if (currentMode.type == "grid" && currentMode.size > 1) {
showLabels(currentMode);
}
// SHOW AXIS (if our mode is scatter plot)
if (currentMode.type == "scatterplot") {
xScale = d3.scaleLinear().range([0, width])
.domain([dataExtents[currentMode.xDataField][0], dataExtents[currentMode.xDataField][1]]);
yScale = d3.scaleLinear().range([height, 0])
.domain([dataExtents[currentMode.yDataField][0], dataExtents[currentMode.yDataField][1]]);
showAxis(currentMode);
}
// ADD FORCE LAYOUT
if (currentMode.type == "scatterplot" || currentMode.type == "map") {
addForceLayout(true); // make it static so we can plot bubbles
} else {
addForceLayout(false); // the bubbles should repel about the grid centers
}
// SHOW MAP (if our mode is "map")
if (currentMode.type == "map") {
var path = d3.geoPath().projection(bubbleMercProjection);
d3.queue()
.defer(d3.json, BUBBLE_PARAMETERS.map_file)
.await(ready);
function ready(error, topology) {
if (error) throw error;
inner_svg.selectAll(".world_map_container")
.append("g")
.attr("class", "world_map")
.selectAll("path")
.data(topojson.feature(topology, topology.objects.states).features)
.enter()
.append("path")
.attr("d", path);
}
}
// MOVE BUBBLES TO THEIR NEW LOCATIONS
var targetFunction;
if (currentMode.type == "grid") {
targetFunction = getGridTargetFunction(currentMode);
}
if (currentMode.type == "scatterplot") {
targetFunction = function (d) {
return {
x: xScale(d[currentMode.xDataField]),
y: yScale(d[currentMode.yDataField])
};
};
}
if (currentMode.type == "map") {
targetFunction = function (d) {
return {
x: bubbleMercProjection([+d.Longitude, +d.Latitude])[0],
y: bubbleMercProjection([+d.Longitude, +d.Latitude])[1]
};
};
}
// Given the mode we are in, obtain the node -> target mapping
var targetForceX = d3.forceX(function(d) {return targetFunction(d).x})
.strength(+BUBBLE_PARAMETERS.force_strength);
var targetForceY = d3.forceY(function(d) {return targetFunction(d).y})
.strength(+BUBBLE_PARAMETERS.force_strength);
// Specify the target of the force layout for each of the circles
forceSim
.force("x", targetForceX)
.force("y", targetForceY);
// Restart the force layout simulation
forceSim.alphaTarget(1).restart();
};
// Return the bubbleChart function from closure.
return bubbleChart;
}
/////////////////////////////////////////////////////////////////////////////////////
function ViewMode(button_id, width, height) {
/* ViewMode: an object that has useful parameters for each view mode.
* initialize it with your desired view mode, then use its parameters.
* Attributes:
- mode_index (which button was pressed)
- buttonId (which button was pressed)
- gridDimensions e.g. {"rows": 10, "columns": 20}
- gridCenters e.g. {"group1": {"x": 10, "y": 20}, ...}
- dataField (string)
- labels (an array)
- type (type of grouping: "grouping" or "scatterplot")
- size (number of groups)
*/
// Find which button was pressed
var mode_index;
for(mode_index=0; mode_index<BUBBLE_PARAMETERS.modes.length; mode_index++) {
if(BUBBLE_PARAMETERS.modes[mode_index].button_id == button_id) {
break;
}
}
if(mode_index>=BUBBLE_PARAMETERS.modes.length) {
console.log("Error: can't find mode with button_id = ", button_id)
}
var curMode = BUBBLE_PARAMETERS.modes[mode_index];
this.buttonId = curMode.button_id;
this.type = curMode.type;
if (this.type == "grid") {
this.gridDimensions = curMode.grid_dimensions;
this.labels = curMode.labels;
if (this.labels == null) { this.labels = [""]; }
this.dataField = curMode.data_field;
this.size = this.labels.length;
// Loop through all grid labels and assign the centre coordinates
this.gridCenters = {};
for(var i=0; i<this.size; i++) {
var cur_row = Math.floor(i / this.gridDimensions.columns); // indexed starting at zero
var cur_col = i % this.gridDimensions.columns; // indexed starting at zero
var currentCenter = {
x: (2 * cur_col + 1) * (width / (this.gridDimensions.columns * 2)),
y: (2 * cur_row + 1) * (height / (this.gridDimensions.rows * 2))
};
this.gridCenters[this.labels[i]] = currentCenter;
}
}
if (this.type == "scatterplot") {
// Set up the x and y scales (domains need to be set using the actual data)
this.xDataField = curMode.x_data_field;
this.yDataField = curMode.y_data_field;
this.xFormatString = curMode.x_format_string;
this.yFormatString = curMode.y_format_string;
}
if (this.type == "map") {
this.latitudeField = curMode.latitude_field;
this.longitudeField = curMode.longitude_field;
}
};
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Set title
document.title = BUBBLE_PARAMETERS.report_title;
report_title.innerHTML = BUBBLE_PARAMETERS.report_title;
// Set footer
document.getElementById("footer_text").innerHTML = BUBBLE_PARAMETERS.footer_text;
// Create a new bubble chart instance
var myBubbleChart = createBubbleChart();
// Load data
d3.csv(BUBBLE_PARAMETERS.data_file, function (error, data) {
// Once the data is loaded...
if (error) { console.log(error); }
// Display bubble chart inside the #vis div.
myBubbleChart('#vis', data);
// Start the visualization with the first button
myBubbleChart.switchMode(BUBBLE_PARAMETERS.modes[0].button_id)
});
function setupButtons() {
// As the data is being loaded: setup buttons
// Create the buttons
// TODO: change this to use native d3js selection methods
for (i = 0; i<BUBBLE_PARAMETERS.modes.length; i++) {
var button_element = document.createElement("a");
button_element.href = "#";
if (i == 0) {
button_element.className = "button active";
} else {
button_element.className = "button";
}
button_element.id = BUBBLE_PARAMETERS.modes[i].button_id;
button_element.innerHTML = BUBBLE_PARAMETERS.modes[i].button_text;
document.getElementById("toolbar").appendChild(button_element);
}
// Handle button click
// Set up the layout buttons to allow for toggling between view modes.
d3.select('#toolbar')
.selectAll('.button')
.on('click', function () {
// Remove active class from all buttons
d3.selectAll('.button').classed('active', false);
// Set the button just clicked to active
d3.select(this).classed('active', true);
// Get the id of the button
var buttonId = d3.select(this).attr('id');
// Switch the bubble chart to the mode of
// the currently clicked button.
myBubbleChart.switchMode(buttonId);
});
}
setupButtons();
var BUBBLE_PARAMETERS = {
"data_file": "DonaldTrumpNetWorth2016.csv",
"map_file": "us.json",
"report_title": "Donald Trump Net Worth 2016",
"footer_text": "Net Value of Assets owned by Donald J. Trump. Source: Jennifer Wang/Forbes.",
"width": 940,
"height": 600,
"force_strength": 0.03,
"force_type": "charge",
"radius_field": "Net Value",
"numeric_fields": ["Asset Value", "Debt", "Net Value", "Change vs 2015", "Stake"],
"fill_color": {
"data_field": "Change",
"color_groups": {
"Down": "#d84b2a",
"No Change": "#beccae",
"Up": "#7aa25c"
}
},
"tooltip": [
{"title": "Asset", "data_field": "Asset"},
{"title": "Type", "data_field": "Type"},
{"title": "Asset Value", "data_field": "Asset Value", "format_string": ",.2r"},
{"title": "Debt", "data_field": "Debt", "format_string": ",.2r"},
{"title": "Net Value", "data_field": "Net Value", "format_string": ",.2r"},
{"title": "Change vs 2015", "data_field": "Change vs 2015", "format_string": ",.2r"}
],
"modes": [
{
"button_text": "All Assets",
"button_id": "all",
"type": "grid",
"labels": null,
"grid_dimensions": {"rows": 1, "columns": 1},
"data_field": null
},
{
"button_text": "Assets by Type",
"button_id": "region",
"type": "grid",
"labels": ["Office and retail", "Residential and retail", "Hotel, condos and retail", "Affordable housing units", "Personal assets", "Golf resort", "Winery", "Licensing agreements", "Industrial warehouse"],
"grid_dimensions": {"rows": 3, "columns": 3},
"data_field": "Type"
},
{
"button_text": "Assets by Change in Value",
"button_id": "Change",
"type": "grid",
"labels": ["Down", "No Change", "Up"],
"grid_dimensions": {"rows": 1, "columns": 3},
"data_field": "Change"
},
{
"button_text": "Change in value vs Net Value",
"button_id": "change_vs_net_value",
"type": "scatterplot",
"x_data_field": "Net Value",
"y_data_field": "Change vs 2015",
"x_format_string": ",.2r",
"y_format_string": ",.2r"
},
{
"button_text": "Assets by Location",
"button_id": "assets_on_map",
"type": "map",
"latitude_field": "Latitude",
"longitude_field": "Longitude"
}
]
};
Asset Type Asset Value Debt Net Value Change vs 2015 Change Stake Latitude Longitude Notes
Trump Tower (New York City) Office and retail 471 100 371 -159 Down 1 40.768277 -73.981455 Opened 1983
1290 Avenue of the Americas (New York City) Office and retail 2310 950 408 -62 Down 0.3 40.768277 -73.981455
Niketown (New York City) Office and retail 400 10 390 -52 Down 1 40.768277 -73.981455 Ground lease through 2079
40 Wall Street (New York City) Office and retail 501 156 345 -28 Down 1 40.768277 -73.981455
Trump Park Avenue (New York City) Residential and retail 191 14.3 176.7 -27 Down 1 40.768277 -73.981455 49,564 sq. ft. of condos; 27,467 sq. ft. of retail
Trump Parc/Trump Parc East (New York City) Residential and retail 88 0 88 17 Up 1 40.768277 -73.981455 11,750 sq. ft. of condos; 14,963 sq. feet of retail; 13,108 sq. ft. of garage
Trump International Hotel and Tower, Central Park West (New York City) Hotel, condos and retail 38 0 38 21 Up 1 40.768277 -73.981455
Trump World Tower, 845 United Nations Plaza (New York City) Residential and retail 27 0 27 -16 Down 1 40.768277 -73.981455 9,007 sq. ft. of retail; 28,579 sq. ft. of garage; one 2,835-square-foot condo
Spring Creek Towers (Brooklyn, N.Y.) Affordable housing units 1000 408 23.68 0 No Change 0.04 40.768277 -73.981455 Trump’s father, Fred, amassed a portfolio of 20,000 Brooklyn and Queens apartments worth hundreds of millions at one point. But Donald was more interested in Manhattan. Over time the family sold most of the outer-borough holdings. The lone remaining asset from his father’s era is a 4% interest in Spring Creek Towers, a massive, 46-tower government subsidized housing complex with 5,881 units in Brooklyn’s East New York neighborhood that the Trumps reportedly bought into in 1973.
Trump Plaza (New York City) Residential and retail 27.7 14.7 13 -16 Down 1 40.768277 -73.981455 Ground lease through 2082
Trump Tower Penthouse (New York City) Personal assets 90 0 90 -10 Down 1 40.768277 -73.981455 Personal residence, 30,000 sq. ft.
555 California Street (San Francisco) Office and retail 1645 589 316.8 32 Up 0.3 37.792282 -122.403747 The other half of the deal that Trump’s Chinese investors completed in 2006. In exchange for a 78-acre tract of land on New York’s Upper West Side, the Chinese got 1290 Avenue of Americas in New York (see above) and 555 California Street in San Francisco, then called the Bank of America Center. While valuations for San Francisco office space have dipped, the building has brought a higher net income, raising the value of Trump’s stake by $32 million.
Trump National Doral Miami Golf resort 275 106 169 -25 Down 1 25.813875 -80.339183
Mar-A-Lago (Palm Beach, Florida) Golf resort 150 0 150 -50 Down 1 26.677316 -80.036959 Private club
U.S. Golf Courses Golf resort 225 18.5 206.5 -72 Down 1 39.80949 -71.886246 10 golf courses in 6 states plus the District of Columbia
Scotland & Ireland Golf Courses Golf resort 85 0 85 -3 Down 1 55.314129 -4.828675 3 golf resorts in Scotland and Ireland
Trump Chicago Hotel, condos and retail 169 50 119 -39 Down 1 41.888988 -87.625997
Trump International Hotel Washington, D.C. Hotel, condos and retail 229 125 104 -97 Down 1 38.89454 -77.027011 Ground lease through 2075
Trump International Hotel Las Vegas Hotel, condos and retail 156 18 69 -27 Down 0.5 36.129588 -115.172671 The gleaming hotel–which claims to be encased in 24-karat gold glass–has been more successful than Trump’s previous forays in gambling zones. While his Atlantic City casinos suffered through corporate bankruptcies, eventually reducing his stake to nothing, this joint venture with fellow real estate billionaire Phil Ruffin has become a premier destination by the Strip. It has 1,282 suites, more than half of which have been sold since it opened in 2008. It apparently has managed some of the properties for owners, renting them out.
Cash/Liquid Assets Personal assets 230 0 230 -97 Down 1 39.80949 -71.886246
Trump Winery (Charlottesville, Va.) Winery 30 0 30 0 No Change 1 37.93909 -78.498251
Seven Springs (Bedford, N.Y.) Personal assets 37.5 20 17.5 -5.5 Down 1 41.170502 -73.700027 Private estate
Trump Hotel Management & Licensing Business Licensing agreements 123 0 123 -229 Down 1 39.80949 -71.886246 The management and licensing company has roughly two dozen properties under its umbrella. Trump’s organization manages some of the hotels and resorts, including Trump Vancouver. Others, like India’s luxury condo Trump Tower, merely pay Trump to use his name. The highly lucrative business has enabled the Donald to spread his brand from the Philippines to Uruguay. While Trump has struck more licensing arrangements in the past year, FORBES cut the value of the portfolio after several sources suggested that the revenue from wholly owned properties like Doral Miami and Trump Las Vegas (disclosed in Federal Election Commission filings) should not be included in the valuation. FORBES already values those assets separately and thus this year subtracted their estimated revenues from the management business to avoid double counting.
Product Licensing Licensing agreements 14 0 14 -8.75 Down 1 39.80949 -71.886246 Products: Trump Home, Select by Trump (coffee), Trump Natural Spring Water, Trump Fragrance
Aircraft Personal assets 35 0 35 -27 Down 1 39.80949 -71.886246 Models: Two 1989 Sikorsky S-76B Helicopters, one 1990 Sikorsky S-76B Helicopter, one 1991 Boeing 757, one 1997 Cessna 750 Citation X.
Two Palm Beach, Fla. Residences Personal assets 14.5 0 14.5 2.85 Up 1 26.714198 -80.054904
809 N. Canon Drive, Beverly Hills Personal assets 9 0 9 0.5 Up 1 34.079731 -118.413402
Stark Industrial Park, Charleston, S.C. Industrial warehouse 3.5 0 3.5 0 No Change 1 32.859908 -79.906982
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Bubble Chart Template</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="bubble_chart.css">
<script src="bubble_parameters.js"></script>
</head>
<body>
<div class="container">
<div id="header">
<h1 id="report_title"></h1>
</div>
<div id="toolbar">
</div>
<div id="vis"></div>
<div class="footer">
<p id="footer_text"></p>
<p>
<a href="https://gist.github.com/MichaelCurrie/5e2da378a53ea624082cb55e78fdfa05">Code</a>
</p>
</div>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="tooltip.js"></script>
<script src="bubble_chart.js"></script>
</body>
</html>
/* HTML5 ✰ Boilerplate
* ==|== normalize ==========================================================
*/
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
audio:not([controls]) { display: none; }
[hidden] { display: none; }
html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
body { margin: 0; font-size: 13px; line-height: 1.231; }
body, button, input, select, textarea { font-family: sans-serif; color: #222; }
::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
::selection { background: #fe57a1; color: #fff; text-shadow: none; }
a { color: #00e; }
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }
abbr[title] { border-bottom: 1px dotted; }
b, strong { font-weight: bold; }
blockquote { margin: 1em 40px; }
dfn { font-style: italic; }
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
ins { background: #ff9; color: #000; text-decoration: none; }
mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
pre, code, kbd, samp { font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 1em; }
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
q { quotes: none; }
q:before, q:after { content: ""; content: none; }
small { font-size: 85%; }
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
sup { top: -0.5em; }
sub { bottom: -0.25em; }
ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
dd { margin: 0 0 0 40px; }
nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
svg:not(:root) { overflow: hidden; }
figure { margin: 0; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
legend { border: 0; *margin-left: -7px; padding: 0; }
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
button, input { line-height: normal; *overflow: visible; }
table button, table input { *overflow: auto; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; }
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; }
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
textarea { overflow: auto; vertical-align: top; resize: vertical; }
input:valid, textarea:valid { }
input:invalid, textarea:invalid { background-color: #f0dddd; }
table { border-collapse: collapse; border-spacing: 0; }
td { vertical-align: top; }
/* ==|== primary styles =====================================================
Author:
========================================================================== */
/* ==|== non-semantic helper classes ======================================== */
.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; }
.ir br { display: none; }
.hidden { display: none !important; visibility: hidden; }
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
.invisible { visibility: hidden; }
.clearfix:before, .clearfix:after { content: ""; display: table; }
.clearfix:after { clear: both; }
.clearfix { zoom: 1; }
/* ==|== media queries ====================================================== */
@media only screen and (min-width: 480px) {
}
@media only screen and (min-width: 768px) {
}
/* ==|== print styles ======================================================= */
@media print {
* { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; }
a, a:visited { text-decoration: underline; }
a[href]:after { content: " (" attr(href) ")"; }
abbr[title]:after { content: " (" attr(title) ")"; }
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
thead { display: table-header-group; }
tr, img { page-break-inside: avoid; }
img { max-width: 100% !important; }
@page { margin: 0.5cm; }
p, h2, h3 { orphans: 3; widows: 3; }
h2, h3 { page-break-after: avoid; }
}
function floatingTooltip(tooltipID, width) {
/*
* Creates tooltip with provided id that
* floats on top of visualization.
* Most styling is expected to come from CSS
* so check out bubble_chart.css for more details.
*/
// Local variable to hold tooltip div for
// manipulation in other functions.
var tt = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.attr('id', tooltipID)
.style('pointer-events', 'none');
// Set a width if it is provided.
if (width) {
tt.style('width', width);
}
// Initially it is hidden.
hideTooltip();
function showTooltip(content, event) {
/*
* Display tooltip with provided content.
*
* content is expected to be HTML string.
*
* event is d3.event for positioning.
*/
tt.style('opacity', 1.0)
.html(content);
updatePosition(event);
}
function hideTooltip() {
/*
* Hide the tooltip div.
*/
tt.style('opacity', 0.0);
}
function updatePosition(event) {
/*
* Figure out where to place the tooltip
* based on d3 mouse event.
*/
var xOffset = 20;
var yOffset = 10;
var ttw = tt.style('width');
var tth = tt.style('height');
var wscrY = window.scrollY;
var wscrX = window.scrollX;
var curX = (document.all) ? event.clientX + wscrX : event.pageX;
var curY = (document.all) ? event.clientY + wscrY : event.pageY;
var ttleft = ((curX - wscrX + xOffset * 2 + ttw) > window.innerWidth) ?
curX - ttw - xOffset * 2 : curX + xOffset;
if (ttleft < wscrX + xOffset) {
ttleft = wscrX + xOffset;
}
var tttop = ((curY - wscrY + yOffset * 2 + tth) > window.innerHeight) ?
curY - tth - yOffset * 2 : curY + yOffset;
if (tttop < wscrY + yOffset) {
tttop = curY + yOffset;
}
tt.style('top', tttop + 'px');
tt.style('left', ttleft + 'px');
}
return {
showTooltip: showTooltip,
hideTooltip: hideTooltip,
updatePosition: updatePosition
};
}
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment