All examples By author By category About

timelyportfolio

alternate version of market quilt

Built with blockbuilder.org


This all started here ...

These market quilts or carpets are extremely common and popular, but for almost my entire career I have thought there must be a better way to communicate this information.

This is a very ugly sketch of an alternate version with imaginary data. It is sort of like a bump chart. I don't think it is any better.

Code in R and JavaScript

library(htmltools)

# make some fake data to use with rawgraph bump chart
#  but rawgraphs doesn't work as I would like
dat <- data.frame(
  idx = rep(LETTERS[1:10], each=10),
  date = rep(
    seq.Date(from=as.Date("2000-12-31"), to=as.Date("2009-12-31"), by="years"),
    10
  ),
  perf = runif(10 * 10,-20,20),
  stringsAsFactors = FALSE
)



# so go custom
browsable(
  tagList(
    d3r::d3_dep_v4(),
    tags$script(HTML(
sprintf(
'
var data = %s
var height = 500, width = 900
var color = d3.scaleOrdinal(d3.schemeCategory10)

var svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g")
  .attr("transform", "translate(20,20)")

var sc_x = d3.scalePoint()
  .domain(data.map(d => (d.date)))
  .range([0,width - 40])

var sc_y = d3.scaleLinear()
  .domain(d3.extent(data.map(d => d.perf)))
  .range([height - 40, 0])

var line = d3.line()
  .x(d => sc_x(d.date))
  .y(d => sc_y(d.perf))
  .curve(d3.curveBasis)

var nested = d3.nest().key(d => d.idx).entries(data)

var lines = svg.selectAll(".line").data(nested)

lines.exit().remove()

lines = lines.merge(lines.enter().append("path"))

lines
  .classed("line", true)
  .attr("d", d => line(d.values))
  .style("fill", "none")
  .style("opacity", 0.5)
  .style("stroke", d => color(d.key))
  .style("stroke-width", 20)
',
  jsonlite::toJSON(dat, dataframe="rows", pretty=TRUE)
)
    ))
  )
)


# so go custom 2
browsable(
  tagList(
    d3r::d3_dep_v4(),
    tags$script(HTML(
      sprintf(
'
var data = %s

var height = 600, width = 900
var margin = {
  top: 20,
  bottom: 50,
  left: 50,
  right: 20
}

var color = d3.scaleOrdinal(d3.schemeCategory10)

var svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height)

var g_plot = svg.append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")

var sc_x = d3.scaleBand()
  .domain(data.map(d => (d.date)))
  .range([0,width - margin.left - margin.right])

var sc_y = d3.scaleLinear()
  .domain(d3.extent(data.map(d => d.perf)))
  .range([height - margin.top - margin.bottom, 0])

var line = d3.line()
  .curve(d3.curveMonotoneX)

var horiz_smooth = 50

var nested = d3.nest().key(d => d.idx).entries(data)

var highlight = function() {
  svg.selectAll(".line").style("opacity", 0.25)
  d3.select(this).style("opacity", 1)
}

var unhighlight = function() {
  svg.selectAll(".line").style("opacity", 0.5)
}

nested.map(function(d) {
points = []

d.values.forEach(function(dd, i) {
  if(i > d.values.length - 2) {
    points.push([sc_x(dd.date) + horiz_smooth, sc_y(dd.perf)])
    return
  }

  if(i === 0) {
    points.push([sc_x(dd.date), sc_y(dd.perf)]),
    points.push([sc_x(dd.date) + horiz_smooth, sc_y(dd.perf)])
    points.push([sc_x(dd.date) + sc_x.bandwidth(), sc_y(d.values[i+1].perf)])
    return
  }

  points.push([sc_x(dd.date) + horiz_smooth, sc_y(dd.perf)])

  points.push([sc_x(dd.date) + sc_x.bandwidth(), sc_y(d.values[i+1].perf)])
})

g_plot.append("path")
  .classed("line", true)
  .attr("d", line(points))
  .style("fill", "none")
  .style("stroke", color(d.key))
  .style("stroke-width", 10)
  .style("opacity", 0.5)
  .on("mouseover", highlight)
  .on("mouseout", unhighlight)
})

g_plot.append("g")
  .call(d3.axisBottom().scale(sc_x))
  .attr("transform", "translate(" + (-sc_x.bandwidth()/2 + horiz_smooth/2) + "," + (+height - margin.top - margin.bottom) + ")")

g_plot.append("g")
  .call(d3.axisLeft().scale(sc_y))
',
  jsonlite::toJSON(dat, dataframe="rows", pretty=TRUE)
)
    ))
  )
)