(d3) Binning dinâmico para linha do tempo com zoom de eixo único?

Novíssimo na D3 aqui ... Estou tentando criar uma linha do tempo de eixo único com binninge ampliação. Eu tenho uma prova de conceito trabalhandosem binning:

const data = [
  {
    assessment_date: "2018-04-19T00:31:03.153000Z",
    score: 4,
    type: "formative",
    is_proficient: false,
    label: "a",
    id: 1
  }, {
    assessment_date: "2017-11-20T09:51:36.035983Z",
    score: 3,
    type: "summative",
    is_proficient: false,
    label: "b",
    id: 2,
  }, {
    assessment_date: "2018-02-15T09:51:36.035983Z",
    score: 3,
    type: "formative",
    is_proficient: true,
    label: "c",
    id: 3,
  }, {
    assessment_date: "2018-02-20T09:51:36.035983Z",
    score: 3,
    type: "summative",
    is_proficient: true,
    label: "d",
    id: 4,
  }, {
    assessment_date: "2018-03-19T17:48:44.820000Z",
    score: 4,
    type: "summative",
    is_proficient: false,
    label: "e",
    id: 5
  }
];

const byDate = o => o.assessment_date;

const sortedData = data.map(o => Object.assign({}, o, {
  "assessment_date": new Date(o.assessment_date)
})).sort((a,b) => a.assessment_date - b.assessment_date);

const NODE_RADIUS = 6;
const WIDTH = 600;
const HEIGHT = 30;

const xScale = d3.time.scale()
  .domain(d3.extent(sortedData.map(byDate)))
  .range([0, WIDTH])
  .nice();

const xAxis = d3.svg.axis()
  .scale(xScale)
  .orient('bottom');

const zoom = d3.behavior.zoom()
  .x(xAxis.scale())
  .on("zoom", function() {
    axisSelector.call(xAxis);
    nodesSelector.attr('cx', o => {
      return xScale(o.assessment_date)
    });
  });

const svg = d3.select("#timeline")
  .append("svg")
  .attr("width", WIDTH)
  .attr("height", HEIGHT)
  .attr("padding-top", "10px")
  .attr("transform", "translate(0," + (HEIGHT) + ")")
  .call(zoom);

const axisSelector = svg.append('g')
  .attr("class", "x axis")
  .call(xAxis);

const nodesSelector = svg.selectAll(".node")
  .data(sortedData)
  .enter()
    .append("circle")
    .attr('id', o => `node${o.id}`)
    .attr('class', o => {
      let cx = ['node'];
      (o.type === 'formative') ? cx.push('formative') : cx.push('summative');
      (o.is_proficient) ? cx.push('proficient') : cx.push('not-proficient');
      return cx.join(' ');
    })
    .attr("r", 8)
    .attr("cx", o => xScale(o.assessment_date))

nodesSelector.on("click", function(node) {
  console.log('boop!')
});
#timeline {
  overflow: hidden;
}

#timeline svg {
  padding: 15px 30px;
  overflow: hidden;
}

.axis text {
  font-family: sans-serif;
  font-size: 10px;
}

.axis path,
.axis line {
  stroke: 3px;
  fill: none;
  stroke: black;
  stroke-linecap: round;  
}

.node {
  stroke-width: 3px;
  stroke: white;
}

.node.proficient {
  fill: green;
  stroke: green;
}

.node.not-proficient {
  fill: orange;
  stroke: orange;
}

.node.summative {
  stroke: none;
}

.node.formative {
  fill: white;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="timeline"></div>

Na produção, lidarei com muitos dados e precisarei agrupar os nós em um grupo (exibindo um número acima do grupo, indicando quantos nós em um grupo).

Minha primeira tentativa é aqui:

const data = [
  {
    assessment_date: "2018-04-19T00:31:03.153000Z",
    id: 1
  }, {
    assessment_date: "2017-11-20T09:51:36.035983Z",
    id: 2,
  }, {
    assessment_date: "2018-02-15T09:51:36.035983Z",
    id: 3,
  }, {
    assessment_date: "2018-02-20T09:51:36.035983Z",
    id: 4,
  }, {
    assessment_date: "2018-03-19T17:48:44.820000Z",
    id: 5
  }
];

const byDate = datum => datum.assessment_date;

const sortedData = data.map(datum => Object.assign({}, datum, {
  "assessment_date": new Date(datum.assessment_date)
})).sort((a,b) => a.assessment_date - b.assessment_date);

const NODE_RADIUS = 6;
const WIDTH = 600;
const HEIGHT = 30;

const xScale = d3.time.scale()
  .domain(d3.extent(sortedData.map(byDate)))
  .range([0, WIDTH])
  // .nice();

const xAxis = d3.svg.axis()
  .scale(xScale)
  .orient('bottom');

const histogram = d3.layout.histogram()
  .value(datum => datum.assessment_date)
  .range(xAxis.scale().domain())

const zoom = d3.behavior
  .zoom()
  .x(xScale)
  .on("zoom", function() {
    axisSelector.call(xAxis);
    update(histogram(sortedData));
  });

const svg = d3.select("#timeline")
  .append("svg")
  .attr("width", WIDTH)
  .attr("height", HEIGHT)
  .attr("padding-top", "10px")
  // .attr("transform", "translate(0," + (HEIGHT) + ")")
  .call(zoom);

const axisSelector = svg.append('g')
  .attr("class", "x axis")
  .call(xAxis);

function update(data) {
  const node = svg.selectAll(".node").data(data);
  const nodeLabel = svg.selectAll(".node-label").data(data);

  node.enter()
      .append("circle")
      .attr("class", "node")
      .attr("r", NODE_RADIUS)
      .attr("style", datum => !datum.length && 'display: none')
      // ^ this seems inelegant. why are some bins empty?
      .attr("cx", datum => xScale(datum.x))
  
  node.enter()
      .append("text")
      .attr("class", "node-label")
      .text(datum => datum.length > 1 ? `${datum.length}` : '')
      .attr("x", datum => xScale(datum.x) - NODE_RADIUS/2)
      .attr("y", "-10px")
  
  node.attr("cx", datum => xScale(datum.x));
  nodeLabel.attr("x", datum => xScale(datum.x) - NODE_RADIUS/2);
  return node;
}

const nodeSelector = update(histogram(sortedData));
#timeline {
  overflow: hidden;
}

#timeline svg {
  padding: 20px 30px;
  overflow: hidden;
}

.axis text {
  font-family: sans-serif;
  font-size: 10px;
}

.axis path,
.axis line {
  stroke: 3px;
  fill: none;
  stroke: black;
  stroke-linecap: round;  
}

.node {
  stroke-width: 3px;
  stroke: white;
}

.node-label {
  font-family: sans-serif;
  font-size: 11px;
}

.node.proficient {
  fill: green;
  stroke: green;
}

.node.not-proficient {
  fill: orange;
  stroke: orange;
}

.node.summative {
  stroke: none;
}

.node.formative {
  fill: white;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="timeline"></div>

Parece agrupar os nós próximos bem o suficiente, mas não agrupa / desagrupa no zoom. Alguma idéia ou exemplo? Venho vasculhando blocos e google por horas.

Um histograma com escaninhos é o primitivo correto para o comportamento que estou buscando? Aqui está um ótimo exemplo do que estou procurando, caso não esteja claro:http://www.iftekhar.me/ibm/ibm-project-timeline/ … Navegue para baixo até oFinal Iteration seção.

Finalmente, estou usando o D3v3.x como ainda não atualizamos nossa dependência.

questão bônus: por que alguns dos compartimentos do histograma estão vazios?

questionAnswers(1)

yourAnswerToTheQuestion