Skip to content

Instantly share code, notes, and snippets.

@dsaffo
Last active July 28, 2020 19:57
Show Gist options
  • Save dsaffo/05f53164cc14b375065404eca18247a0 to your computer and use it in GitHub Desktop.
Save dsaffo/05f53164cc14b375065404eca18247a0 to your computer and use it in GitHub Desktop.
Collaborative wheres Waldo game by Micha Schwab using VisConnect

VisConnect Wheres Waldo

This VisConnect example, created by Micha Schawb, demonstrates divde and conquer collaberative brushing. Click the visconnect logo on the bottom right and send the coppied URL to a friend. Once they open the link all your interactions will be synchronized!

<!DOCTYPE html>
<meta charset="utf-8">
<style>
#left {
float: left;
width: 400px;
}
#right {
float: left;
width: 400px;
margin-left: 10px;
}
#overview {
position: relative;
}
#overview svg {
position: absolute;
left: 0;
top: 0;
}
#detail {
position: relative;
}
#detail-image {
background-image: url(https://user-images.githubusercontent.com/13991410/88224720-eaff2700-cc58-11ea-8d3a-e7bd9b8b04c2.jpg);
width: 400px;
height: 200px;
visibility: hidden;
}
.marker-button {
margin: 10px 10px 0 0;
padding: 8px;
border: none;
}
#noWaldoButton {
background: #99ff99;
}
#waldoButton {
background: #ff9999;
}
#title {
position: relative;
}
#waldoLogo {
position: absolute;
top: -10px;
left: 240px;
}
#recommendedBrush {
width: 20px;
height: 20px;
display: inline-block;
background: #ff9999;
border: 1px solid #aa0000;
}
.detail-crop {
position: absolute;
background: #eee;
}
#detail-crop-left {
left: 0;
}
#detail-crop-right {
right: 0;
}
#detail-crop-top {
top: 0;
}
#detail-crop-bottom {
bottom: 0;
}
#detail-crop-left, #detail-crop-right {
height: 100%;
top: 0;
}
#detail-crop-top, #detail-crop-bottom {
width: 100%;
left: 0;
}
</style>
<body collaboration="live" custom-events="brush-message, label-message" ignore-events="all">
<h1 id="title">Where's Waldo? <img src="https://user-images.githubusercontent.com/13991410/88224723-ed618100-cc58-11ea-84f6-dfe6d4e407f2.png" width="50" id="waldoLogo" /></h1>
<div id="left">
<h2>Overview</h2>
<div id="overview">
<img src="https://user-images.githubusercontent.com/13991410/88224720-eaff2700-cc58-11ea-8d3a-e7bd9b8b04c2.jpg" width="400" />
</div>
Recommended brush size: <div id="recommendedBrush"></div>
</div>
<div id="right">
<h2>Detail</h2>
<div id="detail">
<div id="detail-image"></div>
<div id="crop">
<div id="detail-crop-left" class="detail-crop"></div>
<div id="detail-crop-right" class="detail-crop"></div>
<div id="detail-crop-top" class="detail-crop"></div>
<div id="detail-crop-bottom" class="detail-crop"></div>
</div>
</div>
<button id="noWaldoButton" class="marker-button">No Waldo Here (Shortcut: &lt;N&gt;)</button>
<button id="waldoButton" class="marker-button">Found Waldo! (Shortcut: &lt;W&gt;)</button>
<!--<button id="removeLabelButton" class="marker-button">Remove Label (Shortcut: &lt;R&gt;)</button>-->
</div>
<script src="https://unpkg.com/peerjs@1.0.4/dist/peerjs.min.js"></script>
<script src="https://unpkg.com/visconnect@1.0.1/visconnect-bundle.js"></script>
<script src="//d3js.org/d3.v5.js"></script>
<script>
const widthOrig = 6447;
const heightOrig = 3772;
const scale = 400 / widthOrig;
const width = scale * widthOrig;
const height = scale * heightOrig;
const overviewWrap = d3.select('#overview');
const overviewSvg = overviewWrap.append("svg").attr("width", width).attr("height", height);
const overviewLabels = overviewSvg.append('g').style('opacity', '0.5');
const detail = d3.select('#detail-image');
let x0, y0, x1, y1;
const cropLeft = d3.select('#detail-crop-left');
const cropRight = d3.select('#detail-crop-right');
const cropTop = d3.select('#detail-crop-top');
const cropBottom = d3.select('#detail-crop-bottom');
const updateDetailView = () => {
const [[x0orig, y0orig], [x1orig, y1orig]] = d3.event.selection;
[x0, y0, x1, y1] = [x0orig/scale, y0orig/scale, x1orig/scale, y1orig/scale];
let [selWidth, selHeight] = [x1 - x0, y1 - y0];
let cropPercent;
let cropXoffset = 0;
let cropYoffset = 0;
if(selWidth < selHeight) {
cropPercent = 1 - selWidth / selHeight;
selWidth = selHeight;
cropXoffset = 600*cropPercent/2;
cropLeft.style('width', `${600*cropPercent/2}px`);
cropRight.style('width', `${600*cropPercent/2}px`);
cropTop.style('height', `0`);
cropBottom.style('height', `0`);
} else {
cropPercent = 1 - selHeight / selWidth;
selHeight = selWidth;
cropLeft.style('width', `0`);
cropRight.style('width', `0`);
cropYoffset = 400*cropPercent/2;
cropTop.style('height', `${400*cropPercent/2}px`);
cropBottom.style('height', `${400*cropPercent/2}px`);
}
const [imageWidth, imageHeight] = [600*widthOrig/selWidth, 400*heightOrig/selHeight];
detail.style('visibility', 'visible');
detail.style('background-size', `${imageWidth}px ${imageHeight}px`);
detail.style('background-position', `${-x0*imageWidth/widthOrig+cropXoffset}px ${-y0*imageHeight/heightOrig+cropYoffset}px`);
};
const brush = vc.brush()
.extent([[0,0], [width, height]])
.on("brush", updateDetailView);
overviewSvg.call(brush);
const onLabel = (color) => {
return () => {
const event = new CustomEvent('label-message', {detail: {positions: [x0, y0, x1, y1], color}});
document.body.dispatchEvent(event);
}
}
document.body.addEventListener('label-message', (e) => {
const [x0, y0, x1, y1] = e.detail.positions;
overviewLabels.append('rect')
.attr('x', x0 * scale)
.attr('y', y0 * scale)
.attr('width', (x1 - x0) * scale)
.attr('height', (y1 - y0) * scale)
.attr('fill', e.detail.color)
.style('pointer-events', 'none');
});
const onWaldo = onLabel('#ff3333');
const onNoWaldo = onLabel('#99ff99');
document.getElementById('noWaldoButton').addEventListener('click', onNoWaldo);
document.getElementById('waldoButton').addEventListener('click', onWaldo);
window.addEventListener('keydown', (event) => {
if(event.key === 'n') { onNoWaldo(); }
if(event.key === 'w') { onWaldo(); }
})
</script>
@dsaffo
Copy link
Author

dsaffo commented Jul 22, 2020

megamap
waldo
beach

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment