D3Kit Histogram with Mithril

by ramnathv

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/mithril/0.2.3/mithril.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//cdn.rawgit.com/twitter/d3kit/master/dist/d3kit.min.js"></script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.6/paper/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jstat/1.5.2/jstat.min.js"></script>
<script src="//rawgit.com/egtork/Rands.js/master/rands.min.js"></script>
<div class="container" id="main">
  <div class="row">
    <div class="col-xs-12 col-md-6">
      <div id="app"></div>
    </div>
  </div>
</div>

CSS

#main{margin-top: 20px;}
.y-axis-layer path,
.y-axis-layer line{
  fill: none;
}
.y-axis-layer line{
  stroke: white;
}
.y-axis-layer text{
  font-size: 11px;
}

.x-axis-layer line,
.x-axis-layer path{
  fill: none;
}
.x-axis-layer line{
  stroke: white;
}
.x-axis-layer text{
  font-size: 11px;
}

/* Tooltip */
.d3-tip {
   line-height: 1;
   font-weight: bold;
   padding: 8px;
   background: darkgreen;
   border-color: white;
   color: #fff;
   border-radius: 2px;
 }


/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: darkgreen;
  content: "\25BC";
  position: absolute;
  text-align: center;
}

/* Style northward tooltips differently */
.d3-tip.n:after {
  margin: -3px 0 0 0;
  top: 100%;
  left: 0;
}

.panel-heading{
  padding-bottom: 0px;
}
.panel-body{
  padding-top: 0px;
}

Babel + JSX

/** @jsx m */
const chartDefaults = {
  margin: {left: 30, right: 20, bottom: 20, top: 20},
  initialWidth: "auto",
  initialHeight: 300
}

const chartConstructor = (skeleton) => {
  // Set up scales
  const S = {x: d3.scale.linear(), y: d3.scale.linear()}
  const A = {x: d => d.x, y: d => d.y}
  
  // Create layers
  const L = skeleton.getLayerOrganizer()
  L.create(['background', 'y-axis', 'x-axis', 'bars'])
  L.get('background').append('rect')
  const visualize = d3Kit.helper.debounce(() => {
    // get data, width and height
    const data = skeleton.data()
    const W = skeleton.getInnerWidth()
    const H = skeleton.getInnerHeight()
    const opts = skeleton.options()
    
    const numBins = x =>
      Math.ceil(Math.log(x.length)/Math.log(2) + 1)
      
    S.x.range([0, W])
      .domain([d3.min(data)*0.95, d3.max(data)*1.15])
    const histData = d3.layout.histogram()
      .bins(S.x.ticks(numBins(data)))
      (data)
    S.y.range([H, 0]).domain([0, d3.max(histData, A.y)*1.1])
    
    L.get('background')
      .select('rect')
      .attr({
        width: W,
        height: H,
        fill: "#ebebeb"
      })
      
    // Render bars corresponding to the histogram
    const bars = L.get('bars').selectAll(".bar").data(histData)
    bars.enter().append("rect")
      .classed("bar", true)
      .attr({y: H, height: 0}) // this line is responsible for initial transition
    bars.attr({
      x: d => S.x(A.x(d)) + 1,
      width: S.x(A.x(histData[1])) - S.x(A.x(histData[0])) - 1,
      fill: opts.fill || "steelblue"
    })
    bars.transition().attr({
      y: d => S.y(A.y(d)),
      height: d => H - S.y(A.y(d)),
    })
    
    bars.exit().remove()
    
    // Create a tooltip and add it to the bars
    const tip = d3.tip()
      .attr("class", "d3-tip")
      .offset([-10, 0])
      .html(d => `${d.y} values in (${d.x}, ${d.x + d.dx}]`)
    skeleton.getRootG().call(tip)
    L.get("bars").selectAll(".bar")
      .on("mouseover", tip.show)
     ...