Home > Blockchain >  How can you draw a collection of lines and points based on categorical data in D3?
How can you draw a collection of lines and points based on categorical data in D3?

Time:05-28

I am new to JavaScript/D3, and I am trying to code a relatively simple visualization based on some toy data I have, but cannot figure it out. This is the toy data:

toy_data = {
  'abstract' : ['a'],
  'introduction' : ['b', 'c'],
  'methods' : ['d', 'e', 'f'],
  'findings' : ['g', 'h', 'i', 'j'],
  'conclusion' : ['k', 'l', 'm', 'n', 'o']
  }

I want to write a function that takes in data formatted like this (or in a slightly different way, if necessary) and generates a visualization in D3 that looks roughly like the following (I generated this plot in Altair using similar data):

enter image description here

The difference would be that the x axis labels would correspond to the terms specified in my toy data, and I would only have as many circles as there are items in each associated key-value pair (e.g. 1 circle for abstract, 2 for introduction, 3 for methods, etc.). Eventually, I also will add additional data to control the size of the circles as in the image above, but for the moment I am satisfied with just a function that can take in any data of this form and at least generate a prototype with circles of the same size.

I feel like this should be fairly doable in D3, perhaps by defining a scatter plot and then adding connecting horizontal lines, or by combining a line plot and scatter plot, but I cannot figure out how to do it. If someone can explain and give an example of how to achieve this behavior, that would be extremely helpful, as I think I can build off of it to achieve what I want.

CodePudding user response:

This will get you started, though I'd suggest you rework your data. Most D3 patterns begin with either arrays of arrays or arrays of objects. Objects containing arrays of various lengths will be difficult to work with.

const toy_data = {
  'abstract' : ['a'],
  'introduction' : ['b', 'c'],
  'methods' : ['d', 'e', 'f'],
  'findings' : ['g', 'h', 'i', 'j'],
  'conclusion' : ['k', 'l', 'm', 'n', 'o']
  }
  
 const margin = {top:10,left:90,bottom:25,right:10},
  width = 480 - margin.top - margin.bottom,
  height = 250 - margin.left - margin.right;
  
 const svg = d3.select('#chart')
  .append('svg')
  .attr('width', width   margin.left   margin.right)
  .attr('height', height   margin.top   margin.bottom)
  .append('g')
  .attr('transform','translate('   margin.left   ','   margin.top   ')')
 
 let rows = Object.getOwnPropertyNames(toy_data) 
 let cols = [];
 

 for(prop in toy_data) {
  cols = cols.concat(toy_data[prop])
 }
 const x = d3.scaleBand()
  .domain(cols)
  .range([0,width])
  
 const y = d3.scaleBand()
  .domain(rows)
  .range([0,height])
  
 svg.append('g')
  .call(d3.axisLeft(y))
 
 svg.append('g')
  .attr('transform','translate(0,'   height   ')')
  .call(d3.axisBottom(x))
  
for(prop in toy_data) {

  svg.selectAll(null)
    .data(toy_data[prop])
    .enter()
    .append('circle')
    .attr('cx', d => x(d)   x.bandwidth()/2)
    .attr('cy', y(prop)   y.bandwidth()/2)
    .attr('r', x.bandwidth()/4)
    .style('fill','steelblue')

}
 
 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"><div>

UPDATE: I don't know the constraints of your data, but strictly from a D3 perspective, this would be easier to work with:

const toy_data = {
  'abstract' : ['a'],
  'introduction' : ['b', 'c'],
  'methods' : ['d', 'e', 'f'],
  'findings' : ['g', 'h', 'i', 'j'],
  'conclusion' : ['k', 'l', 'm', 'n', 'o']
 }
 
 let d3_data = [];
 for(prop in toy_data) {
   d3_data = d3_data.concat(toy_data[prop].map(d=> [prop, d]))
 }
 

      

  • Related