Published
Edited
Apr 30, 2019
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = (await fetch(`https://raw.githubusercontent.com/Jianan-Li/Kickstarter-Time-Series-Analysis/master/df_1.json`)).json().then((json) => json.map(histogram))
Insert cell
boxplot = (g) => {
g.append("line")
.attr("class", "guide")
.attr("x1", bandwidth / 2)
.attr("y1", (d) => y(d.min))
.attr("x2", bandwidth / 2)
.attr("y2", (d) => y(d.max))
.attr("stroke", "steelblue");
g.append("line")
.attr("class", "whisker")
.attr("x1", bandwidth / 2)
.attr("y1", (d) => y(d.range[1]))
.attr("x2", bandwidth / 2)
.attr("y2", (d) => y(d.range[0]))
.attr("stroke", "green");

g.append("line")
.attr("class", "range-bot")
.attr("x1", 0)
.attr("y1", (d) => y(d.range[0]))
.attr("x2", bandwidth)
.attr("y2", (d) => y(d.range[0]))
.attr("stroke", "steelblue");

g.append("line")
.attr("class", "range-bot")
.attr("x1", 0)
.attr("y1", (d) => y(d.range[1]))
.attr("x2", bandwidth)
.attr("y2", (d) => y(d.range[1]))
.attr("stroke", "steelblue");

g.append("path")
.attr("class", "quartile")
.attr("d", (d) => `M0,${ y(d.quartiles[2]) }h${ bandwidth }V${ y(d.quartiles[0]) }H0Z`)
.attr("data-tooltip", (d) => quartileTooltip(d))
.attr("data-pid", (d) => d.pid)
.on("mouseover", onMouseOverQuartile)
.on("mousemove", onMouseMoveQuartile)
.on("mouseout", onMouseOutQuartile);
g.append("line")
.attr("class", "median")
.attr("x1", 0)
.attr("y1", (d) => y(d.quartiles[1]))
.attr("x2", bandwidth)
.attr("y2", (d) => y(d.quartiles[1]));

g.selectAll('.outlier')
.data(d => d.outliers)
.enter()
.append('circle')
.attr('class', 'outlier')
.attr('r', 3)
.attr('cx', (d, i) => {
// alternate the x position of each
// outlier so they overlap less
return (bandwidth/2) + offsets[i%6];
})
.attr('cy', (d) => y(d['usd_goal_real']))
.attr("data-tooltip", (d) => title(d))
.attr("data-pid", (d) => d['ID'])
.on("mouseover", onMouseOverOutlier)
.on("mousemove", onMouseMoveOutlier)
.on("mouseout", onMouseOutOutlier);
}
Insert cell
offsets = [-15,-9,-3,3,9,15]
Insert cell
function onMouseOutQuartile (d) {
tooltip.text("");
// d3.selectAll(`circle.outlier[data-pid="${ d.pid }"]`).classed('active', false);
return tooltip.style("visibility", "hidden");
}
Insert cell
function onMouseMoveQuartile (d) {
return tooltip.style("top", (d3.event.pageY+20)+"px").style("left",(d3.event.pageX+10)+"px");
}
Insert cell
function onMouseOverQuartile (d) {
tooltip.text(this.dataset.tooltip);
// d3.selectAll(`circle.outlier[data-pid="${ d.pid }"]`).classed('active', true);
return tooltip.style("visibility", "visible");
}
Insert cell
function onMouseOutOutlier (d) {
tooltip.text("");
d3.selectAll(`circle.outlier[data-pid="${ d['ID'] }"]`).classed('active', false);
return tooltip.style("visibility", "hidden");
}
Insert cell
function onMouseMoveOutlier (d) {
return tooltip.style("top", (d3.event.pageY+20)+"px").style("left",(d3.event.pageX+10)+"px");
}
Insert cell
function onMouseOverOutlier (d) {
tooltip.text(this.dataset.tooltip);
d3.selectAll(`circle.outlier[data-pid="${ d['ID'] }"]`).classed('active', true);
return tooltip.style("visibility", "visible");
}
Insert cell
tooltip = d3.select("body").append("div")
.attr("class", "svg-tooltip")
.text("")
Insert cell
bandwidth = x.bandwidth()
Insert cell
t = d3.transition().duration(250)
Insert cell
x = d3.scaleBand()
.range([margin.left, width - margin.right])
.padding(0.3)
.domain(data.map((s) => s.group))
Insert cell
y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, (yAxisZoom === '0' ? 50000 : d3.max(data, (d) => d.max))]).nice()
Insert cell
xAxis = d3.axisBottom(x).tickSize(0)
Insert cell
yAxis = d3.axisLeft(y).tickSize(-width+margin.left+margin.right).tickFormat((d) => "$" + d3.format(",")(d) );
Insert cell
quartileTooltip = (d) =>
`Max: $${ d3.format(",.0f")(d.range[1])}
Q3: $${ d3.format(",.0f")(d.quartiles[2]) }
Median: $${ d3.format(",.0f")(d.quartiles[1]) }
Q1: $${ d3.format(",.0f")(d.quartiles[0]) }
Min: $${ d3.format(",.0f")(d.range[0]) }
`
Insert cell
title = (d) =>
`${ d.name }
${ d.main_category } / ${ d.category }
${ (d.currency==='USD') ? 'US$ '+numberWithCommas(d.goal) : d.currency + ' ' + numberWithCommas(d.goal) + ' (US$ '+numberWithCommas(d['usd_goal_real'])+')' }
Launched on ${new Date(d.launched).getMonth()+'/'+new Date(d.launched).getDate()+'/'+new Date(d.launched).getFullYear()}
${ capitalize(d.state) }
`
Insert cell
Insert cell
// this function maps each season
// to individual box & whisker plots
function histogram (year) {
// sort players by points asc
let projects = year.projects.sort((a,b) => a['usd_goal_real'] - b['usd_goal_real']);

// map points to an array
let stat = projects.map((sk) => sk['usd_goal_real']);

// get the min and max
let min = stat[0];
let max = stat[stat.length-1];
// quantiles
let q1 = d3.quantile(stat, 0.25);
let q2 = d3.quantile(stat, 0.50);
let q3 = d3.quantile(stat, 0.75);

// interquartile range
let iqr = q3 - q1;
// range
let r0 = Math.max(min, q1 - iqr * 1.50);
let r1 = Math.min(max, q3 + iqr * 1.50);
let group = {
group: year.launch_year,
projects,
min,
max,
quartiles: [q1,q2,q3],
range: [r0,r1],
iqr,
outliers: projects.filter(sk => sk['usd_goal_real'] > r1)
}
return group;
}
Insert cell
width
Insert cell
height = width * 0.55
Insert cell
margin = ({ top:20, right:80, bottom:100, left:120 })
Insert cell
graphWidth = width - margin.left - margin.right
Insert cell
graphHeight = height - margin.top - margin.bottom
Insert cell
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
Insert cell
// Use jQuery to select all element on the page and set font family
$( "*" ).css( "font-family", "-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" )
Insert cell
// Import jQuery for both selection and easy text fade in and fade out transitions
$ = require("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js")
Insert cell
import {slider, radio, button} from "@jashkenas/inputs"
Insert cell
Insert cell
styles = html`
<style>
.domain {
visibility: hidden;
}

.whisker {
stroke: #024752;
stroke-width: 1px;
}

.range-top,
.range-bot {
stroke: #024752;
stroke-width: 1px;
}

.guide {
visibility: hidden;
stroke: #bababa;
stroke-dasharray: 1 2;
}

.quartile {
fill: #0395AB;
stroke: #0395AB;
fill-opacity: 1;
stroke-width: 0px;
}

.median {
stroke: #ffffff;
stroke-width: 2px;
}

.outlier {
fill: #dadada;
stroke: #c8c8c8;
cursor: pointer;
}

.outlier.active {
fill: #0395AB;
stroke: #0395AB;
}
.y-axis-label,
.x-axis-label {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 12px;
text-transform: uppercase;
}

.y-axis line {
stroke-dasharray: 2 2;
stroke: #bababa;
}

.y-axis .domain {
fill-opacity: 0;
stroke-opacity: 0;
}

.svg-tooltip {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background: rgba(69,77,93,.9);
border-radius: .1rem;
color: #fff;
display: block;
font-size: 11px;
max-width: 500px;
padding: .2rem .4rem;
position: absolute;
text-overflow: ellipsis;
white-space: pre;
z-index: 300;
visibility: hidden;
}
</style>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more