Skip to content

Instantly share code, notes, and snippets.

@evan-goode
Created October 28, 2018 04:10
Show Gist options
  • Save evan-goode/06f688d7e2a68a0125318e81fd458ebd to your computer and use it in GitHub Desktop.
Save evan-goode/06f688d7e2a68a0125318e81fd458ebd to your computer and use it in GitHub Desktop.
Graph
<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>
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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
$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