Luis Carli's
Copy/Paste Visualizations

For prototyping, the fast you can spin up a new project the better. I wanted to see how it would look like to quickly develop data visualizations by copy and pasting small snippets of code into an HTML file. How concise can you go with modern browsers while keeping the legibility and extendability of the code?

The answer is a lot! Here is the result of this experiment.

To see the interactive version, open the site in a bigger window

This code blocks contain all the HTML you need to start

  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script>
  d3.csv("data.csv", function(data){
    histogram(data, "value", "name")
  })
  function histogram(data, key){ … }
</script>
Load the D3 library
Fetch your data,
remember to parse and transform it as needed
Call the plot function,
pass the data keys you want to use on the visualization
And here's the full plot function,
extend and modify it as needed

First look at the data

When getting a new dataset, start by inspecting the overall format of the data, how its dimensions look like and relate to each other. The plots in this section give this broader look, by visualizing multiple data dimensions at once, according to the type of the data. After getting the big picture, move to more specialized and focused charts. The plots on this section are not as strip down as the plots on the next section, as their purpose is to help you pick which dimensions are worth exploring further.

Distributions

See how the values on the numeric dimensions of your dataset are distributed, call this plot function with an array of the keys on your dataset that contain numeric values, for each key it generates a small histogram. Small bonus, the top of the histograms contain a minimalistic box-plot.

ABC
123123123
123123123
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    distributions(data, ["A", "B", "C"])
  })
  function distributions(data, keys){  
    d3.select("body").append("div")
      .selectAll("svg").data(keys)
      .enter().append("svg")
        .each(distribution)

    function distribution(xKey){
      var values = data.map(d => d[xKey])
      values.sort(d3.ascending)
      var box = [
        d3.quantile(values, 0.25),
        d3.quantile(values, 0.5),
        d3.quantile(values, 0.75),
      ]

      var margin = {top: 10, right: 10, bottom: 35, left: 25},
          width = 150,
          height = 50;

      var x = d3.scaleLinear()
          .domain(d3.extent(data, d => d[xKey]))
          .rangeRound([0, width])
      var bins = d3.histogram()
          .domain(x.domain())
          .value(d => d[xKey])
        (data)
      var y = d3.scaleLinear()
          .domain([0, d3.max(bins, d => d.length)])
          .range([height, 0])

      var g = d3.select(this)
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
        .append("g")
          .attr("transform", `translate(${margin.left},${margin.top})`)
      // plot
      var rect = g.selectAll("rect").data(bins)
        .enter().append("rect")
          .attr("x", d => x(d.x0))
          .attr("y", d => Math.min(y(0), y(d.length)))
          .attr("width", d => x(d.x1) - x(d.x0))
          .attr("height", d => Math.abs(y(d.length) - y(0)))
      // upper box-plot
      g.append("path")
          .attr("d", `M0,0 H${x(box[0])} V-2.5 H${x(box[1])-1} m2,0 H${x(box[2])} V0 H${width}`)
          .attr("fill", "none")
          .attr("stroke", "black")
      // axis
      g.append("g")
          .attr("transform", `translate(0,${height})`)
          .call(d3.axisBottom(x).ticks(width/45, "s").tickSize(-height).tickPadding(6))
        .append("text")
          .text(xKey)
          .attr("fill", "black")
          .attr("transform", `translate(${width/2},28)`)
      g.append("g")
          .call(d3.axisLeft(y).ticks(height/35, "s").tickSizeInner(-width).tickSizeOuter(0).tickPadding(6))
      g.selectAll(".tick line")
          .attr("stroke-dasharray", "1,2")
    }
   }
</script>

Relations

See how the numeric dimensions of your dataset relate to each other, call this plot function with an array of the keys on your dataset that contain numeric values, for each combination it generates a small scatter plot. The resulting matrix smartly skips unneeded permutations.

ABC
123123123
123123123
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    relations(data, ["A", "B", "C"])
  })
  function relations(data, keys){ 
    var cols = keys.slice(0,-1)
    var rows = keys.slice(1)
    var permutations = []
    rows.forEach(function(row, rowIdx){
      return cols.forEach(function(col, colIdx){
        if(colIdx > rowIdx) return 
        permutations.push({col: col, row: row, rowIdx: rowIdx, colIdx: colIdx})
      })
    })
    var domains = {}
    keys.forEach(function(key){domains[key] = d3.extent(data, d => d[key])})

    var n = keys.length - 1
    var margin = {top: 10, right: 10, bottom: 35, left: 52},
        cellSize = 125,
        gap = 10,
        width = height = n * cellSize + gap * (n-1);

    var x = d3.scaleLinear()
        .range([0, cellSize])
    var y = d3.scaleLinear()
        .range([cellSize, 0])

    var g = d3.select("body").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})`)
    // cells
    g.selectAll("g.cell").data(permutations)
      .enter().append("g")
        .attr("class", "cell")
        .attr("transform", d => `translate(${d.colIdx*(cellSize+gap)},${d.rowIdx*(cellSize+gap)})`)
        .each(function(d){
          x.domain(domains[d.col])
          y.domain(domains[d.row])
          d3.select(this)
            .selectAll("circle").data(data)
            .enter().append("circle")
              .attr("cx", v => x(v[d.col]))
              .attr("cy", v => y(v[d.row]))
              .attr("r", 2)
        })
    // axis
    g.selectAll("g.x.axis").data(cols)
      .enter().append("g")
        .attr("class", "x axis")
        .attr("transform", (d,i) => `translate(${i*(cellSize+gap)},${height})`)
        .each(function(d,i){
          x.domain(domains[d])
          d3.select(this)
              .call(d3.axisBottom(x).ticks(cellSize/45, "s").tickSize(-height).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(${cellSize/2},28)`)
    g.selectAll("g.y.axis").data(rows)
      .enter().append("g")
        .attr("class", "y axis")
        .attr("transform", (d,i) => `translate(0,${i*(cellSize+gap)})`)
        .each(function(d,i){
          y.domain(domains[d])
          d3.select(this)
              .call(d3.axisLeft(y).ticks(cellSize/35, "s").tickSize(-width).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(-38,${cellSize/2}) rotate(-90)`)
        .attr("text-anchor", "middle")
    g.selectAll(".tick line")
        .attr("stroke-dasharray", "1,2")
   }
</script>

Time-Series

See how the time based dimensions of your dataset relate with the numeric dimensions, call this plot function with an array of the keys on your dataset that contain time based values for populating the x axis and an array of keys containing numeric values for populating the y axis. For each plot the data will be sorted in place according to its dates.

ABCDE
123123123datedate
123123123datedate
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    timeSeries(data, ["D", "E"], ["A", "B", "C"])
  })
  function timeSeries(data, xKeys, yKeys){ 
    var permutations = []
    xKeys.forEach(function(xKey, xI){
      return yKeys.forEach(function(yKey, yI){
        permutations.push({xKey: xKey, xI: xI, yKey: yKey, yI: yI})
      })
    })
    var domains = {}
    xKeys.concat(yKeys)
      .forEach(function(key){domains[key] = d3.extent(data, d => d[key])})

    var nX = xKeys.length,
        nY = yKeys.length;
    var cellHeight = 75,
        cellWidth = 150,
        gap = 10;
    var margin = {top: 10, right: 10, bottom: 35, left: 52},
        width = nX * cellWidth + (nX-1) * gap,
        height = nY * cellHeight + (nY-1) * gap;

    var x = d3.scaleTime()
        .range([0, cellWidth])
    var y = d3.scaleLinear()
        .range([cellHeight, 0])
    var line = d3.line()

    var g = d3.select("body").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})`)
    // cells
    g.selectAll("g.cell").data(permutations)
      .enter().append("g")
        .attr("class", "cell")
        .attr("transform", d => `translate(${d.xI*(cellWidth+gap)},${d.yI*(cellHeight+gap)})`)
        .each(function(d){
          data.sort((a,b) => d3.ascending(a[d.xKey], b[d.xKey]))
          x.domain(domains[d.xKey])
          y.domain(domains[d.yKey])
          line.x(v => x(v[d.xKey])).y(v => y(v[d.yKey]))
          d3.select(this).append("path")
              .attr("d", line(data))
              .attr("stroke", "black")
              .attr("fill", "none")
        })
    // axis
    g.selectAll("g.x.axis").data(xKeys)
      .enter().append("g")
        .attr("class", "x axis")
        .attr("transform", (d,i) => `translate(${i*(cellWidth+gap)},${height})`)
        .each(function(d){
          x.domain(domains[d])
          d3.select(this)
              .call(d3.axisBottom(x).ticks(cellWidth/45).tickSize(-height).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(${cellWidth/2},28)`)
    g.selectAll("g.y.axis").data(yKeys)
      .enter().append("g")
        .attr("class", "y axis")
        .attr("transform", (d,i) => `translate(0,${i*(cellHeight+gap)})`)
        .each(function(d,i){
          y.domain(domains[d])
          d3.select(this)
              .call(d3.axisLeft(y).ticks(cellHeight/35, "s").tickSize(-width).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(-38,${cellHeight/2}) rotate(-90)`)
        .attr("text-anchor", "middle")
    g.selectAll(".tick line")
        .attr("stroke-dasharray", "1,2")
   }
</script>

Categories

See the distribution of categories in your data, call this plot function with an array of the keys on your dataset that contain text values, the incidence of each category will be counted and displayed in a bar chart, sorted from the least common to the more common.

FG
abcabc
abcabc
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    categories(data, ["F", "G"])
  })
  function categories(data, keys){ 
    d3.select("body").append("div")
      .selectAll("svg").data(keys)
      .enter().append("svg")
        .each(category)

    function category(key){
      var countData = d3.nest()
          .key(d => d[key]).rollup(d => d.length)
        .entries(data)
          .sort((a,b) => d3.descending(a.value, b.value))

      var margin = {top: 10, right: 10, bottom: 35, left: 50},
          width = 150,
          height = 13 * countData.length;

      var x = d3.scaleLinear()
          .domain([
            Math.min(0, d3.min(countData, d => d.value)),
            Math.max(0, d3.max(countData, d => d.value))
          ])
          .range([0, width])
      var y = d3.scaleBand()
          .domain(countData.map(d => d.key))
          .range([0, height])

      var svg = d3.select(this)
      var g = svg.append("g")
      // yAxis parametric sizing
      var yAxis = g.append("g")
          .call(d3.axisLeft(y).tickPadding(6))
      margin.left = yAxis.node().getBoundingClientRect().width+25
      yAxis.call(d3.axisLeft(y).tickSize(-width).tickPadding(6))
        .append("text")
          .text(key.toString())
          .attr("fill", "black")
          .attr("transform", `translate(${-margin.left+15},${height/2}) rotate(-90)`)
          .attr("text-anchor", "middle")
      // svg
      svg.attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
      g.attr("transform", `translate(${margin.left},${margin.top})`)
      // plot
      g.selectAll("rect").data(countData)
        .enter().append("rect")
          .attr("y", d => y(d.key)+2)
          .attr("x", d => Math.min(x(0), x(d.value)))
          .attr("width", d => Math.abs(x(d.value) - x(0)))
          .attr("height", y.bandwidth()-3)
      // axis
      g.append("g")
          .attr("transform", `translate(0,${height})`)
          .call(d3.axisBottom(x).ticks(width/45, "s").tickSize(-height).tickPadding(6))
        .append("text")
          .text("count")
          .attr("fill", "black")
          .attr("transform", `translate(${width/2},28)`)
      g.selectAll(".tick line")
          .attr("stroke-dasharray", "1,2")
    }
   }
</script>

All together!

All the previous charts give together a view of how your dataset looks. To facilitate their use we grouped their call in one convenient function. To call this function pass in three arrays, the first with the keys that contain numeric values, the second with the keys that contain time based values and the last with the keys that contain text values.

ABCDEFG
123123123datedateabcabc
123123123datedateabcabc
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    firstLook(data, ["A", "B", "C"], ["D", "E"], ["F", "G"])
  })
  function firstLook(data, numericKeys, dateKeys, textKeys){
    if(numericKeys){
      distributions(data, numericKeys)
      relations(data, numericKeys)
    }
    if(dateKeys){
      timeSeries(data, dateKeys, numericKeys)
    }
    if(textKeys){
      categories(data, textKeys)
    }
  }
  function distributions(data, keys){ 
    d3.select("body").append("div")
      .selectAll("svg").data(keys)
      .enter().append("svg")
        .each(distribution)

    function distribution(xKey){
      var values = data.map(d => d[xKey])
      values.sort(d3.ascending)
      var box = [
        d3.quantile(values, 0.25),
        d3.quantile(values, 0.5),
        d3.quantile(values, 0.75),
      ]

      var margin = {top: 10, right: 10, bottom: 35, left: 25},
          width = 150,
          height = 50;

      var x = d3.scaleLinear()
          .domain(d3.extent(data, d => d[xKey]))
          .rangeRound([0, width])
      var bins = d3.histogram()
          .domain(x.domain())
          .value(d => d[xKey])
        (data)
      var y = d3.scaleLinear()
          .domain([0, d3.max(bins, d => d.length)])
          .range([height, 0])

      var g = d3.select(this)
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
        .append("g")
          .attr("transform", `translate(${margin.left},${margin.top})`)
      // plot
      var rect = g.selectAll("rect").data(bins)
        .enter().append("rect")
          .attr("x", d => x(d.x0))
          .attr("y", d => Math.min(y(0), y(d.length)))
          .attr("width", d => x(d.x1) - x(d.x0))
          .attr("height", d => Math.abs(y(d.length) - y(0)))
      // upper box-plot
      g.append("path")
          .attr("d", `M0,0 H${x(box[0])} V-2.5 H${x(box[1])-1} m2,0 H${x(box[2])} V0 H${width}`)
          .attr("fill", "none")
          .attr("stroke", "black")
      // axis
      g.append("g")
          .attr("transform", `translate(0,${height})`)
          .call(d3.axisBottom(x).ticks(width/45, "s").tickSize(-height).tickPadding(6))
        .append("text")
          .text(xKey)
          .attr("fill", "black")
          .attr("transform", `translate(${width/2},28)`)
      g.append("g")
          .call(d3.axisLeft(y).ticks(height/35, "s").tickSizeInner(-width).tickSizeOuter(0).tickPadding(6))
      g.selectAll(".tick line")
          .attr("stroke-dasharray", "1,2")
    }
   }
  function relations(data, keys){ 
    var cols = keys.slice(0,-1)
    var rows = keys.slice(1)
    var permutations = []
    rows.forEach(function(row, rowIdx){
      return cols.forEach(function(col, colIdx){
        if(colIdx > rowIdx) return 
        permutations.push({col: col, row: row, rowIdx: rowIdx, colIdx: colIdx})
      })
    })
    var domains = {}
    keys.forEach(function(key){domains[key] = d3.extent(data, d => d[key])})

    var n = keys.length - 1
    var margin = {top: 10, right: 10, bottom: 35, left: 52},
        cellSize = 125,
        gap = 10,
        width = height = n * cellSize + gap * (n-1);

    var x = d3.scaleLinear()
        .range([0, cellSize])
    var y = d3.scaleLinear()
        .range([cellSize, 0])

    var g = d3.select("body").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})`)
    // cells
    g.selectAll("g.cell").data(permutations)
      .enter().append("g")
        .attr("class", "cell")
        .attr("transform", d => `translate(${d.colIdx*(cellSize+gap)},${d.rowIdx*(cellSize+gap)})`)
        .each(function(d){
          x.domain(domains[d.col])
          y.domain(domains[d.row])
          d3.select(this)
            .selectAll("circle").data(data)
            .enter().append("circle")
              .attr("cx", v => x(v[d.col]))
              .attr("cy", v => y(v[d.row]))
              .attr("r", 2)
        })
    // axis
    g.selectAll("g.x.axis").data(cols)
      .enter().append("g")
        .attr("class", "x axis")
        .attr("transform", (d,i) => `translate(${i*(cellSize+gap)},${height})`)
        .each(function(d,i){
          x.domain(domains[d])
          d3.select(this)
              .call(d3.axisBottom(x).ticks(cellSize/45, "s").tickSize(-height).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(${cellSize/2},28)`)
    g.selectAll("g.y.axis").data(rows)
      .enter().append("g")
        .attr("class", "y axis")
        .attr("transform", (d,i) => `translate(0,${i*(cellSize+gap)})`)
        .each(function(d,i){
          y.domain(domains[d])
          d3.select(this)
              .call(d3.axisLeft(y).ticks(cellSize/35, "s").tickSize(-width).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(-38,${cellSize/2}) rotate(-90)`)
        .attr("text-anchor", "middle")
    g.selectAll(".tick line")
        .attr("stroke-dasharray", "1,2")
   }
  function timeSeries(data, xKeys, yKeys){ 
    var permutations = []
    xKeys.forEach(function(xKey, xI){
      return yKeys.forEach(function(yKey, yI){
        permutations.push({xKey: xKey, xI: xI, yKey: yKey, yI: yI})
      })
    })
    var domains = {}
    xKeys.concat(yKeys)
      .forEach(function(key){domains[key] = d3.extent(data, d => d[key])})

    var nX = xKeys.length,
        nY = yKeys.length;
    var cellHeight = 75,
        cellWidth = 150,
        gap = 10;
    var margin = {top: 10, right: 10, bottom: 35, left: 52},
        width = nX * cellWidth + (nX-1) * gap,
        height = nY * cellHeight + (nY-1) * gap;

    var x = d3.scaleTime()
        .range([0, cellWidth])
    var y = d3.scaleLinear()
        .range([cellHeight, 0])
    var line = d3.line()

    var g = d3.select("body").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})`)
    // cells
    g.selectAll("g.cell").data(permutations)
      .enter().append("g")
        .attr("class", "cell")
        .attr("transform", d => `translate(${d.xI*(cellWidth+gap)},${d.yI*(cellHeight+gap)})`)
        .each(function(d){
          data.sort((a,b) => d3.ascending(a[d.xKey], b[d.xKey]))
          x.domain(domains[d.xKey])
          y.domain(domains[d.yKey])
          line.x(v => x(v[d.xKey])).y(v => y(v[d.yKey]))
          d3.select(this).append("path")
              .attr("d", line(data))
              .attr("stroke", "black")
              .attr("fill", "none")
        })
    // axis
    g.selectAll("g.x.axis").data(xKeys)
      .enter().append("g")
        .attr("class", "x axis")
        .attr("transform", (d,i) => `translate(${i*(cellWidth+gap)},${height})`)
        .each(function(d){
          x.domain(domains[d])
          d3.select(this)
              .call(d3.axisBottom(x).ticks(cellWidth/45).tickSize(-height).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(${cellWidth/2},28)`)
    g.selectAll("g.y.axis").data(yKeys)
      .enter().append("g")
        .attr("class", "y axis")
        .attr("transform", (d,i) => `translate(0,${i*(cellHeight+gap)})`)
        .each(function(d,i){
          y.domain(domains[d])
          d3.select(this)
              .call(d3.axisLeft(y).ticks(cellHeight/35, "s").tickSize(-width).tickPadding(6))
        })
      .append("text")
        .text(d => d)
        .attr("fill", "black")
        .attr("transform", `translate(-38,${cellHeight/2}) rotate(-90)`)
        .attr("text-anchor", "middle")
    g.selectAll(".tick line")
        .attr("stroke-dasharray", "1,2")
   }
  function categories(data, keys){ 
    d3.select("body").append("div")
      .selectAll("svg").data(keys)
      .enter().append("svg")
        .each(category)

    function category(key){
      var countData = d3.nest()
          .key(d => d[key]).rollup(d => d.length)
        .entries(data)
          .sort((a,b) => d3.descending(a.value, b.value))

      var margin = {top: 10, right: 10, bottom: 35, left: 50},
          width = 150,
          height = 13 * countData.length;

      var x = d3.scaleLinear()
          .domain([
            Math.min(0, d3.min(countData, d => d.value)),
            Math.max(0, d3.max(countData, d => d.value))
          ])
          .range([0, width])
      var y = d3.scaleBand()
          .domain(countData.map(d => d.key))
          .range([0, height])

      var svg = d3.select(this)
      var g = svg.append("g")
      // yAxis parametric sizing
      var yAxis = g.append("g")
          .call(d3.axisLeft(y).tickPadding(6))
      margin.left = yAxis.node().getBoundingClientRect().width+25
      yAxis.call(d3.axisLeft(y).tickSize(-width).tickPadding(6))
        .append("text")
          .text(key.toString())
          .attr("fill", "black")
          .attr("transform", `translate(${-margin.left+15},${height/2}) rotate(-90)`)
          .attr("text-anchor", "middle")
      // svg
      svg.attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
      g.attr("transform", `translate(${margin.left},${margin.top})`)
      // plot
      g.selectAll("rect").data(countData)
        .enter().append("rect")
          .attr("y", d => y(d.key)+2)
          .attr("x", d => Math.min(x(0), x(d.value)))
          .attr("width", d => Math.abs(x(d.value) - x(0)))
          .attr("height", y.bandwidth()-3)
      // axis
      g.append("g")
          .attr("transform", `translate(0,${height})`)
          .call(d3.axisBottom(x).ticks(width/45, "s").tickSize(-height).tickPadding(6))
        .append("text")
          .text("count")
          .attr("fill", "black")
          .attr("transform", `translate(${width/2},28)`)
      g.selectAll(".tick line")
          .attr("stroke-dasharray", "1,2")
    }
   }
</script>

Base Charts

These are basic strip down charts, to serve as base for further explorations after a general sense of the data was achieved with the first look plots. They go without axis names, so they are easy to customize to a specific aesthetic.

Scatter Plot

See the relation between two dimensions of the data, optionally also map an extra text dimension to the color of the circles and a numeric dimension to their radius, call this function with the data keys to be used for the x, y, color and radius.

ABCF
123123123abc
123123123abc
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    scatterPlot(data, "A", "B", "F", "C")
  })
  function scatterPlot(data, xKey, yKey, colorKey, radiusKey){  
    var margin = {top: 10, right: 10, bottom: 30, left: 30},
        width = 400 - margin.left - margin.right,
        height = 300 - margin.top - margin.bottom;
    var x = d3.scaleLinear()
        .domain(d3.extent(data, d => d[xKey]))
        .range([0, width])
    var y = d3.scaleLinear()
        .domain(d3.extent(data, d => d[yKey]))
        .range([height, 0])
    var color = d3.scaleOrdinal(d3.schemeCategory10)
    var radius = d3.scaleLinear()
        .domain(d3.extent(data, d => d[radiusKey]))
        .range([2, 5])
    // svg
    var g = d3.select("body").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})`)
    // plot
    g.selectAll("circle").data(data)
      .enter().append("circle")
        .attr("cx", d => x(d[xKey]))
        .attr("cy", d => y(d[yKey]))
        .attr("fill", colorKey ? d => color(d[colorKey]) : "black")
        .attr("r", radiusKey ? d => radius(d[radiusKey]) : 2)
    // axis
    g.append("g")
        .attr("transform", `translate(0,${height})`)
        .call(d3.axisBottom(x).ticks(width/45, "s"))
    g.append("g")
        .call(d3.axisLeft(y).ticks(height/35, "s"))
   }
</script>

Line Chart

See the relation between a numeric dimension and a time based dimension of the data, call the function with the data keys that will be used on the x and y properties.

CD
123date
123date
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    lineChart(data, "C", "D")
  })
  function lineChart(data, xKey, yKey){  
    data.sort((a,b) => d3.ascending(a[xKey], b[xKey]))
    var margin = {top: 10, right: 10, bottom: 30, left: 30},
        width = 400 - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom;
    var x = d3.scaleTime()
        .domain(d3.extent(data, d => d[xKey]))
        .range([0, width])
    var y = d3.scaleLinear()
        .domain(d3.extent(data, d => d[yKey]))
        .range([height, 0])
    var line = d3.line()
        .x(d => x(d[xKey]))
        .y(d => y(d[yKey]))
    // svg
    var g = d3.select("body").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})`)
    // plot
    g.append("path")
        .attr("d", line(data))
        .style("fill", "none")
        .style("stroke", "black")
    // axis
    g.append("g")
        .attr("transform", `translate(0,${height})`)
        .call(d3.axisBottom(x).ticks(width/60))
    g.append("g")
        .call(d3.axisLeft(y).ticks(height/30, "s"))
   }
</script>

Bar Chart

See how pairs of numeric/text values relate to each other, call the function with the data keys that will be used on the x and y properties.

valuename
123abs
123abs
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    barChart(data, "value", "name")
  })
  function barChart(data, xKey, yKey){  
    var margin = {top: 10, right: 10, bottom: 30, left: 30},
        width = 400 - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom;

    var x = d3.scaleLinear()
        .domain([
          Math.min(0, d3.min(data, d => d[xKey])),
          Math.max(0, d3.max(data, d => d[xKey]))
        ])
        .range([0, width])
    var y = d3.scaleBand()
        .domain(data.map(d => d[yKey]))
        .range([0, height])
        .paddingInner(.1)
    var svg = d3.select("body").append("svg")
    var g = svg.append("g")
    // y axis
    var yAxis = g.append("g")
        .call(d3.axisLeft(y))
    width += margin.left
    margin.left = yAxis.node().getBoundingClientRect().width
    width -= margin.left
    x.range([0, width])
    // svg
    svg.attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    g.attr("transform", `translate(${margin.left},${margin.top})`)
    // plot
    g.selectAll("rect").data(data)
      .enter().append("rect")
        .attr("y", d => y(d[yKey]))
        .attr("x", d => Math.min(x(0), x(d[xKey])))
        .attr("width", d => Math.abs(x(d[xKey]) - x(0)))
        .attr("height", y.bandwidth())
    // x axis
    g.append("g")
        .attr("transform", `translate(0,${height})`)
        .call(d3.axisBottom(x).ticks(width/45, "s"))
   }
</script>

Histogram

See the distribution of numeric values for a dimension of the data, call the function with the dataset key to be used on the visualization.

value
123
123
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  d3.csv("data.csv", function(data){
    // parse and transform data as needed, then call plot function
    histogram(data, "value", "name")
  })
  function histogram(data, key){  
    var margin = {top: 10, right: 10, bottom: 30, left: 30},
        width = 400 - margin.left - margin.right,
        height = 200 - margin.top - margin.bottom;

    var x = d3.scaleLinear()
        .domain(d3.extent(data, d => d[xKey]))
        .rangeRound([0, width])
    var bins = d3.histogram()
        .domain(x.domain())
        .value(d => d[xKey])
      (data)
    var y = d3.scaleLinear()
        .domain([0, d3.max(bins, d => d.length)])
        .range([height, 0])
    // svg
    var g = d3.select("body").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})`)
    // plot
    var rect = g.selectAll("rect").data(bins)
      .enter().append("rect")
        .attr("x", d => x(d.x0))
        .attr("y", d => Math.min(y(0), y(d.length)))
        .attr("width", d => x(d.x1) - x(d.x0))
        .attr("height", d => Math.abs(y(d.length) - y(0)))
    // axis
    g.append("g")
        .attr("transform", `translate(0,${height})`)
        .call(d3.axisBottom(x).ticks(width/45, "s"))
    g.append("g")
        .call(d3.axisLeft(y).ticks(height/35, "s"))
   }
</script>
Newsletter
Movies Box Office
The Age of the World Population