|
|
|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
body { |
|
font-family: Helvetica; |
|
font-size: 16px; |
|
} |
|
ol li { |
|
margin-bottom: 10px; |
|
} |
|
li.highlight { |
|
background: yellow; |
|
} |
|
pre { |
|
display: inline; |
|
background: lightgray; |
|
} |
|
#viewport { |
|
width: 960px; |
|
height: 350px; |
|
} |
|
path { |
|
fill: none; |
|
stroke: #999; |
|
} |
|
path.hidden { |
|
display: none; |
|
} |
|
path.init { |
|
stroke-width: 2; |
|
} |
|
path.piece { |
|
stroke-width: 6; |
|
stroke: none; |
|
} |
|
line.sep { |
|
stroke-width: 2; |
|
stroke: none; |
|
} |
|
</style> |
|
|
|
|
|
|
|
<body> |
|
|
|
<h2>Think Oregon Trail as Cards</h2> |
|
|
|
<div id="viewport"></div> |
|
|
|
<h2>Quest</h2> |
|
<ol id="instructions"> |
|
<li data-id="1">Embark on your new adventure</li> |
|
<li data-id="2">Engage the Wampas and Collect gold and other trinkets</li> |
|
<li data-id="3">Pass through the Valley of Many Moons</li> |
|
<li data-id="4">Arrive at New Port Gultch and trade your cargo</li> |
|
<li data-id="5">Return home</li> |
|
</ol> |
|
|
|
<h4>Dev Log</h4> |
|
<p>Sept 5 2018<p> |
|
<p>Sept 23 2018 - upgraded to D3 v5<p> |
|
|
|
<h4>To Do</h4> |
|
<p>show a party progressing along the trail</p> |
|
|
|
<script src="https://d3js.org/d3.v5.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> |
|
<script> |
|
|
|
// State |
|
|
|
const margin = {top: 0, right: 20, bottom: 0, left: 20}; |
|
const width = 1300 - margin.left - margin.right; |
|
const height = 350 - margin.top - margin.bottom; |
|
|
|
// generates an array of 12 colors but I only need 5 |
|
// and each color should have a theme ie forest is green |
|
const colorsGen = d3.schemePaired; |
|
|
|
const water = "lightblue"; |
|
const beach = "#fdbf6f"; |
|
const forest = "#33a02c"; |
|
const burn = "#b15928"; |
|
|
|
const colors = [water, |
|
beach, |
|
forest, |
|
burn, |
|
beach] |
|
|
|
console.log(colors); |
|
|
|
let pts = []; |
|
const numPts = 7; |
|
|
|
// this is interesting but I don't want a random color order but I could possibly re-purpose the random sort for other things |
|
/* |
|
function randomSort(a, b) { |
|
return Math.random() > .5 ? 1 : -1; |
|
} |
|
colors.sort(randomSort); |
|
*/ |
|
|
|
function getPoints(numPts){ |
|
|
|
const halfHeight = height / 2; |
|
const heights = [ halfHeight - 50, |
|
halfHeight - 75, |
|
halfHeight - 100, |
|
halfHeight - 25, |
|
halfHeight -10]; |
|
|
|
function getHeight(height){ |
|
return Math.random() * ( height - 100 ); |
|
} |
|
|
|
let newPts = []; |
|
|
|
_.times( numPts, function(num){ |
|
newPts.push([num*(width/numPts), heights[num]]); |
|
newPts.push([num*(width/numPts)+50, heights[num]]); |
|
|
|
}); |
|
|
|
return newPts; |
|
} |
|
|
|
pts = getPoints(numPts); |
|
|
|
const path = d3.line() |
|
.curve(d3.curveCardinal); |
|
|
|
const svg = d3.select("#viewport").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom); |
|
|
|
const g = svg.append("g") |
|
.attr("id","world") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
const world = g; |
|
|
|
const line = g.append("path") |
|
.attr("class", "hidden init") |
|
.attr("d", path(pts)); |
|
|
|
const p = line.node(); |
|
const pLength = p.getTotalLength(); |
|
|
|
let cumu = 0; |
|
const sampleInterval = .25; |
|
|
|
// Main Functions |
|
|
|
function showLine(callback, line) { |
|
|
|
line.classed("hidden", false) |
|
.attr("stroke-dasharray", pLength + " " + pLength) |
|
.attr("stroke-dashoffset", pLength) |
|
.transition() |
|
.duration(1500) |
|
.ease(d3.easeLinear) |
|
.attr("stroke-dashoffset", 0) |
|
.on("end", function() { |
|
callback(); |
|
}); |
|
} |
|
|
|
function splitPath() { |
|
|
|
const numPieces = 5; |
|
const pieceSizes = []; |
|
const pieces = []; |
|
|
|
for (let i=0; i<numPieces; i++) { |
|
pieceSizes.push({i: i, size: Math.floor(Math.random() * 20) + 5}); |
|
} |
|
|
|
const size = pieceSizes.reduce(function(a, b) { |
|
return a + b.size; |
|
}, 0); |
|
|
|
const pieceSize = pLength / size; |
|
|
|
pieceSizes.forEach(function(x, j) { |
|
var segs = []; |
|
for (let i=0; i<=x.size+sampleInterval; i+=sampleInterval) { |
|
pt = p.getPointAtLength((i*pieceSize)+(cumu*pieceSize)); |
|
segs.push([pt.x, pt.y]); |
|
} |
|
angle = Math.atan2(segs[1][1] - segs[0][1], segs[1][0] - segs[0][0]) * 180 / Math.PI; |
|
pieces.push({id: j, segs: segs, angle: angle}); |
|
cumu += x.size; |
|
}); |
|
|
|
return pieces; |
|
} |
|
|
|
function drawSegments(pieces) { |
|
|
|
function hiliteStoryPoint(i) { |
|
|
|
function hilite() { |
|
return this.getAttribute("data-id") === String(i); |
|
} |
|
|
|
d3.selectAll("#instructions li") |
|
.classed("highlight", hilite); |
|
} |
|
|
|
const lines = g.selectAll("path.piece") |
|
.data(pieces) |
|
.enter().append("path") |
|
.attr("class", "piece") |
|
.attr("d", function(d, i) { |
|
return path(d.segs); |
|
}); |
|
|
|
// end of each segment there should be a goal icon |
|
const seps = g.selectAll("line.sep") |
|
.data(pieces) |
|
.enter().append("line") |
|
.attr("class", "sep") |
|
.attr("transform", function(d, i) { |
|
return "translate(" |
|
+ d.segs[0][0] + "," |
|
+ d.segs[0][1] + ")rotate(" |
|
+ (d.angle-90) + " 0 0)"; |
|
}) |
|
.attr("x1", -12) |
|
.attr("y1", 0) |
|
.attr("x2", 12) |
|
.attr("y2", 0); |
|
|
|
const drawLineEverySec = 1000; |
|
|
|
let tickCount = 1; |
|
lines.transition() |
|
.duration(0) |
|
.delay(function(d, i) { |
|
|
|
return i * drawLineEverySec; |
|
}) |
|
.style("stroke", function(d, i) { |
|
return colors[i]; |
|
}) |
|
.tween("tick", function(){ |
|
hiliteStoryPoint(tickCount); |
|
tickCount++; |
|
}) |
|
.on("end", function(d, i) { |
|
if (i === pieces.length-1) { |
|
// turns off story point hilites |
|
setTimeout( function(){ |
|
d3.selectAll("#instructions li") |
|
.classed("highlight", false); |
|
}, drawLineEverySec) |
|
|
|
} |
|
}) |
|
|
|
// seperations |
|
seps.transition() |
|
.duration(0) |
|
.delay(function(d, i) { |
|
return i * drawLineEverySec; |
|
}) |
|
.style("stroke-width", "10px") |
|
.style("stroke", "#fff"); |
|
} |
|
|
|
let hiliteCount = 0; |
|
|
|
function defineLine() { |
|
|
|
const pieces = splitPath(); |
|
|
|
const segments = g.selectAll("g.segment") |
|
.data(pieces) |
|
.enter().append("g"), |
|
|
|
pts = []; |
|
|
|
pieces.forEach(function(x) { |
|
x.segs.forEach(function(seg, i) { |
|
if (i > 0 && i % 2 === 0) { |
|
pts.push({id: x.id, seg}); |
|
} |
|
}); |
|
}); |
|
|
|
const dots = g.selectAll("circle") |
|
.data(pts) |
|
.enter().append("circle") |
|
.attr("cx", function(d, i) { |
|
return d.seg[0]; |
|
}) |
|
.attr("cy", function(d, i) { |
|
return d.seg[1]; |
|
}) |
|
.style("fill", function(d, i, j) { |
|
return colors[d.id]; |
|
}) |
|
.attr("r", 0); |
|
|
|
dots.transition() |
|
.duration(0) |
|
.delay(function(d, i) { |
|
return i * 10; |
|
}) |
|
.attr("r", 3) |
|
.on("end", function(d, i, j) { |
|
if (i === pts.length-1) { |
|
drawSegments(pieces); |
|
} |
|
}); |
|
|
|
} |
|
|
|
|
|
function init(){ |
|
|
|
showLine(defineLine, line); |
|
} |
|
|
|
init(); |
|
|
|
/* Icon experiment |
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none" x="0px" y="0px" width="100px" height="100px" viewBox="0 0 100 100"> |
|
<defs> |
|
<g id="Layer0_0_FILL"> |
|
<path fill="#009999" stroke="none" d=" |
|
M 90 48.05 |
|
L 90 32.05 80 32.05 80 16.05 22.05 16.05 22.05 29.05 14.05 29.05 14.05 43.05 22.05 43.05 22.05 71.05 35.05 71.05 35.05 88.05 67 88.05 67 71.05 80 71.05 80 48.05 90 48.05 Z"/> |
|
</g> |
|
</defs> |
|
|
|
<g transform="matrix( 1, 0, 0, 1, 0,0) "> |
|
<use xlink:href="#Layer0_0_FILL"/> |
|
</g> |
|
</svg> |
|
|
|
*/ |
|
|
|
</script> |
|
|
|
</body> |