Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active December 15, 2016 17:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timelyportfolio/df195101592024e1916205e9100936e2 to your computer and use it in GitHub Desktop.
Save timelyportfolio/df195101592024e1916205e9100936e2 to your computer and use it in GitHub Desktop.
work history d3v3 and R ver 1
license: mit
scrolling: yes
height: 800

assembled with blockbuilder.org


slightly improved in Version 2

A very unfinished little example combining d3.js and d3.layout.timeline by Elijah Meeks to render what you might use in a resume or something similar. It is partially reusable, but still needs lots of love and attention. Please do not use this as an model for how to implement a reusable component.

If you are wondering about my potential motivation for this, see post.

code in R

library(pipeR) #install.packages("pipeR")
library(timelineR) #devtools::install_github("timelyportfolio/timelineR")
library(htmltools) #install.packages("htmltools")
library(d3r) #devtools::install_github("timelyportfolio/d3r")
library(googlefontR) #devtools::install_github("timelyportfolio/googlefontR")
library(whisker) #install.packages("whisker")

dat <- data.frame(
  id = 1:4,
  company = c(
    "Company A",
    "Company A",
    "Company B",
    "Company C"
  ),
  start = c(
    "1998-08-31",
    "1999-12-31",
    "2001-05-31",
    "2011-08-31"
  ),
  end = c(
    "1999-12-31",
    "2001-05-31",
    "2011-08-31",
    "2016-12-31"
  ),
  description = c(
    "Title and all the stuff I did",
    "New Title with other sophisticated sounding items",
    "Really Important Sounding Title with all the buzzwords",
    "Even More Important Title with persuasive text"
  )
)

# based off of 
#  http://bl.ocks.org/emeeks/3184af35f4937d878ac0
template <- '
(function(){
  var svg = d3.select("svg#{{svgid}}");
  var width = svg.node().getBoundingClientRect().width,
    height = svg.node().getBoundingClientRect().height;

  var data = {{{data}}};
  var groups = d3.set(data.map(function(d){ return d.{{group}} })).values();

  var bandHeight = {{bandHeight}};
  var timeline = d3.layout.timeline()
    .size([width - 150 - 20, bandHeight - 10])
    .extent(["1998-01-01", "2016-12-31"])
    .padding(3)
    .maxBandHeight(bandHeight - 20);
  
  var colorScale = {{colorFun}};
  
  groups.forEach(function (type, i) {
    onlyThisType = data.filter(function(d) {return d.{{group}} === type});
    
    theseBands = timeline(onlyThisType);
    
    svg.append("g")
      .attr("transform", "translate(150," + (35 + (i * 50)) + ")")
      .selectAll("rect")
      .data(theseBands)
      .enter()
      .append("rect")
      .classed("rect-position",true)
      .attr("rx", 2)
      .attr("x", function (d) {return d.start})
      .attr("y", function (d) {return d.y})
      .attr("height", function (d) {return d.dy})
      .attr("width", function (d) {return d.end - d.start})
      .style("fill", function (d) {return colorScale(d.{{group}})})
      .style("stroke", "black")
      .style("stroke-width", 1);
    
    d3.select("svg").append("text")
      .text(type)
      .attr("y", bandHeight + (i * bandHeight))
      .attr("x", 20);

  });

  var axisScale = d3.time.scale();

  axisScale
    .domain(timeline.extent())
    .range([0, width - 150 - 20]);

  var axis = d3.svg.axis();
  axis.scale(axisScale);
  axis.tickSize(bandHeight * groups.length + 20);

  var axis_g = svg.append("g")
    .attr("class","timeline-axis")
    .attr("transform","translate(170,0)")
    .call(axis);

  // some default styles for our axis
  axis_g.selectAll("path, .tick > line")
    .style("fill","none")
    .style("stroke","black");

  svg.selectAll(".timeline-axis > path")
    .style("fill","none")
    .style("stroke","none");
  svg.selectAll(".timeline-axis .tick line")
    .style("stroke","gray")
    .attr("stroke-dasharray","5 5");
  svg.selectAll("rect")
    .style("stroke","white")
    .attr("rx",4);

  svg.attr("height", (bandHeight * groups.length + 20 + 20) + "px")

  // now render the text descriptions of each position
  var textdiv = d3.select("div#{{divid}}");
  var posdiv = textdiv.selectAll("p")
    .data(data)
    .enter()
    .append("div");

  posdiv.append("h3")
    .text(function(d){
      return d.company + " | " + d.start + " - " + d.end
    });

  posdiv.append("p")
    .text(function(d){ return d.description; });

  // add some mouseover on the rects
  svg.selectAll("rect.rect-position")
    .on("mouseover", highlightPos)
    .on("mouseout", unhighlightPos);

  function highlightPos(pos){
    posdiv.each(function(d) {
      if(d.id !== pos.id) {
        d3.select(this).style("color", "lightgray");
      }
    });
  }

  function unhighlightPos() {
    posdiv.style("color", "black")
  }
  
})();
'

tagList(
  h1("Work History"),
  tag("svg",list(id="work-timeline", width="80%", height="400px")),
  hr(),
  div(id = "work-history"),
  tags$script(
    HTML(
      whisker.render(
        template,
        list(
          svgid = "work-timeline",
          divid = "work-history",
          group = "company",
          data = jsonlite::toJSON(dat, dataframe="rows"),
          bandHeight = 50, # total height of band not the timeline rect
          colorFun = "d3.scale.category10()"
        )
      )
    )
  )
) %>>%
  gf_add_font("Titillium+Web") %>>%
  attachDependencies(
    list(
      d3r::d3_dep_v3(offline=FALSE),
      htmlwidgets:::getDependency("timeline","timelineR")[[3]]
    ),
    append = TRUE
  ) %>>%
  browsable()
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link href="https://fonts.googleapis.com/css?family=Titillium+Web" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://cdn.rawgit.com/emeeks/d3.layout.timeline/master/d3.layout.timeline.js"></script>
<style>body {font-family:Titillium Web;}</style>
</head>
<body style="background-color:white;">
<h1>Work History</h1>
<svg id="work-timeline" width="80%" height="400px"></svg>
<hr/>
<div id="work-history"></div>
<script>
(function(){
var svg = d3.select("svg#work-timeline");
var width = svg.node().getBoundingClientRect().width,
height = svg.node().getBoundingClientRect().height;
var data = [{"id":1,"company":"Company A","start":"1998-08-31","end":"1999-12-31","description":"Title and all the stuff I did"},{"id":2,"company":"Company A","start":"1999-12-31","end":"2001-05-31","description":"New Title with other sophisticated sounding items"},{"id":3,"company":"Company B","start":"2001-05-31","end":"2011-08-31","description":"Really Important Sounding Title with all the buzzwords"},{"id":4,"company":"Company C","start":"2011-08-31","end":"2016-12-31","description":"Even More Important Title with persuasive text"}];
var groups = d3.set(data.map(function(d){ return d.company })).values();
var bandHeight = 50;
var timeline = d3.layout.timeline()
.size([width - 150 - 20, bandHeight - 10])
.extent(["1998-01-01", "2016-12-31"])
.padding(3)
.maxBandHeight(bandHeight - 20);
var colorScale = d3.scale.category10();
groups.forEach(function (type, i) {
onlyThisType = data.filter(function(d) {return d.company === type});
theseBands = timeline(onlyThisType);
svg.append("g")
.attr("transform", "translate(150," + (35 + (i * 50)) + ")")
.selectAll("rect")
.data(theseBands)
.enter()
.append("rect")
.classed("rect-position",true)
.attr("rx", 2)
.attr("x", function (d) {return d.start})
.attr("y", function (d) {return d.y})
.attr("height", function (d) {return d.dy})
.attr("width", function (d) {return d.end - d.start})
.style("fill", function (d) {return colorScale(d.company)})
.style("stroke", "black")
.style("stroke-width", 1);
d3.select("svg").append("text")
.text(type)
.attr("y", bandHeight + (i * bandHeight))
.attr("x", 20);
});
var axisScale = d3.time.scale();
axisScale
.domain(timeline.extent())
.range([0, width - 150 - 20]);
var axis = d3.svg.axis();
axis.scale(axisScale);
axis.tickSize(bandHeight * groups.length + 20);
var axis_g = svg.append("g")
.attr("class","timeline-axis")
.attr("transform","translate(170,0)")
.call(axis);
// some default styles for our axis
axis_g.selectAll("path, .tick > line")
.style("fill","none")
.style("stroke","black");
svg.selectAll(".timeline-axis > path")
.style("fill","none")
.style("stroke","none");
svg.selectAll(".timeline-axis .tick line")
.style("stroke","gray")
.attr("stroke-dasharray","5 5");
svg.selectAll("rect")
.style("stroke","white")
.attr("rx",4);
svg.attr("height", (bandHeight * groups.length + 20 + 20) + "px")
// now render the text descriptions of each position
var textdiv = d3.select("div#work-history");
var posdiv = textdiv.selectAll("p")
.data(data)
.enter()
.append("div");
posdiv.append("h3")
.text(function(d){
return d.company + " | " + d.start + " - " + d.end
});
posdiv.append("p")
.text(function(d){ return d.description; });
// add some mouseover on the rects
svg.selectAll("rect.rect-position")
.on("mouseover", highlightPos)
.on("mouseout", unhighlightPos);
function highlightPos(pos){
posdiv.each(function(d) {
if(d.id !== pos.id) {
d3.select(this).style("color", "lightgray");
}
});
}
function unhighlightPos() {
posdiv.style("color", "black")
}
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment