In addition to the interactive charts used in our analysis, our developers had some fun experimenting with this bubble chart illustrating the differences between emerging firm (in orange) and established firm (in blue) settlements reached in 2015 alone. This chart shows settlement size by relative area of the circles. The shade of color for each circle represents settlement timing, with the lightest shades representing cases that settled during early pleading and the darkest shades representing cases that settled during discovery. Rolling over a particular settlement will reveal additional information collected by Stanford Securities Litigation Analytics about each case. We will continue refining this chart, including it's functionality and design for use in future posts and welcome any feedback with respect to this chart or any others on our site.
Back to Article
Early pleading
Late Pleading
Discovery
Emerging
Established
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.tooltip {
position: absolute;
width: 250px;
pointer-events: none;
padding: 2em;
background: white;
border: 1px solid black;
border-radius: 6px 6px 6px 6px;
-moz-border-radius: 6px 6px 6px 6px;
-webkit-border-radius: 6px 6px 6px 6px;
}
.nvtooltip {
padding: 1em;
}
.nvtooltip p {
margin: .5em 0;
padding: 0;
}
circle {
stroke: black;
}
.externalObject p { margin: 0.4em 0;
}
.externalObject p.bubblechart-title { font-weight:800;
}
.externalObject .bubblechart-label { display:inline-block; min-width:10em;
}
/*
Early Pleading Late Pleading Discovery
Established Firms #bdd7e7 #6baed6 #3182bd
Emerging Firms #fdbe85 #fd8d3c #e6550d
*/
.established-firm {
fill: #3182bd;
}
.established-firm.early-pleading {
fill: #bdd7e7;
background-color: #bdd7e7;
}
.established-firm.late-pleading {
fill: #6baed6;
background-color: #6baed6;
}
.established-firm.discovery {
fill: #3182bd;
background-color: #3182bd;
}
.emerging-firm.early-pleading {
fill: #fdbe85;
background-color: #fdbe85;
}
.emerging-firm.late-pleading {
fill: #fd8d3c;
background-color: #fd8d3c;
}
.emerging-firm.discovery {
fill: #e6550d;
background-color: #e6550d;
}
d3.csv("/sites/default/files/bubble_chart-with-x-z2.csv", function (error, data) {
// d3.csv("bubble_chart-with-x-z-2.csv", function (error, data) {
// -------------------------------------------------
var margin = {top: 400, right: 120, bottom: 100, left: 60},
width = 1400 - margin.left - margin.right,
height = 2500 - margin.top - margin.bottom;
// -------------------------------------------------
// -------------------------------------------
var random = d3.random.normal();
// convert e.g. "$1,000" -> (int) 1000
var dollarsToInt = function (s) {
return parseInt(s.slice(1).replace(/,/g, ""));
};
var settlementComparator = function (a, b) {
return dollarsToInt(a["Settlement Amount"]) - dollarsToInt(b["Settlement Amount"]);
};
// ------------------------------------------
// ---------------------------------------------
// add a size field to the data
data.forEach(function (d) {
d.size = parseInt(d["Settlement Amount"].slice(1).replace(/,/g, "")) / 10000;
// console.log (parseInt(d["Settlement Amount"].slice(1)));
});
// --------------------------------------------
// -------------------------------------------------
// x-getter
var xValue = function (d) {
//console.log("setting xValue to " + d.x);
// return d.size*80 ;
return parseInt(d.x);
};
/*
console.log(" min " + d3.min(data, xValue));
console.log(" max " + d3.max(data, xValue));
*/
// y-getter
var yValue = function (d) {
return parseInt(d["Settlement Date"].split("/")[0]);
};
var xScale = d3.scale.linear()
.domain([d3.min(data, xValue), d3.max(data, xValue)])
.range([100, width - 200]);
var xMap = function (d) {
//console.log(" xMap is " + xValue(d));
return xScale(xValue(d));
};
var yScale = d3.scale.linear()
.domain([d3.min(data, yValue), d3.max(data, yValue)])
.range([height - 100, 0])
var yMap = function (d) {
return yScale(yValue(d));
};
var yAxis = d3.svg.axis().scale(yScale).orient("left").tickFormat(function (d) {
var months = ["Jan", "Feb", "March", "April", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"];
return months[d - 1];
});
var sizeScale = (function () {
return d3.scale.sqrt()
.domain([
d3.min(data, function (d) {
return d.size;
}),
d3.max(data, function (d) {
return d.size;
})
]
)
.range([1, 300])
})();
// ---------------------------------------------------------------------
// ----------------------------------------------------------------
/* the structure of the visualisation is as follows:
there is a top-level svg element which contains two elements, a y-axis
and a chart body, both of which are implemented as their own svg group
elements.
the y-axis is a d3.svg.axis() element with a linear scale whose tickFormat
returns the month corresponding to each valid integer (i.e. it displays the 12
months).
the chart body consists of a set of svg groups containing two elements each,
together forming the initially-visible bubble and overlay text. on mouseover,
all of the elements in the chart body are hidden and the corresponding bubble's
"tooltip" text is displayed in its place, with the appropriate transitions applied
to the fade out/in and a background element "under"-laying the text.
*/
// add the graph canvas to the body of the webpage
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
/*
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
*/
// y-axis
svg.append("g")
.attr("class", "y axis")
.attr("id", "y-axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 1)
.attr("dy", ".71em")
.style("text-anchor", "end")
// ---------------------------------------------------------------
var chartBody = svg.append("g")
.attr("class", "chart")
var groups = chartBody.selectAll("g")
.data(data).enter()
.append("g")
.attr("class", "blgroup")
.attr("transform", function (d, i) {
var yAxisHeight = d3.select("#y-axis").node().getBBox().height;
// 12 months, 12 ticks
var distanceBetweenTicks = yAxisHeight / 12;
// map the day to a point between the current tick and the next
var dayOffsetScale = d3.scale.linear()
.domain([0, 31])
.range([0, distanceBetweenTicks]);
function getDay(d) {
return d["Settlement Date"].split("/")[1];
}
return "translate(" + xMap(d) + "," + (yMap(d) - dayOffsetScale(getDay(d))) + ")";
})
// initial circles
var transitionDuration = 1000;
var visibleCircles = groups.append("circle")
.attr("r", function (d, i) {
return sizeScale(d.size);
})
.attr("class", function (d) {
return d["Settlement Timing"].replace(/\s+/g, '-').toLowerCase() + ' ' + d["Firm Type"].replace(/\s+/g, '-').toLowerCase();
})
.attr("cx", 20)
.attr("cy", 0)
// generate a circle and appropriate text
.on("mouseover", function () {
var hiddenCircleRadius = 200;
d3.select(this).attr("stroke", "red") // change color of stroke on rollover
// add delay
d3.selectAll(".blgroup circle")
.attr("r", function (d, i) {
return sizeScale(d.size)/5;
});
// append a new circle to the parent group, rendering it on top
// and making it appear gradually
var mouseoverCircle = d3.select(this.parentNode).append("circle")
.attr("id", "mouseoverCircle")
.attr("r", hiddenCircleRadius)
.attr("cx", 20)
.attr("cy", 0)
.attr("class", function (d) {
return d["Settlement Timing"].replace(/\s+/g, '-').toLowerCase() + ' ' + d["Firm Type"].replace(/\s+/g, '-').toLowerCase();
})
.attr("class", function (d) {
return d["Settlement Timing"].replace(/\s+/g, '-').toLowerCase() + ' ' + d["Firm Type"].replace(/\s+/g, '-').toLowerCase();
})
.on("mouseout", function () {
d3.selectAll("circle").transition(transitionDuration).style("opacity", function () {
return (d3.select(this).node() === mouseoverCircle.node()) ? 0.0 : 1.0;
}).attr("stroke", "red").attr("r", function (d) {
return sizeScale(d.size);
})
d3.selectAll(".blgroup text").transition(transitionDuration).style("opacity", function () {
return d3.select(this).attr("defaultOpacity")
});
d3.select(this).remove();
d3.select("foreignObject").transition(transitionDuration).style("opacity", 0);
d3.select("foreignObject").remove();
})
var mouseoverTextObject = d3.select(this.parentNode).append("foreignObject")
.attr("class", "externalObject")
.attr("x", -100)
.attr("y", -100)
.attr("width", 300)
.attr("fill", "white")
var mouseoverTextDiv = mouseoverTextObject.append("xhtml:div")
.html(function (d) {
var tooltipContents = "Issuer: " + d["Issuer"] +
"Settlement Date: " + d["Settlement Date"] +
"Settlement Amount: " + d["Settlement Amount"] +
"Firm Type: " + d["Firm Type"] +
"Nature of Misstatement: " + d["Nature of Misstatement"] +
"Settlement Timing: " + d["Settlement Timing"];
if (d["Mediator"])
tooltipContents += "Mediator: " + d["Mediator"] + "";
return tooltipContents;
})
// remove all other mouseover text, in case it's left behind
d3.selectAll("foreignObject").filter(function () {
return (d3.select(this).node() !== mouseoverTextObject.node());
}).remove();
var circleUnderMouse = this;
d3.selectAll("circle").transition(transitionDuration).style("opacity", function () {
// comparing selections doesn't work
return (d3.select(this).node() === mouseoverCircle.node()) ? 1.0 : 0.1;
})
d3.selectAll(".blgroup text").transition(transitionDuration).style("opacity", 0);
})
// initial text
var visibleText = groups.append("text")
.text(function (d) {
if (d.size > 1000) {
// return d.Issuer.substring(0,24);
return d.Issuer;
}
})
.attr("font-size", "14px")
.attr("text-anchor", "middle")
.attr("x", "20")
// if the text is wider than the bubble plus some amount, hide it
// the extra attribute 'hidden' stores the 'last word' in whether the text is displayed
// i.e. no matter what, if the text is too wide, it should not be shown. thus, the code
// that hides elements on mouseover should reference this attribute.
.attr("defaultOpacity", function (d) {
if (this.getBBox().width > this.parentNode.children[0].getBBox().width + 50) {
return 0;
} else return 1;
})
.style("opacity", function (d) {
return d3.select(this).attr("defaultOpacity");
})
});