Skip to content

Instantly share code, notes, and snippets.

@zreese
Last active September 17, 2018 15:45
Show Gist options
  • Save zreese/d0cbf17b1400e2d2ebf132b6b14274c2 to your computer and use it in GitHub Desktop.
Save zreese/d0cbf17b1400e2d2ebf132b6b14274c2 to your computer and use it in GitHub Desktop.
CRISPR Trendline
@import url(//fonts.googleapis.com/css?family=Karla);
svg {
font-family: Karla;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
fill: #000;
}
.axis .tick line {
stroke: rgba(0, 0, 0, 0.1);
}
.area {
stroke-width: 1;
}
.area.outer,
.legend .outer {
fill: rgba(230, 230, 255, 0.8);
stroke: rgba(216, 216, 255, 0.8);
}
.area.inner,
.legend .inner {
fill: rgba(127, 127, 255, 0.8);
stroke: rgba(96, 96, 255, 0.8);
}
.median-line,
.legend .median-line {
fill: none;
stroke: #000;
stroke-width: 3;
}
.legend .legend-bg {
fill: rgba(0, 0, 0, 0.5);
stroke: rgba(0, 0, 0, 0.5);
}
.marker.client .marker-bg,
.marker.client path {
fill: rgba(255, 127, 0, 0.8);
stroke: rgba(255, 127, 0, 0.8);
stroke-width: 3;
}
.marker.server .marker-bg,
.marker.server path {
fill: rgba(0, 153, 51, 0.8);
stroke: rgba(0, 153, 51, 0.8);
stroke-width: 3;
}
.marker path {
fill: none;
}
.legend text,
.marker text {
fill: #fff;
font-weight: bold;
}
.marker text {
text-anchor: middle;
}
function addAxesAndLegend (svg, xAxis, yAxis, margin, chartWidth, chartHeight) {
var legendWidth = 200,
legendHeight = 100;
// clipping to make sure nothing appears behind legend
svg.append('clipPath')
.attr('id', 'axes-clip')
.append('polygon')
.attr('points', (-margin.left) + ',' + (-margin.top) + ' ' +
(chartWidth - legendWidth - 1) + ',' + (-margin.top) + ' ' +
(chartWidth - legendWidth - 1) + ',' + legendHeight + ' ' +
(chartWidth + margin.right) + ',' + legendHeight + ' ' +
(chartWidth + margin.right) + ',' + (chartHeight + margin.bottom) + ' ' +
(-margin.left) + ',' + (chartHeight + margin.bottom));
var axes = svg.append('g')
.attr('clip-path', 'url(#axes-clip)');
axes.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + chartHeight + ')')
.call(xAxis);
axes.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Time (s)');
var legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', 'translate(' + (chartWidth - legendWidth) + ', 0)');
legend.append('rect')
.attr('class', 'legend-bg')
.attr('width', legendWidth)
.attr('height', legendHeight);
legend.append('rect')
.attr('class', 'outer')
.attr('width', 75)
.attr('height', 20)
.attr('x', 10)
.attr('y', 10);
legend.append('text')
.attr('x', 115)
.attr('y', 25)
.text('5% - 95%');
legend.append('rect')
.attr('class', 'inner')
.attr('width', 75)
.attr('height', 20)
.attr('x', 10)
.attr('y', 40);
legend.append('text')
.attr('x', 115)
.attr('y', 55)
.text('25% - 75%');
legend.append('path')
.attr('class', 'median-line')
.attr('d', 'M10,80L85,80');
legend.append('text')
.attr('x', 115)
.attr('y', 85)
.text('Median');
}
function drawPaths (svg, data, x, y) {
var upperOuterArea = d3.svg.area()
.interpolate('basis')
.x (function (d) { return x(d.date) || 1; })
.y0(function (d) { return y(d.pct95); })
.y1(function (d) { return y(d.pct75); });
var upperInnerArea = d3.svg.area()
.interpolate('basis')
.x (function (d) { return x(d.date) || 1; })
.y0(function (d) { return y(d.pct75); })
.y1(function (d) { return y(d.pct50); });
var medianLine = d3.svg.line()
.interpolate('basis')
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.pct50); });
var lowerInnerArea = d3.svg.area()
.interpolate('basis')
.x (function (d) { return x(d.date) || 1; })
.y0(function (d) { return y(d.pct50); })
.y1(function (d) { return y(d.pct25); });
var lowerOuterArea = d3.svg.area()
.interpolate('basis')
.x (function (d) { return x(d.date) || 1; })
.y0(function (d) { return y(d.pct25); })
.y1(function (d) { return y(d.pct05); });
svg.datum(data);
svg.append('path')
.attr('class', 'area upper outer')
.attr('d', upperOuterArea)
.attr('clip-path', 'url(#rect-clip)');
svg.append('path')
.attr('class', 'area lower outer')
.attr('d', lowerOuterArea)
.attr('clip-path', 'url(#rect-clip)');
svg.append('path')
.attr('class', 'area upper inner')
.attr('d', upperInnerArea)
.attr('clip-path', 'url(#rect-clip)');
svg.append('path')
.attr('class', 'area lower inner')
.attr('d', lowerInnerArea)
.attr('clip-path', 'url(#rect-clip)');
svg.append('path')
.attr('class', 'median-line')
.attr('d', medianLine)
.attr('clip-path', 'url(#rect-clip)');
}
function addMarker (marker, svg, chartHeight, x) {
var radius = 32,
xPos = x(marker.date) - radius - 3,
yPosStart = chartHeight - radius - 3,
yPosEnd = (marker.type === 'Client' ? 80 : 160) + radius - 3;
var markerG = svg.append('g')
.attr('class', 'marker '+marker.type.toLowerCase())
.attr('transform', 'translate(' + xPos + ', ' + yPosStart + ')')
.attr('opacity', 0);
markerG.transition()
.duration(1000)
.attr('transform', 'translate(' + xPos + ', ' + yPosEnd + ')')
.attr('opacity', 1);
markerG.append('path')
.attr('d', 'M' + radius + ',' + (chartHeight-yPosStart) + 'L' + radius + ',' + (chartHeight-yPosStart))
.transition()
.duration(1000)
.attr('d', 'M' + radius + ',' + (chartHeight-yPosEnd) + 'L' + radius + ',' + (radius*2));
markerG.append('circle')
.attr('class', 'marker-bg')
.attr('cx', radius)
.attr('cy', radius)
.attr('r', radius);
markerG.append('text')
.attr('x', radius)
.attr('y', radius*0.9)
.text(marker.type);
markerG.append('text')
.attr('x', radius)
.attr('y', radius*1.5)
.text(marker.version);
}
function startTransitions (svg, chartWidth, chartHeight, rectClip, markers, x) {
rectClip.transition()
.duration(1000*markers.length)
.attr('width', chartWidth);
markers.forEach(function (marker, i) {
setTimeout(function () {
addMarker(marker, svg, chartHeight, x);
}, 1000 + 500*i);
});
}
function makeChart (data, markers) {
var svgWidth = 960,
svgHeight = 500,
margin = { top: 20, right: 20, bottom: 40, left: 40 },
chartWidth = svgWidth - margin.left - margin.right,
chartHeight = svgHeight - margin.top - margin.bottom;
var x = d3.time.scale().range([0, chartWidth])
.domain(d3.extent(data, function (d) { return d.date; })),
y = d3.scale.linear().range([chartHeight, 0])
.domain([0, d3.max(data, function (d) { return d.pct95; })]);
var xAxis = d3.svg.axis().scale(x).orient('bottom')
.innerTickSize(-chartHeight).outerTickSize(0).tickPadding(10),
yAxis = d3.svg.axis().scale(y).orient('left')
.innerTickSize(-chartWidth).outerTickSize(0).tickPadding(10);
var svg = d3.select('body').append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// clipping to start chart hidden and slide it in later
var rectClip = svg.append('clipPath')
.attr('id', 'rect-clip')
.append('rect')
.attr('width', 0)
.attr('height', chartHeight);
addAxesAndLegend(svg, xAxis, yAxis, margin, chartWidth, chartHeight);
drawPaths(svg, data, x, y);
startTransitions(svg, chartWidth, chartHeight, rectClip, markers, x);
}
var parseDate = d3.time.format('%Y-%m-%d').parse;
d3.json('data.json', function (error, rawData) {
if (error) {
console.error(error);
return;
}
var data = rawData.map(function (d) {
return {
date: parseDate(d.date),
pct05: d.pct05 / 1000,
pct25: d.pct25 / 1000,
pct50: d.pct50 / 1000,
pct75: d.pct75 / 1000,
pct95: d.pct95 / 1000
};
});
d3.json('markers.json', function (error, markerData) {
if (error) {
console.error(error);
return;
}
var markers = markerData.map(function (marker) {
return {
date: parseDate(marker.date),
type: marker.type,
version: marker.version
};
});
makeChart(data, markers);
});
});
[
{
"date": "2014-08-01",
"pct05": 5350,
"pct25": 6756,
"pct50": 7819,
"pct75": 9284,
"pct95": 13835
},
{
"date": "2014-08-02",
"pct05": 4439,
"pct25": 5584,
"pct50": 6554,
"pct75": 8016,
"pct95": 12765
},
{
"date": "2014-08-03",
"pct05": 4247,
"pct25": 5419,
"pct50": 6332,
"pct75": 7754,
"pct95": 12236
},
{
"date": "2014-08-04",
"pct05": 3293,
"pct25": 4414,
"pct50": 5191,
"pct75": 6491,
"pct95": 10325
},
{
"date": "2014-08-05",
"pct05": 3942,
"pct25": 5134,
"pct50": 6069,
"pct75": 7501,
"pct95": 11685
},
{
"date": "2014-08-06",
"pct05": 2744,
"pct25": 5508,
"pct50": 6879,
"pct75": 9221,
"pct95": 17239
},
{
"date": "2014-08-07",
"pct05": 1807,
"pct25": 3019,
"pct50": 4119,
"pct75": 5656,
"pct95": 8851
},
{
"date": "2014-08-08",
"pct05": 1855,
"pct25": 3386,
"pct50": 4473,
"pct75": 5915,
"pct95": 10580
},
{
"date": "2014-08-09",
"pct05": 1830,
"pct25": 3202,
"pct50": 4233,
"pct75": 5559,
"pct95": 8930
},
{
"date": "2014-08-10",
"pct05": 1828,
"pct25": 3195,
"pct50": 4304,
"pct75": 5482,
"pct95": 9189
},
{
"date": "2014-08-11",
"pct05": 2246,
"pct25": 3929,
"pct50": 5326,
"pct75": 7077,
"pct95": 11648
},
{
"date": "2014-08-12",
"pct05": 2051,
"pct25": 3662,
"pct50": 4849,
"pct75": 6194,
"pct95": 10078
},
{
"date": "2014-08-13",
"pct05": 1700,
"pct25": 3075,
"pct50": 4068,
"pct75": 5259,
"pct95": 9789
},
{
"date": "2014-08-14",
"pct05": 2161,
"pct25": 3891,
"pct50": 5262,
"pct75": 6924,
"pct95": 11612
},
{
"date": "2014-08-15",
"pct05": 1765,
"pct25": 3190,
"pct50": 4388,
"pct75": 5822,
"pct95": 9433
},
{
"date": "2014-08-16",
"pct05": 2036,
"pct25": 3756,
"pct50": 4775,
"pct75": 6158,
"pct95": 9999
},
{
"date": "2014-08-17",
"pct05": 2079,
"pct25": 3561,
"pct50": 4753,
"pct75": 6124,
"pct95": 9807
},
{
"date": "2014-08-18",
"pct05": 2108,
"pct25": 3576,
"pct50": 4818,
"pct75": 6344,
"pct95": 10235
},
{
"date": "2014-08-19",
"pct05": 2143,
"pct25": 3792,
"pct50": 5073,
"pct75": 6772,
"pct95": 11338
},
{
"date": "2014-08-20",
"pct05": 2086,
"pct25": 3801,
"pct50": 5073,
"pct75": 6688,
"pct95": 12394
},
{
"date": "2014-08-21",
"pct05": 1767,
"pct25": 3253,
"pct50": 4282,
"pct75": 5563,
"pct95": 9167
},
{
"date": "2014-08-22",
"pct05": 1756,
"pct25": 3047,
"pct50": 3950,
"pct75": 5006,
"pct95": 7948
},
{
"date": "2014-08-23",
"pct05": 2123,
"pct25": 3755,
"pct50": 5173,
"pct75": 7243,
"pct95": 12338
},
{
"date": "2014-08-24",
"pct05": 1967,
"pct25": 3404,
"pct50": 4529,
"pct75": 5970,
"pct95": 9897
},
{
"date": "2014-08-25",
"pct05": 1537,
"pct25": 2612,
"pct50": 3394,
"pct75": 4279,
"pct95": 7104
},
{
"date": "2014-08-26",
"pct05": 2182,
"pct25": 3958,
"pct50": 5505,
"pct75": 7642,
"pct95": 12707
},
{
"date": "2014-08-27",
"pct05": 1932,
"pct25": 3366,
"pct50": 4526,
"pct75": 6086,
"pct95": 9930
},
{
"date": "2014-08-28",
"pct05": 1268,
"pct25": 2344,
"pct50": 3256,
"pct75": 4215,
"pct95": 6673
},
{
"date": "2014-08-29",
"pct05": 1225,
"pct25": 2239,
"pct50": 3033,
"pct75": 4111,
"pct95": 7601
},
{
"date": "2014-08-30",
"pct05": 1393,
"pct25": 2432,
"pct50": 3417,
"pct75": 4710,
"pct95": 8798
},
{
"date": "2014-08-31",
"pct05": 1175,
"pct25": 2020,
"pct50": 2768,
"pct75": 3889,
"pct95": 7744
},
{
"date": "2014-09-01",
"pct05": 989,
"pct25": 1655,
"pct50": 2218,
"pct75": 3167,
"pct95": 6018
},
{
"date": "2014-09-02",
"pct05": 1249,
"pct25": 2069,
"pct50": 2738,
"pct75": 3938,
"pct95": 7574
},
{
"date": "2014-09-03",
"pct05": 936,
"pct25": 1510,
"pct50": 1968,
"pct75": 2700,
"pct95": 5215
},
{
"date": "2014-09-04",
"pct05": 1264,
"pct25": 2039,
"pct50": 2657,
"pct75": 3646,
"pct95": 7042
},
{
"date": "2014-09-05",
"pct05": 1305,
"pct25": 2106,
"pct50": 2745,
"pct75": 3766,
"pct95": 7273
},
{
"date": "2014-09-06",
"pct05": 798,
"pct25": 1288,
"pct50": 1678,
"pct75": 2303,
"pct95": 4448
},
{
"date": "2014-09-07",
"pct05": 1314,
"pct25": 2120,
"pct50": 2763,
"pct75": 3791,
"pct95": 7321
},
{
"date": "2014-09-08",
"pct05": 1042,
"pct25": 1681,
"pct50": 2191,
"pct75": 3007,
"pct95": 5806
}
]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Trend Chart (Area + Line)</title>
<link rel="stylesheet" href="app.css">
</head>
<body>
</body>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="app.js"></script>
</html>
[
{
"date": "2014-08-06",
"type": "Client",
"version": "Jennifer Doudna, Emmanuelle Charpentier, and Martin Jinek of the University of California, Berkeley, publish a landmark paper demonstrating the use of CRISPR/Cas9 to edit DNA at specific sites. (0 articles in the 14 days following the announcement)"
},
{
"date": "2014-08-20",
"type": "Client",
"version": "February 15, 2013 – Feng Zhang of Harvard University publishes a paper outlining the use of CRISPR to edit the genomes of both mice and humans. (0 articles)"
},
{
"date": "2014-08-27",
"type": "Server",
"version": "3.5"
},
{
"date": "2014-09-03",
"type": "Client",
"version": "2.2"
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment