Skip to content

Instantly share code, notes, and snippets.

@jwilber
Last active May 4, 2019 18:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jwilber/77337df96fcd953e6320efbbc19a5433 to your computer and use it in GitHub Desktop.
Save jwilber/77337df96fcd953e6320efbbc19a5433 to your computer and use it in GitHub Desktop.
basic joy plot
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
/* .y-axis text, .x-axis text {
fill: white;
opacity: .85;
}
.y-axis line, .y-axis path { opacity: 0}
.x-axis line, .x-axis path {
stroke: white;
opacity: .85;
} */
.y-axis, .x-axis {
font-family: 'arial'
}
</style>
</head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<body></body>
<!-- <script src="joyplot.js"></script> -->
<script>
// set the dimensions and margins of the graph
var margin = {top: 120, right: 30, bottom: 20, left:110},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style('background-color', 'white')
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//read data
d3.csv("https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv", function(data) {
// Get the different categories and count them
var categories = data.columns.filter(d => !isNaN(data[0][d]))
var n = categories.length
const {min, max} = csvExtent(data)
// Add X axis
var x = d3.scaleLinear()
.domain([min - 15, max + 15])
.range([ 0, width ]);
svg.append("g")
.attr('class', 'x-axis')
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Create a Y scale for densities
var y = d3.scaleLinear()
.domain([0, .25])
.range([ height, 0]);
// Create the Y axis for names
var yName = d3.scaleBand()
.domain(categories)
.range([0, height])
.paddingInner(1)
svg.append("g")
.attr('class', 'y-axis')
.call(d3.axisLeft(yName));
// Compute kernel density estimation for each column:
var kde = kernelDensityEstimator(kernelEpanechnikov(7), x.ticks(40)) // increase this 40 for more accurate density.
var allDensity = []
for (i = 0; i < n; i++) {
key = categories[i]
density = kde( data.map(function(d){ return d[key]; }) )
allDensity.push({key: key, density: density})
}
// Add areas
svg.selectAll("areas")
.data(allDensity)
.enter()
.append("path")
.attr("transform", function(d){return("translate(0," + (yName(d.key)-height) +")" )})
.datum(d => d.density)
.attr("fill", "skyblue")
.attr('opacity', 1)
.attr("stroke", "azure")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveStep)
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); })
)
}) // end csv load
// This is what I need to compute kernel density estimation
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(function(x) {
return [x, d3.mean(V, function(v) { return kernel(x - v); })];
});
};
}
function kernelEpanechnikov(k) {
return function(v) {
return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0;
};
}
function csvExtent(data) {
const colNames = data.columns
const extents = colNames.map(col => d3.extent(data, d => +d[col]));
const csvMin = d3.min(extents, d => d[0])
const csvMax = d3.max(extents, d => d[1])
return {'min': csvMin, 'max': csvMax}
}
</script>
class Chart {
constructor(opts) {
this.element = opts.element;
this.x = opts.x;
this.y = opts.y;
d3.csv(opts.data, (d) => {
this.data = d;
this.draw();
});
}
draw() {
this.width = 750;
this.height = this.width / 1.5;
this.margin = {
top: 50,
bottom: 50,
right: 50,
left: 50
};
this.element.innerHTML = '';
const svg = d3.select(this.element).append('svg');
svg.attr('width', this.width);
svg.attr('height', this.height);
this.plot = svg.append('g')
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`)
this.createScales();
this.addAxes();
// this.addScatter();
}
createScales() {
var categories = this.data.columns.filter(d => !isNaN(this.data[0][d]))
var n = categories.length
const {min, max} = csvExtent(this.data)
var this.x = d3.scaleLinear()
.domain([min - 15, max + 15])
.range([ 0, this.width ]);
// Create a Y scale for densities
var this.y = d3.scaleLinear()
.domain([0, .25])
.range([ this.height, 0]);
// Create the Y axis for names
var this.yName = d3.scaleBand()
.domain(categories)
.range([0, this.height])
.paddingInner(1)
};
addAxes() {
svg.append("g")
.attr('class', 'x-axis')
.attr("transform", "translate(0," + this.height + ")")
.call(d3.axisBottom(this.x));
svg.append("g")
.attr('class', 'y-axis')
.call(d3.axisLeft(this.yName));
};
addDensities() {
this.plot.selectAll('circle')
.data(this.data).enter()
.append('circle')
.attr("cx", d => this.xScale(+d[this.x]))
.attr("cy", d => this.yScale(+d[this.y]))
.attr("r", 4);
};
}
csvExtent(data) {
const colNames = data.columns
const extents = colNames.map(col => d3.extent(data, d => +d[col]));
const csvMin = d3.min(extents, d => d[0])
const csvMax = d3.max(extents, d => d[1])
return {'min': csvMin, 'max': csvMax}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment