Skip to content

Instantly share code, notes, and snippets.

@chrismllr
Last active April 19, 2019 15: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 chrismllr/c91e19b4958044b3e554948649d10527 to your computer and use it in GitHub Desktop.
Save chrismllr/c91e19b4958044b3e554948649d10527 to your computer and use it in GitHub Desktop.
bubble scatter
license: mit
[
{ "timing": 250, "count": 1, "date": 1523163600000 },
{ "timing": 450, "count": 10, "date": 1523163600000 },
{ "timing": 750, "count": 2, "date": 1523163600000 },
{ "timing": 100, "count": 1, "date": 1523250000000 },
{ "timing": 290, "count": 7, "date": 1523250000000 },
{ "timing": 490, "count": 2, "date": 1523250000000 },
{ "timing": 450, "count": 80, "date": 1523336400000 },
{ "timing": 600, "count": 2, "date": 1523336400000 },
{ "timing": 250, "count": 3, "date": 1523422800000 },
{ "timing": 450, "count": 1, "date": 1523422800000 },
{ "timing": 250, "count": 3, "date": 1523509200000 },
{ "timing": 450, "count": 10, "date": 1523509200000 },
{ "timing": 750, "count": 2, "date": 1523509200000 },
{ "timing": 250, "count": 3, "date": 1523595600000 },
{ "timing": 450, "count": 10, "date": 1523595600000 },
{ "timing": 750, "count": 2, "date": 1523595600000 },
{ "timing": 100, "count": 65, "date": 1523682000000 },
{ "timing": 120, "count": 1, "date": 1523768400000 },
{ "timing": 450, "count": 20, "date": 1523768400000 },
{ "timing": 750, "count": 80, "date": 1523768400000 },
{ "timing": 550, "count": 3, "date": 1523854800000 },
{ "timing": 700, "count": 25, "date": 1523854800000 },
{ "timing": 800, "count": 1, "date": 1523854800000 },
{ "timing": 300, "count": 3, "date": 1523941200000 },
{ "timing": 350, "count": 46, "date": 1523941200000 },
{ "timing": 475, "count": 2, "date": 1523941200000 }
]
[
{ "timing": 250, "count": 1, "date": 0 },
{ "timing": 450, "count": 10, "date": 0 },
{ "timing": 750, "count": 2, "date": 0 },
{ "timing": 100, "count": 1, "date": 1 },
{ "timing": 290, "count": 7, "date": 1 },
{ "timing": 490, "count": 2, "date": 1 },
{ "timing": 450, "count": 40, "date": 2 },
{ "timing": 600, "count": 2, "date": 2 },
{ "timing": 250, "count": 3, "date": 3 },
{ "timing": 450, "count": 1, "date": 3 },
{ "timing": 250, "count": 3, "date": 4 },
{ "timing": 450, "count": 10, "date": 4 },
{ "timing": 750, "count": 2, "date": 4 },
{ "timing": 250, "count": 3, "date": 5 },
{ "timing": 450, "count": 10, "date": 5 },
{ "timing": 750, "count": 2, "date": 5 },
{ "timing": 250, "count": 3, "date": 6 },
{ "timing": 450, "count": 10, "date": 6 },
{ "timing": 250, "count": 3, "date": 7 },
{ "timing": 450, "count": 10, "date": 7 },
{ "timing": 750, "count": 2, "date": 7 },
{ "timing": 250, "count": 3, "date": 8 },
{ "timing": 450, "count": 10, "date": 8 },
{ "timing": 750, "count": 2, "date": 8 },
{ "timing": 250, "count": 3, "date": 9 },
{ "timing": 450, "count": 10, "date": 9 },
{ "timing": 750, "count": 2, "date": 9 }
]
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.js"></script>
<style>
.chart {
background-color: #fcfcfc;
}
.domain {
stroke: #E3E3E3;
}
.tick text,
.sla-label {
font-size: 14px;
font-family: monospace;
fill: #848E99;
}
.sla-label {
fill: #fff;
}
</style>
</head>
<body>
<div class="chart"></div>
<script>
const margin = { top: 40, right: 35, bottom: 80, left: 80 };
const width = 900 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const fullWidth = width + margin.left + margin.right;
const fullHeight = height + margin.top + margin.bottom;
const svg = d3.select('.chart')
.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 + ')');
d3.json('data-timestamps.json', (err, data) => {
// DEFINE SCALES
const xScale = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, width])
.nice();
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.timing)])
.range([height, 0])
.nice();
const rScale = d3.scaleSqrt()
.domain([0, d3.max(data, d => d.count)])
.range([2, 20]);
// DEFINE AXES
const yAxis = d3.axisLeft(yScale)
.tickFormat(v => `${v}ms`);
const xAxis = d3.axisBottom(xScale)
.tickFormat((v) => dateFns.format(v, 'MMM D'));
// ADD AXES
svg.append('g')
.attr('class', 'y-axis')
.call(yAxis);
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// ADD GRID
svg.selectAll('.y-axis .tick line')
.attr('stroke', '#E3E3E3')
.attr('stroke-width', 1)
.attr('x1', 0)
.attr('x2', width);
svg.selectAll('.x-axis .tick line')
.attr('stroke', '#E3E3E3')
.attr('stroke-width', 1)
.attr('y1', 0)
.attr('y2', -height)
// DEFINE BUBBLES
const bubbles = svg.selectAll('.bubble')
.data(data)
.enter()
.append('g')
.attr('class', 'bubble')
.attr('transform', d => {
return `translate(${xScale(d.date)}, ${yScale(d.timing)})`
})
.attr('opacity', 0.8)
// DEFINE SLA Line to indicate long request times
const sla = svg.selectAll('.sla')
.data([500])
.enter()
.append('g')
.attr('class', 'sla')
.attr('transform', d =>
`translate(0, ${yScale(d)})`
);
// ADD SLA LINE
sla.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('stroke-width', 2)
.attr('stroke-dasharray', '6 3')
.attr('stroke', '#F74C7D')
// ADD SLA LABELS
const rectHeight = 20;
const rectOffset = 2;
const rectPadding = 10;
sla.append('rect')
.attr('height', rectHeight)
.attr('fill', '#F74C7D')
.attr('rx', '3')
.attr('ry', '3');
sla.append('text')
.attr('class', 'sla-label')
.text(d => `${d}ms`);
// POSITION SLA RECT
sla.selectAll('rect')
.attr('width', function() {
return this.parentNode.querySelector('.sla-label')
.getComputedTextLength() + rectPadding;
})
.attr('transform', `translate(0, ${-rectHeight - rectOffset})`);
// POSTION SLA TEXT
sla.selectAll('text')
.attr('dx', rectPadding / 2)
.attr('dy', function() {
const rectBox = this.parentNode.querySelector('rect').getBoundingClientRect();
const textBox = this.getBoundingClientRect();
return -((rectBox.height / 2) - (textBox.height / 3) + rectOffset);
})
// ADD BUBBLES
bubbles.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', d => rScale(d.count))
.attr('fill', '#4F65FF');
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment