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!
Last active
July 28, 2020 19:57
-
-
Save dsaffo/05f53164cc14b375065404eca18247a0 to your computer and use it in GitHub Desktop.
Collaborative wheres Waldo game by Micha Schwab using VisConnect
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
<!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: <N>)</button> | |
<button id="waldoButton" class="marker-button">Found Waldo! (Shortcut: <W>)</button> | |
<!--<button id="removeLabelButton" class="marker-button">Remove Label (Shortcut: <R>)</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> |
Author
dsaffo
commented
Jul 22, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment