Built with blockbuilder.org
This all started here ...
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>Omg this chart is a mess. https://t.co/pnWa2RMVcm pic.twitter.com/kFm1n1MqDl
— yoni sidi (@yoniceedee) January 4, 2018
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.
library(htmltools)
library(dplyr)
# 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 = rnorm(10 * 10,100,1),
stringsAsFactors = FALSE
)%>%
group_by(idx)%>%mutate(perf=perf/head(perf,1))
# 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)
)
))
)
)