Skip to content

Instantly share code, notes, and snippets.

@thurt
Created May 8, 2017 01:04
Show Gist options
  • Save thurt/904796a64c9b726737aaba40ebe0d8ca to your computer and use it in GitHub Desktop.
Save thurt/904796a64c9b726737aaba40ebe0d8ca to your computer and use it in GitHub Desktop.
JS Bin // source https://jsbin.com/tulefi
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
<style id="jsbin-css">
body {
margin: 0;
overflow: hidden;
}
/* http://stackoverflow.com/a/15804615/3072751 */
div, svg {
display: block;
}
svg g {
outline: thin solid lightgray;
}
#info-panel {
position: absolute;
top: 0;
right: -400px;
width: 300px;
background-color: rgba(255, 255, 255, .8);
border-left: 1px solid gray;
border-bottom: 1px solid gray;
transition: right 250ms;
padding: 15px;
border-bottom-left-radius: 5px;
}
#info-panel.show {
right: 0;
}
#info-panel pre {
margin: 0;
white-space: pre-line;
}
</style>
</head>
<body>
<div id="info-panel">
<pre>
<span class="content"></span>
</pre>
</div>
<div id="svg"></div>
<script id="jsbin-javascript">
// Helpers
'use strict';
var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var select = function select(prop) {
return function (obj) {
return obj[prop];
};
};
var pipe = function pipe() {
for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) {
fns[_key] = arguments[_key];
}
return function (x) {
return fns.reduce(function (x, fn) {
return fn(x);
}, x);
};
};
var trace = function trace(x) {
console.log(x);return x;
};
// Models
var Data = {
_d: [{ location: 'A', itemCount: 25, x: 50, y: 42 }, { location: 'B', itemCount: 0, x: 66, y: 40 }, { location: 'C', itemCount: 40, x: 18, y: 12 }],
get: function get() {
return Data._d;
},
add: function add(dataObj) {
Data._d.push(dataObj);
},
set: function set(i, dataObj) {
Data._d[i] = _extends({}, Data._d[i], dataObj);
}
};
var Svg = {
instance: d3.select('#svg').append('svg'),
scale: function scale(_ref) {
var width = _ref.width;
var height = _ref.height;
Svg.instance.attr('width', width + 'px').attr('height', height + 'px');
return { width: width, height: height };
}
};
var G = {
instance: Svg.instance.call(d3.zoom().on('zoom', function () {
G.instance.attr('transform', d3.event.transform);
})).on('dblclick.zoom', null).append("g"),
enterCircles: function enterCircles(r) {
G.instance.selectAll('circle').data(Data.get(), select('x')).enter().append('circle').on('click', State.selected).on('mouseover', Circle.mouseover).on('mouseout', Circle.mouseout).attr('cx', pipe(select('x'), G.xScale)).attr('cy', pipe(select('y'), G.yScale)).attr('r', 0).transition().attr('r', r);
},
_updateCirclePositions: function _updateCirclePositions() {
G.instance.selectAll('circle').attr('cx', pipe(select('x'), G.xScale)).attr('cy', pipe(select('y'), G.yScale));
},
createDataPoint: function createDataPoint() {
var _d3$mouse = d3.mouse(this);
var _d3$mouse2 = _slicedToArray(_d3$mouse, 2);
var x = _d3$mouse2[0];
var y = _d3$mouse2[1];
Data.add({ x: G.xScale.invert(x), y: G.yScale.invert(y) });
G.enterCircles(Circle.r);
},
scale: function scale(_ref2) {
var width = _ref2.width;
var height = _ref2.height;
G.xScale = d3.scaleLinear().domain([0, 100]).range([0, width]);
G.yScale = d3.scaleLinear().domain([0, 100 / (width / height)]).range([0, height]);
G._updateCirclePositions();
}
};
var Circle = {
r: 6,
largeR: 10,
mouseover: function mouseover() {
d3.select(this).transition().attr('r', Circle.largeR);
},
mouseout: function mouseout() {
d3.select(this).transition().attr('r', Circle.r);
},
select: function select() {
d3.select(this).raise().on('mouseout', null).attr('stroke', 'red').transition().attr('r', Circle.largeR).attr('stroke-width', 5);
},
unselect: function unselect() {
d3.select(this).on('mouseout', Circle.mouseout).transition().attr('r', Circle.r).attr('stroke', 'black').attr('stroke-width', 0);
}
};
var InfoPanel = {
instance: d3.select('#info-panel'),
content: d3.select('#info-panel .content'),
select: function select() {
var datum = d3.select(this).datum();
var view = Object.keys(datum).map(function (key) {
return InfoPanel.unmarshal(key, datum[key]);
}).join('');
InfoPanel.content.html(view);
InfoPanel.instance.attr('class', 'show');
},
unselect: function unselect() {
InfoPanel.instance.attr('class', '');
},
unmarshal: function unmarshal(key, val) {
return '\n <span onclick="event.stopPropagation();" contenteditable="true">' + key + '</span>: <span onclick="event.stopPropagation();" contenteditable="true">' + val + '</span>\n ';
}
};
var Image = {
instance: G.instance.append('image').attr("xlink:href", 'https://upload.wikimedia.org/wikipedia/commons/d/dd/Whitehouse_MapRoom.svg'),
scale: function scale(_ref3) {
var width = _ref3.width;
var height = _ref3.height;
// http://stackoverflow.com/questions/1373035/how-do-i-scale-one-rectangle-to-the-maximum-size-possible-within-another-rectang
var bbox = Image.instance.node().getBBox();
var scale = Math.min(width / bbox.width, height / bbox.height);
Image.instance.attr('width', bbox.width * scale).attr('height', bbox.height * scale);
return Image.instance.node().getBBox();
}
};
var State = {
current: null,
selected: function selected() {
var _this = this;
// show data for this selection
InfoPanel.select.call(this);
// select the circle
Circle.select.call(this);
// change g click behavior
// so clicking outside the circle unselects it
G.instance.on('click', function () {
State.unselected.call(_this);
//prevent parents from responding to this event
d3.event.stopPropagation();
});
G.instance.on('dblclick', null);
G.instance.selectAll('circle').on('click', function () {
State.unselected.call(_this);
State.selected.call(this);
// prevent parents from responding to this event
d3.event.stopPropagation();
});
// update the current State
State.current = 'selected';
console.log(State.current);
// prevent parents from responding to this event
d3.event.stopPropagation();
},
unselected: function unselected() {
if (this !== null) {
Circle.unselect.call(this);
InfoPanel.unselect();
}
G.instance.on('dblclick', G.createDataPoint);
State.current = 'unselected';
console.log(State.current);
}
};
var Application = {
rescale: pipe(function () {
return {
//http://stackoverflow.com/questions/37492158/how-to-convert-pixel-to-translate-value-in-d3
width: Number.parseFloat(window.innerWidth),
height: Number.parseFloat(window.innerHeight)
};
}, Svg.scale, Image.scale, G.scale)
};
// Procedural processing
// this works on the initial render, but when i go to translate with mouse it jumps back to 0,0 first.
// in reality, the G.instance needs to be initialized with a translate built into it
//G.instance.attr("transform", "translate(" + 200 + "," + 0 + ")");
Application.rescale();
G.enterCircles(Circle.r);
State.unselected.call(null);
// makes application responsive to window resizes
window.addEventListener('resize', Application.rescale);
</script>
<script id="jsbin-source-css" type="text/css">body {
margin: 0;
overflow: hidden;
}
/* http://stackoverflow.com/a/15804615/3072751 */
div, svg {
display: block;
}
svg g {
outline: thin solid lightgray;
}
#info-panel {
position: absolute;
top: 0;
right: -400px;
width: 300px;
background-color: rgba(255, 255, 255, .8);
border-left: 1px solid gray;
border-bottom: 1px solid gray;
transition: right 250ms;
padding: 15px;
border-bottom-left-radius: 5px;
}
#info-panel.show {
right: 0;
}
#info-panel pre {
margin: 0;
white-space: pre-line;
}</script>
<script id="jsbin-source-javascript" type="text/javascript">// Helpers
const select = prop => obj => obj[prop];
const pipe = (...fns) => x => fns.reduce((x, fn) => fn(x), x);
const trace = x => { console.log(x); return x; };
// Models
const Data = {
_d: [
{ location: 'A', itemCount: 25, x: 50, y: 42 },
{ location: 'B', itemCount: 0, x: 66, y: 40 },
{ location: 'C', itemCount: 40, x: 18, y: 12 }
],
get: () => {
return Data._d;
},
add: (dataObj) => {
Data._d.push(dataObj);
},
set: (i, dataObj) => {
Data._d[i] = { ...Data._d[i], ...dataObj };
}
};
const Svg = {
instance: d3.select('#svg')
.append('svg'),
scale: function({ width, height }) {
Svg.instance
.attr('width', `${width}px`)
.attr('height', `${height}px`);
return { width, height };
}
};
const G = {
instance:
Svg.instance.call(d3.zoom().on('zoom', function () {
G.instance.attr('transform', d3.event.transform)
}))
.on('dblclick.zoom', null)
.append("g"),
enterCircles: function(r) {
G.instance.selectAll('circle')
.data(Data.get(), select('x'))
.enter().append('circle')
.on('click', State.selected)
.on('mouseover', Circle.mouseover)
.on('mouseout', Circle.mouseout)
.attr('cx', pipe(select('x'), G.xScale))
.attr('cy', pipe(select('y'), G.yScale))
.attr('r', 0)
.transition()
.attr('r', r);
},
_updateCirclePositions: function() {
G.instance.selectAll('circle')
.attr('cx', pipe(select('x'), G.xScale))
.attr('cy', pipe(select('y'), G.yScale))
},
createDataPoint: function() {
const [x, y] = d3.mouse(this);
Data.add({ x: G.xScale.invert(x), y: G.yScale.invert(y) });
G.enterCircles(Circle.r);
},
scale: function({ width, height }) {
G.xScale = d3.scaleLinear().domain([0, 100]).range([0, width]);
G.yScale = d3.scaleLinear().domain([0, 100/(width/height)]).range([0, height]);
G._updateCirclePositions();
}
};
const Circle = {
r: 6,
largeR: 10,
mouseover: function() {
d3.select(this)
.transition()
.attr('r', Circle.largeR);
},
mouseout: function() {
d3.select(this)
.transition()
.attr('r', Circle.r);
},
select: function() {
d3.select(this)
.raise()
.on('mouseout', null)
.attr('stroke', 'red')
.transition()
.attr('r', Circle.largeR).attr('stroke-width', 5);
},
unselect: function() {
d3.select(this)
.on('mouseout', Circle.mouseout)
.transition()
.attr('r', Circle.r).attr('stroke', 'black').attr('stroke-width', 0);
}
};
const InfoPanel = {
instance: d3.select('#info-panel'),
content: d3.select('#info-panel .content'),
select: function() {
const datum = d3.select(this).datum();
const view = Object.keys(datum)
.map(key => InfoPanel.unmarshal(key, datum[key]))
.join('');
InfoPanel.content.html(view);
InfoPanel.instance.attr('class', 'show');
},
unselect: function() {
InfoPanel.instance.attr('class', '');
},
unmarshal: function(key, val) {
return `
<span onclick="event.stopPropagation();" contenteditable="true">${key}</span>: <span onclick="event.stopPropagation();" contenteditable="true">${val}</span>
`;
}
};
const Image = {
instance: G.instance
.append('image')
.attr("xlink:href", 'https://upload.wikimedia.org/wikipedia/commons/d/dd/Whitehouse_MapRoom.svg'),
scale: function({ width, height }) {
// http://stackoverflow.com/questions/1373035/how-do-i-scale-one-rectangle-to-the-maximum-size-possible-within-another-rectang
const bbox = Image.instance.node().getBBox();
const scale = Math.min(width/bbox.width, height/bbox.height);
Image.instance.attr('width', bbox.width * scale).attr('height', bbox.height * scale);
return Image.instance.node().getBBox();
}
};
const State = {
current: null,
selected: function() {
const _this = this;
// show data for this selection
InfoPanel.select.call(this);
// select the circle
Circle.select.call(this);
// change g click behavior
// so clicking outside the circle unselects it
G.instance.on('click', function() {
State.unselected.call(_this);
//prevent parents from responding to this event
d3.event.stopPropagation();
});
G.instance.on('dblclick', null);
G.instance.selectAll('circle')
.on('click', function() {
State.unselected.call(_this);
State.selected.call(this);
// prevent parents from responding to this event
d3.event.stopPropagation();
});
// update the current State
State.current = 'selected';
console.log(State.current);
// prevent parents from responding to this event
d3.event.stopPropagation();
},
unselected: function() {
if (this !== null) {
Circle.unselect.call(this);
InfoPanel.unselect();
}
G.instance.on('dblclick', G.createDataPoint);
State.current = 'unselected';
console.log(State.current);
}
};
const Application = {
rescale: pipe(() => ({
//http://stackoverflow.com/questions/37492158/how-to-convert-pixel-to-translate-value-in-d3
width: Number.parseFloat(window.innerWidth),
height: Number.parseFloat(window.innerHeight)
}), Svg.scale, Image.scale, G.scale)
};
// Procedural processing
// this works on the initial render, but when i go to translate with mouse it jumps back to 0,0 first.
// in reality, the G.instance needs to be initialized with a translate built into it
//G.instance.attr("transform", "translate(" + 200 + "," + 0 + ")");
Application.rescale();
G.enterCircles(Circle.r);
State.unselected.call(null);
// makes application responsive to window resizes
window.addEventListener('resize', Application.rescale);</script></body>
</html>
body {
margin: 0;
overflow: hidden;
}
/* http://stackoverflow.com/a/15804615/3072751 */
div, svg {
display: block;
}
svg g {
outline: thin solid lightgray;
}
#info-panel {
position: absolute;
top: 0;
right: -400px;
width: 300px;
background-color: rgba(255, 255, 255, .8);
border-left: 1px solid gray;
border-bottom: 1px solid gray;
transition: right 250ms;
padding: 15px;
border-bottom-left-radius: 5px;
}
#info-panel.show {
right: 0;
}
#info-panel pre {
margin: 0;
white-space: pre-line;
}
// Helpers
'use strict';
var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var select = function select(prop) {
return function (obj) {
return obj[prop];
};
};
var pipe = function pipe() {
for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) {
fns[_key] = arguments[_key];
}
return function (x) {
return fns.reduce(function (x, fn) {
return fn(x);
}, x);
};
};
var trace = function trace(x) {
console.log(x);return x;
};
// Models
var Data = {
_d: [{ location: 'A', itemCount: 25, x: 50, y: 42 }, { location: 'B', itemCount: 0, x: 66, y: 40 }, { location: 'C', itemCount: 40, x: 18, y: 12 }],
get: function get() {
return Data._d;
},
add: function add(dataObj) {
Data._d.push(dataObj);
},
set: function set(i, dataObj) {
Data._d[i] = _extends({}, Data._d[i], dataObj);
}
};
var Svg = {
instance: d3.select('#svg').append('svg'),
scale: function scale(_ref) {
var width = _ref.width;
var height = _ref.height;
Svg.instance.attr('width', width + 'px').attr('height', height + 'px');
return { width: width, height: height };
}
};
var G = {
instance: Svg.instance.call(d3.zoom().on('zoom', function () {
G.instance.attr('transform', d3.event.transform);
})).on('dblclick.zoom', null).append("g"),
enterCircles: function enterCircles(r) {
G.instance.selectAll('circle').data(Data.get(), select('x')).enter().append('circle').on('click', State.selected).on('mouseover', Circle.mouseover).on('mouseout', Circle.mouseout).attr('cx', pipe(select('x'), G.xScale)).attr('cy', pipe(select('y'), G.yScale)).attr('r', 0).transition().attr('r', r);
},
_updateCirclePositions: function _updateCirclePositions() {
G.instance.selectAll('circle').attr('cx', pipe(select('x'), G.xScale)).attr('cy', pipe(select('y'), G.yScale));
},
createDataPoint: function createDataPoint() {
var _d3$mouse = d3.mouse(this);
var _d3$mouse2 = _slicedToArray(_d3$mouse, 2);
var x = _d3$mouse2[0];
var y = _d3$mouse2[1];
Data.add({ x: G.xScale.invert(x), y: G.yScale.invert(y) });
G.enterCircles(Circle.r);
},
scale: function scale(_ref2) {
var width = _ref2.width;
var height = _ref2.height;
G.xScale = d3.scaleLinear().domain([0, 100]).range([0, width]);
G.yScale = d3.scaleLinear().domain([0, 100 / (width / height)]).range([0, height]);
G._updateCirclePositions();
}
};
var Circle = {
r: 6,
largeR: 10,
mouseover: function mouseover() {
d3.select(this).transition().attr('r', Circle.largeR);
},
mouseout: function mouseout() {
d3.select(this).transition().attr('r', Circle.r);
},
select: function select() {
d3.select(this).raise().on('mouseout', null).attr('stroke', 'red').transition().attr('r', Circle.largeR).attr('stroke-width', 5);
},
unselect: function unselect() {
d3.select(this).on('mouseout', Circle.mouseout).transition().attr('r', Circle.r).attr('stroke', 'black').attr('stroke-width', 0);
}
};
var InfoPanel = {
instance: d3.select('#info-panel'),
content: d3.select('#info-panel .content'),
select: function select() {
var datum = d3.select(this).datum();
var view = Object.keys(datum).map(function (key) {
return InfoPanel.unmarshal(key, datum[key]);
}).join('');
InfoPanel.content.html(view);
InfoPanel.instance.attr('class', 'show');
},
unselect: function unselect() {
InfoPanel.instance.attr('class', '');
},
unmarshal: function unmarshal(key, val) {
return '\n <span onclick="event.stopPropagation();" contenteditable="true">' + key + '</span>: <span onclick="event.stopPropagation();" contenteditable="true">' + val + '</span>\n ';
}
};
var Image = {
instance: G.instance.append('image').attr("xlink:href", 'https://upload.wikimedia.org/wikipedia/commons/d/dd/Whitehouse_MapRoom.svg'),
scale: function scale(_ref3) {
var width = _ref3.width;
var height = _ref3.height;
// http://stackoverflow.com/questions/1373035/how-do-i-scale-one-rectangle-to-the-maximum-size-possible-within-another-rectang
var bbox = Image.instance.node().getBBox();
var scale = Math.min(width / bbox.width, height / bbox.height);
Image.instance.attr('width', bbox.width * scale).attr('height', bbox.height * scale);
return Image.instance.node().getBBox();
}
};
var State = {
current: null,
selected: function selected() {
var _this = this;
// show data for this selection
InfoPanel.select.call(this);
// select the circle
Circle.select.call(this);
// change g click behavior
// so clicking outside the circle unselects it
G.instance.on('click', function () {
State.unselected.call(_this);
//prevent parents from responding to this event
d3.event.stopPropagation();
});
G.instance.on('dblclick', null);
G.instance.selectAll('circle').on('click', function () {
State.unselected.call(_this);
State.selected.call(this);
// prevent parents from responding to this event
d3.event.stopPropagation();
});
// update the current State
State.current = 'selected';
console.log(State.current);
// prevent parents from responding to this event
d3.event.stopPropagation();
},
unselected: function unselected() {
if (this !== null) {
Circle.unselect.call(this);
InfoPanel.unselect();
}
G.instance.on('dblclick', G.createDataPoint);
State.current = 'unselected';
console.log(State.current);
}
};
var Application = {
rescale: pipe(function () {
return {
//http://stackoverflow.com/questions/37492158/how-to-convert-pixel-to-translate-value-in-d3
width: Number.parseFloat(window.innerWidth),
height: Number.parseFloat(window.innerHeight)
};
}, Svg.scale, Image.scale, G.scale)
};
// Procedural processing
// this works on the initial render, but when i go to translate with mouse it jumps back to 0,0 first.
// in reality, the G.instance needs to be initialized with a translate built into it
//G.instance.attr("transform", "translate(" + 200 + "," + 0 + ")");
Application.rescale();
G.enterCircles(Circle.r);
State.unselected.call(null);
// makes application responsive to window resizes
window.addEventListener('resize', Application.rescale);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment