A Pen by Evan Goode on CodePen.
Created
October 28, 2018 04:10
-
-
Save evan-goode/06f688d7e2a68a0125318e81fd458ebd to your computer and use it in GitHub Desktop.
Graph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<main> | |
<section class='info'> | |
<p>x̄<sub>1</sub>=<span></span></p> | |
<p>x̄<sub>2</sub>=<span></span></p> | |
</section> | |
<section class="graphs"> | |
<svg id='main' class='graph' viewbox='0 0 384 512'></svg> | |
</section> | |
</main> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const selector = '#main'; | |
const padding = 1 - (2 / (1 + Math.sqrt(5))); | |
const width = 384; | |
const height = 512; | |
const margins = { | |
top: 8, | |
right: 2, | |
bottom: 26, | |
left: 36, | |
} | |
const range = { | |
minimum: 0, | |
maximum: 100 | |
}; | |
const columnRange = { | |
minimum: 1, | |
maximum: 100, | |
}; | |
const errorBarRange = { | |
minimum: 2, | |
maximum: 100 | |
}; | |
const capSize = 8; | |
var data = /* JSON.parse(localStorage.getItem('data')) || */ [ | |
{ | |
identifier: 'A', | |
value: 0.6 * (range.maximum - range.minimum) + range.minimum, | |
se: 10 | |
}, | |
{ | |
identifier: 'B', | |
value: 0.3 * (range.maximum - range.minimum) + range.minimum, | |
se: 15 | |
} | |
]; | |
window.onbeforeunload = () => { | |
localStorage.setItem('data', JSON.stringify(data)); | |
} | |
var main = d3.select(selector) | |
main.attr('viewBox', `0, 0, ${width + margins.left + margins.right}, ${height + margins.top + margins.bottom}`) | |
.append('g') | |
.attr('class', 'main-group'); | |
main.select('.main-group') | |
.attr('transform', `translate(${margins.left}, ${margins.top})`); | |
var x = d3.scale.ordinal() | |
.domain(data.map(data => { | |
return data.identifier; | |
})) | |
.rangeRoundBands([0, width], padding); | |
var y = d3.scale.linear() | |
.range([height, 0]) | |
.domain([range.minimum, range.maximum]); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient('bottom'); | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient('left'); | |
var dragColumn = d3.behavior.drag() | |
.origin(data => { | |
return data; | |
}) | |
.on('dragstart', startDrag) | |
.on('drag', resizeColumn); | |
var dragErrorBar = d3.behavior.drag() | |
.origin(data => { | |
return data; | |
}) | |
.on('dragstart', startDrag) | |
.on('drag', resizeErrorBar); | |
var original = {}; | |
function startDrag(data) { | |
original = { | |
mouse: d3.mouse(this)[1], | |
value: data.value, | |
se: data.se, | |
}; | |
} | |
function resizeColumn(data) { | |
data.value = Math.max( | |
columnRange.minimum + data.se, | |
Math.min( | |
columnRange.maximum - data.se, | |
original.value + ((range.maximum / y.range()[0]) * (original.mouse - d3.mouse(this)[1])) | |
) | |
); | |
d3.select(this) | |
.call(updateColumn); | |
d3.select(this.parentNode).select('.error-bar') | |
.call(updateErrorBar); | |
} | |
function resizeErrorBar(data) { | |
data.se = Math.max( | |
errorBarRange.minimum, | |
Math.min( | |
errorBarRange.maximum, | |
original.se + (((y.range()[0] - original.mouse) * (range.maximum / y.range()[0]) >= data.value) ? 1 : -1) * ((range.maximum / y.range()[0]) * (original.mouse - d3.mouse(this)[1])), | |
data.value - columnRange.minimum, | |
columnRange.maximum - data.value | |
) | |
); | |
d3.select(this) | |
.call(updateErrorBar); | |
} | |
function updateColumn(selection) { | |
selection.attr('x', data => { | |
return x(data.identifier); | |
}) | |
.attr('y', data => { | |
return y(data.value); | |
}) | |
.attr('width', x.rangeBand()) | |
.attr('height', data => { | |
return y.range()[0] - y(data.value); | |
}); | |
} | |
function updateErrorBar(selection) { | |
let middle = (data) => { | |
return x.rangeBand() / 2 + x(data.identifier); | |
} | |
let top = (data) => { | |
return y(data.value + data.se); | |
} | |
let bottom = (data) => { | |
return y(data.value - data.se); | |
} | |
let left = (data) => { | |
return middle(data) - capSize; | |
} | |
let right = (data) => { | |
return middle(data) + capSize; | |
} | |
selection | |
.select('.top') | |
.attr('x1', left) | |
.attr('y1', top) | |
.attr('x2', right) | |
.attr('y2', top); | |
selection | |
.select('.middle') | |
.attr('x1', middle) | |
.attr('y1', top) | |
.attr('x2', middle) | |
.attr('y2', bottom); | |
selection.select('.bottom') | |
.attr('x1', left) | |
.attr('y1', bottom) | |
.attr('x2', right) | |
.attr('y2', bottom); | |
selection.select('.resize') | |
.attr('x', left) | |
.attr('y', top) | |
.attr('width', (data) => { | |
return right(data) - left(data); | |
}) | |
.attr('height', (data) => { | |
return bottom(data) - top(data); | |
}); | |
} | |
var columnGroups = main.select('.main-group') | |
.selectAll('rect') | |
.data(data) | |
.enter() | |
.append('g') | |
.attr('class', (data) => { | |
return data.identifier.toLowerCase(); | |
}); | |
var columns = columnGroups | |
.append('rect') | |
.attr('class', 'column') | |
.call(updateColumn) | |
.call(dragColumn); | |
var errorBars = columnGroups.append('g') | |
.attr('class', 'error-bar') | |
.call(dragErrorBar); | |
errorBars.append('line') | |
.attr('class', 'top'); | |
errorBars.append('line') | |
.attr('class', 'middle'); | |
errorBars.append('line') | |
.attr('class', 'bottom'); | |
errorBars.append('rect') | |
.attr('class', 'resize'); | |
errorBars.call(updateErrorBar); | |
main.select('.main-group') | |
.append('g') | |
.attr('class', 'x axis') | |
.attr('transform', `translate(0, ${height})`) | |
.call(xAxis) | |
.selectAll('text') | |
.attr('transform', 'translate(0, 4)'); | |
main.select('.main-group') | |
.append('g') | |
.attr('class', 'y axis') | |
.call(yAxis); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$white-blue: hsl(192, 15%, 94%); | |
$dark-blue: hsl(210, 27%, 24%); | |
$light-blue: hsl(204, 67%, 54%); | |
$red: hsl(4, 76%, 58%); | |
$stroke-width: 2px; | |
* { | |
box-sizing: border-box; | |
} | |
html, body { | |
height: 100%; | |
margin: 0; | |
} | |
body { | |
background: $white-blue; | |
color: $dark-blue; | |
font-family: Helvetica, Arial, sans-serif; | |
} | |
main { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
flex-direction: row; | |
width: 100%; | |
height: 100%; | |
} | |
.graph { | |
// width: 100%; | |
max-height: 75%; | |
max-width: 75%; | |
padding: 16px; | |
-webkit-tap-highlight-color: hsla(0, 0%, 0%, 0); | |
-webkit-touch-callout: none; | |
user-select: none; | |
.axis { | |
text { | |
fill: currentColor; | |
} | |
&.x text { | |
margin-top: 2px; | |
} | |
path, line { | |
fill: none; | |
stroke: $dark-blue; | |
stroke-width: $stroke-width; | |
shape-rendering: crispEdges; | |
} | |
} | |
.column, .resize { | |
cursor: ns-resize; | |
} | |
.resize { | |
opacity: 0; | |
} | |
.a { | |
fill: $light-blue; | |
} | |
.b { | |
fill: $red; | |
} | |
line { | |
stroke: $dark-blue; | |
stroke-width: $stroke-width; | |
} | |
} | |
.info { | |
padding: 64px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment