block by jwilber 77337df96fcd953e6320efbbc19a5433

basic joy plot

Full Screen

Built with blockbuilder.org

index.html

<!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>

joyploy.js

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}
    };