|
/* */ |
|
/* d3controls.js */ |
|
/* */ |
|
|
|
if (typeof require === "function") { |
|
var d3 = require('./d3.v4.0.0-alpha.40.js') |
|
} |
|
|
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesControls = global.d3lanesControls || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
|
|
/* ------------- */ |
|
/* stepControls */ |
|
/* ------------- */ |
|
function stepControls(store) { |
|
|
|
var store = store |
|
var currentListeners = [] |
|
var nextListeners = currentListeners |
|
|
|
// ______________________________ ensureCanMutateNextListeners |
|
function ensureCanMutateNextListeners() { |
|
if (nextListeners === currentListeners) { |
|
nextListeners = currentListeners.slice() |
|
} |
|
} |
|
|
|
// ____________________ tc |
|
function tc() {} |
|
|
|
// ____________________ start |
|
tc.start = function start() { |
|
var periodFactor = store.getState().configReducer.periodFactor |
|
var beatTime = store.getState().configReducer.beatTime |
|
var periodTime = periodFactor * beatTime // items added |
|
|
|
var itemSpan = store.getState().configReducer.itemSpan |
|
|
|
var tickspan = store.getState().configReducer.tickspan |
|
var vLow = store.getState().lanesReducer.messagesCursorLow |
|
var vHigh = store.getState().lanesReducer.messagesCursorHigh |
|
|
|
var tf = setInterval(function() { |
|
|
|
var currentMode = store.getState().courtReducer.currentMode |
|
|
|
var listeners = currentListeners = nextListeners |
|
for (var i = 0; i < listeners.length; i++) { |
|
listeners[i]() |
|
} |
|
}, periodTime) |
|
|
|
return tc |
|
} |
|
// ______________________________ subscribe |
|
tc.subscribe = function subscribe (listener) { |
|
if (typeof listener !== 'function') { |
|
throw new Error('Expected listener to be a function.') |
|
} |
|
|
|
var isSubscribed = true |
|
|
|
ensureCanMutateNextListeners() |
|
nextListeners.push(listener) |
|
|
|
return tc |
|
} |
|
|
|
return tc |
|
} |
|
|
|
/* ------------- */ |
|
/* tickControls */ |
|
/* ------------- */ |
|
function tickControls(store) { |
|
|
|
var store = store |
|
var currentListeners = [] |
|
var nextListeners = currentListeners |
|
|
|
// ______________________________ ensureCanMutateNextListeners |
|
function ensureCanMutateNextListeners() { |
|
if (nextListeners === currentListeners) { |
|
nextListeners = currentListeners.slice() |
|
} |
|
} |
|
|
|
// ____________________ tc |
|
function tc() {} |
|
|
|
// ____________________ start |
|
tc.start = function start() { |
|
// Anatomy of a video game |
|
// Misuse of the requestAnimationFrme() |
|
var started = false |
|
var rfps = 60 |
|
var last = performance.now() |
|
var timestamp = 0 |
|
var ticker = function(timestamp) { |
|
window.requestAnimationFrame(ticker) |
|
|
|
if (timestamp != undefined) rfps = rfps * 0.9 + (1000/(timestamp-last)) * 0.1 |
|
if (timestamp != undefined) last = timestamp |
|
while( performance.now() - timestamp < 17 ) {} |
|
var fps = parseFloat(Math.round(rfps * 100) / 100).toFixed(0) |
|
store.dispatch(actions.setFps(fps)) |
|
|
|
var listeners = currentListeners = nextListeners |
|
for (var i = 0; i < listeners.length; i++) { |
|
listeners[i]() |
|
} |
|
} |
|
if (!started) { |
|
started = true |
|
ticker() |
|
} |
|
return tc |
|
} |
|
// ______________________________ subscribe |
|
tc.subscribe = function subscribe (listener) { |
|
if (typeof listener !== 'function') { |
|
throw new Error('Expected listener to be a function.') |
|
} |
|
|
|
var isSubscribed = true |
|
|
|
ensureCanMutateNextListeners() |
|
nextListeners.push(listener) |
|
|
|
return tc |
|
} |
|
|
|
return tc |
|
} |
|
|
|
|
|
/* ------------- */ |
|
/* mouseControls */ |
|
/* ------------- */ |
|
function mouseControls(store) { |
|
var store = store |
|
|
|
// ____________________ mouseEventsActions |
|
var mousedown = function mousedown(svg) { |
|
|
|
// console.log("event: ", d3.event) |
|
// var e = window.event; |
|
var e = d3.event |
|
pauseEvent(e); |
|
|
|
function pauseEvent(e){ |
|
if(e.stopPropagation) e.stopPropagation(); |
|
if(e.preventDefault) e.preventDefault(); |
|
e.cancelBubble=true; |
|
e.returnValue=false; |
|
return false; |
|
} |
|
|
|
var coords = d3.mouse(svg); |
|
store.dispatch(actions.updateMousePos(coords[0], coords[1])) |
|
store.dispatch(actions.startParticles()) |
|
store.dispatch(actions.createParticles({ |
|
particlesPerTick: store.getState().particlesReducer.particlesPerTick, |
|
x: coords[0], |
|
y: coords[1], |
|
xInit: 0, |
|
xEnd: store.getState().courtReducer.svgWidth, |
|
randNormal: store.getState().configReducer.randNormal, |
|
randNormal2: store.getState().configReducer.randNormal2, |
|
lanes: store.getState().lanesReducer.lanes, |
|
})) |
|
} |
|
var touchstart = function touchstart(svg) { |
|
var coords = d3.mouse(svg); |
|
store.dispatch(actions.updateTouchPos(coords[0], coords[1])) |
|
store.dispatch(actions.startParticles()) |
|
store.dispatch(actions.createParticles({ |
|
particlesPerTick: store.getState().particlesReducer.particlesPerTick, |
|
x: coords[0], |
|
y: coords[1], |
|
xInit: 0, |
|
xEnd: store.getState().courtReducer.svgWidth, |
|
randNormal: store.getState().configReducer.randNormal, |
|
randNormal2: store.getState().configReducer.randNormal2, |
|
lanes: store.getState().lanesReducer.lanes, |
|
})) |
|
} |
|
var mousemove = function mousemove(svg) { |
|
var coords = d3.mouse(svg); |
|
store.dispatch(actions.updateMousePos(coords[0], coords[1])) |
|
var generating = store.getState().particlesReducer.particlesGenerating |
|
if (generating === true) { |
|
|
|
store.dispatch(actions.createParticles({ |
|
particlesPerTick: store.getState().particlesReducer.particlesPerTick, |
|
x: coords[0], |
|
y: coords[1], |
|
xInit: 0, |
|
xEnd: store.getState().courtReducer.svgWidth, |
|
randNormal: store.getState().configReducer.randNormal, |
|
randNormal2: store.getState().configReducer.randNormal2, |
|
lanes: store.getState().lanesReducer.lanes, |
|
})) |
|
} |
|
} |
|
var touchmove = function touchmove(svg) { |
|
var coords = d3.mouse(svg); |
|
store.dispatch(actions.updateTouchPos(coords[0], coords[1])) |
|
} |
|
var mouseup = function mouseup(svg) { |
|
store.dispatch(actions.stopParticles()) |
|
var generating = store.getState().particlesReducer.particlesGenerating |
|
} |
|
var touchend = function touchend(svg) { |
|
store.dispatch(actions.stopParticles()) |
|
} |
|
var mouseleave = function mouseleave(svg) { |
|
store.dispatch(actions.stopParticles()) |
|
} |
|
|
|
// ____________________ controlfn |
|
function controlfn() { |
|
} |
|
// ____________________ startMouseEvents |
|
controlfn.startMouseEvents = function startMouseEvents(svg) { |
|
svg.on('mousedown', function() {mousedown(this)}) |
|
svg.on('touchstart', function() {touchstart(this)}) |
|
svg.on('mousemove', function() {mousemove(this)}) |
|
svg.on('touchmove', function() {touchmove(this)}) |
|
svg.on('mouseup', function() {mouseup(this)}) |
|
svg.on('mouseleave', function() {mouseleave(this)}) |
|
svg.on('mouseleave', function() {mouseleave(this)}) |
|
} |
|
|
|
return controlfn |
|
|
|
} |
|
|
|
/* ------------- */ |
|
/* kbdControls */ |
|
/* ------------- */ |
|
function kbdControls(store) { |
|
var store = store |
|
|
|
// ____________________ handleKeyDown |
|
// https://www.kirupa.com/html5/keyboard_events_in_javascript.htm |
|
// https://github.com/gaearon/redux-devtools-dock-monitor |
|
var handleKeyDown = function handleKeyDown(e) { |
|
e.stopPropagation(); |
|
e.preventDefault(); |
|
|
|
store.dispatch(actions.setKeybKey(e.keyCode)) |
|
var keys = store.getState().courtReducer.keys |
|
|
|
// keys[e.keyCode] = true; |
|
if (keys[70] && keys[17]) fKeyCtrl() // change currentView |
|
else if (keys[68] && keys[17]) dKeyCtrl() // change debugMode |
|
else if (e.keyCode == '37' && !keys[17]) leftArrow() // change currentMode autoMode/walkMode |
|
else if (e.keyCode == '37' && keys[17]) leftArrowCtrl() // change width |
|
else if (e.keyCode == '39' && !keys[17]) rightArrow() // change currentMode |
|
else if (e.keyCode == '39' && keys[17]) rightArrowCtrl() // change width |
|
else if (e.keyCode == '38' && !keys[17]) upArrow() // change currentMode nextWalk |
|
else if (e.keyCode == '38' && keys[17]) upArrowCtrl() // change height |
|
else if (e.keyCode == '40' && !keys[17]) downArrow() // change currentMode |
|
else if (e.keyCode == '40' && keys[17]) downArrowCtrl() // change height |
|
} |
|
// ____________________ downArrowCtrl |
|
var downArrowCtrl = function downArrowCtrl() { |
|
store.dispatch(actions.resizeHeight(+10)) |
|
} |
|
// ____________________ fKeyCtrl |
|
var fKeyCtrl = function fKeyCtrl() { // change view |
|
// // Ctrl 17 + Shift 16 + f 70 |
|
var views = Object.keys(store.getState().configReducer.views) |
|
var idx = views.indexOf(store.getState().courtReducer.currentView) |
|
var newIdx = idx + 1 % views.length |
|
var newview = store.getState().configReducer.views[views[newIdx]] |
|
store.dispatch(actions.setView(newview)) |
|
} |
|
// ____________________ dKeyCtrl |
|
var dKeyCtrl = function dKeyCtrl() { // change debug mode |
|
// // Ctrl 17 + Shift 16 + d 68 |
|
store.dispatch(actions.switchDebugMode()) |
|
} |
|
// ____________________ matchesKey |
|
function matchesKey(key, event) { |
|
if (!key) return false |
|
const charCode = event.keyCode || event.which; |
|
const char = String.fromCharCode(charCode); |
|
return key.name.toUpperCase() === char.toUpperCase() && |
|
key.alt === event.altKey && |
|
key.ctrl === event.ctrlKey && |
|
key.meta === event.metaKey && |
|
key.shift === event.shiftKey; |
|
} |
|
// ____________________ handleKeyPressed |
|
var handleKeyPressed = function handleKeyPressed(e) { |
|
} |
|
// ____________________ handleKeyReleased |
|
var handleKeyReleased = function handleKeyReleased(e) { |
|
store.dispatch(actions.releaseKeybKey(e.keyCode)) |
|
} |
|
// ____________________ keysEventsActions |
|
// arrows up/down => currentMode walkMode |
|
// arrow right => currentMode autoMode |
|
// arrow left => currentMode walkMode |
|
|
|
// ____________________ leftArrow |
|
var leftArrow = function leftArrow() { // set currentMode walkMode |
|
var currentMode = 'walkMode' |
|
store.dispatch(actions.setMode(currentMode)) |
|
} |
|
// ____________________ rightArrow |
|
var rightArrow = function rightArrow() { // set currentMode autoMode |
|
var currentMode = 'autoMode' |
|
store.dispatch(actions.setMode(currentMode)) |
|
} |
|
// ____________________ upArrow |
|
var upArrow = function upArrow() { |
|
var currentMode = store.getState().courtReducer.currentMode |
|
if (currentMode == 'autoMode') { |
|
var newMode = 'walkMode' |
|
store.dispatch(actions.setMode(newMode)) |
|
} else if (currentMode == 'walkMode') { |
|
|
|
var itemSpan = store.getState().configReducer.itemSpan |
|
store.dispatch(actions.walkUpRecords(itemSpan, currentMode)) |
|
|
|
} |
|
} |
|
// ____________________ downArrow |
|
var downArrow = function downArrow() { |
|
var currentMode = store.getState().courtReducer.currentMode |
|
if (currentMode == 'autoMode') { |
|
var newMode = 'walkMode' |
|
store.dispatch(actions.setMode(newMode)) |
|
} else if (currentMode == 'walkMode') { |
|
|
|
var itemSpan = store.getState().configReducer.itemSpan |
|
store.dispatch(actions.walkDownRecords(itemSpan, currentMode)) |
|
|
|
} |
|
} |
|
// ____________________ leftArrowCtrl |
|
var leftArrowCtrl = function leftArrowCtrl() { |
|
console.log("leftArrowCtrlFn") |
|
store.dispatch(actions.resizeWidth(-10)) |
|
} |
|
// ____________________ rightArrowCtrl |
|
var rightArrowCtrl = function rightArrowCtrl() { |
|
console.log("rightArrowCtrlFn") |
|
store.dispatch(actions.resizeWidth(10)) |
|
} |
|
// ____________________ upArrowCtrl |
|
var upArrowCtrl = function upArrowCtrl() { |
|
console.log("upArrowCtrlFn") |
|
store.dispatch(actions.resizeWidth(-10)) |
|
} |
|
// ____________________ controlfn |
|
function controlfn() { |
|
} |
|
// ____________________ startKeysEvents |
|
controlfn.startKeybKeyEvents = function startKeybKeyEvents() { |
|
store.dispatch(actions.startKeybKeyEvents()) |
|
document.addEventListener("keydown", handleKeyDown, false); |
|
document.addEventListener("keypress", handleKeyPressed, false); |
|
document.addEventListener("keyup", handleKeyReleased, false); |
|
} |
|
|
|
return controlfn |
|
} |
|
|
|
/* ================================= */ |
|
/* posControls */ |
|
/* ================================= */ |
|
function posControls (scope) { // selection |
|
var dName = 'pos' |
|
var aTypes = { |
|
start: dName + 'start', // tipstart |
|
move: dName + 'move', // tipmove |
|
end: dName + 'end' // tipend |
|
} |
|
var aTypesList = Object.keys(aTypes).map(function(k){return aTypes[k]}) |
|
var qaTypesList = aTypesList.map(function(k){return k + "." + dName}) |
|
// ______________________________ fnCallbacks |
|
var fnCallbacks = {} |
|
for (var i = 0; i < aTypesList.length; i++) { |
|
fnCallbacks[aTypesList[i]] = function(action) {} |
|
} |
|
// ______________________________ start pos |
|
fnCallbacks[aTypes.start] = function(action) { |
|
var node = d3.select(this) // selection |
|
var datum = node.datum() // datum |
|
} |
|
// ______________________________ move pos |
|
fnCallbacks[aTypes.move] = function(action) { |
|
var node = d3.select(this) // selection |
|
|
|
// node.on("mousemove.tip", null) |
|
|
|
createTextPad(action) |
|
displayTextPad(action) |
|
moveTextPad(action) |
|
|
|
function createTextPad(a) { |
|
var textPadDiv = d3.select("body") |
|
.selectAll("div.postip") |
|
.data(['divMousePos']) |
|
.enter() |
|
.append("div") |
|
.attr("class", "postip") |
|
.attr("viewBox", "0 0 10 10") |
|
.style("top", "-5px") |
|
.style("position", "absolute") |
|
.style("padding", "10px") |
|
.style("background", "rgba(255, 255, 255, .90)") |
|
.style("border", "1px solid lightgray") |
|
.style("pointer-events", "none") |
|
.style("z-index", "100") |
|
.style('border', '1px solid red') |
|
.style('color', 'grey') |
|
.classed('postip-hidden', true) |
|
.style("opacity", 0) |
|
} |
|
|
|
function textPadFn (a) { |
|
var s = String("|_____" + a.ox + " " + a.oy + "_____|") |
|
return s |
|
} |
|
|
|
// https://github.com/1wheel/swoopy-drag/blob/master/lib/d3-jetpack.js |
|
function displayTextPad(a) { |
|
d3.select('.postip') |
|
.classed('postip-hidden', false) |
|
.style('opacity', 1) |
|
.html('') |
|
.selectAll('div') |
|
.data([textPadFn]).enter() |
|
.append('div') |
|
.html(function(textPadFn) { |
|
return (textPadFn(a)) |
|
}) |
|
} |
|
|
|
function moveTextPad() { |
|
var postip = d3.select('div.postip') |
|
if (!postip.size()) return |
|
var e = d3.event, |
|
x = e.clientX, |
|
y = e.clientY, |
|
doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop, |
|
n = postip.node(), |
|
nBB = n.getBoundingClientRect() |
|
postip.style('top', (y+doctop-nBB.height-18)+"px"); |
|
postip.style('left', Math.min(Math.max(0, (x-nBB.width/2)), window.innerWidth - nBB.width)+"px"); |
|
} |
|
} |
|
// ______________________________ end pos |
|
fnCallbacks[aTypes.end] = function(action) { |
|
var node = d3.select(this) // selection |
|
var datum = node.datum() // datum |
|
|
|
d3.select('div.postip') |
|
.classed('postip-hidden', true) |
|
.style('opacity', 0) |
|
d3.selectAll('.postipped') |
|
.classed('postipped', false) |
|
} |
|
// ______________________________ dispatcher |
|
var d3_event = d3.dispatch.apply(null, aTypesList) |
|
for (var i=0; i < qaTypesList.length; i++) { |
|
d3_event.on(qaTypesList[i], fnCallbacks[aTypesList[i]]) |
|
} |
|
d3_event.of = function(thiz, argumentz) { |
|
return function(e1) { |
|
d3_event.call(e1.type, thiz, e1) |
|
} |
|
} |
|
// ______________________________ started |
|
function started(d, i, nodes) { |
|
var datum = d, // d datum |
|
node = this, // elem |
|
parent = node.parentNode, |
|
origin = d3.mouse(parent), |
|
ox = origin[0], |
|
oy = origin[1] |
|
|
|
var context = d3.select(d3_window(node)) // selection |
|
var a = { |
|
type: aTypes.start, |
|
ox: ox, |
|
oy: oy |
|
} |
|
d3_event.of(node)(a) |
|
} |
|
// ______________________________ moved |
|
function moved(d, i, nodes) { |
|
var datum = d, // d datum |
|
node = this, // elem |
|
parent = node.parentNode, |
|
origin = d3.mouse(parent), |
|
ox = origin[0], |
|
oy = origin[1] |
|
|
|
var context = d3.select(d3_window(node)) // selection |
|
var a = { |
|
type: aTypes.move, |
|
ox: ox, |
|
oy: oy |
|
} |
|
d3_event.of(node)(a) |
|
} |
|
// ______________________________ ended |
|
function ended(d, i, nodes) { |
|
|
|
var datum = d, // d datum |
|
node = this, // elem |
|
parent = node.parentNode, |
|
origin = d3.mouse(parent) |
|
|
|
var a = { |
|
type: aTypes.end |
|
} |
|
d3_event.of(node)(a) |
|
} |
|
// ______________________________ lib |
|
function prevent() { |
|
event.preventDefault(); |
|
} |
|
function d3_window(node) { |
|
return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); |
|
} |
|
// ______________________________ |
|
function d3Control(scope) { |
|
scope.on("mouseenter.pos", started) |
|
scope.on("mousemove.pos", moved) |
|
scope.on("mouseout.pos", ended) |
|
|
|
} |
|
d3Control.on = function() { |
|
var value = d3_event.on.apply(d3_event, arguments); |
|
return value === d3_event ? d3Control : value; |
|
} |
|
return d3Control |
|
} |
|
|
|
|
|
|
|
/* ================================= */ |
|
/* tipControls */ |
|
/* ================================= */ |
|
function tipControls (scope) { // selection |
|
var dName = 'tip' |
|
var aTypes = { |
|
start: dName + 'start', // tipstart |
|
move: dName + 'move', // tipmove |
|
end: dName + 'end' // tipend |
|
} |
|
var aTypesList = Object.keys(aTypes).map(function(k){return aTypes[k]}) |
|
var qaTypesList = aTypesList.map(function(k){return k + "." + dName}) |
|
// ______________________________ fnCallbacks |
|
var fnCallbacks = {} |
|
for (var i = 0; i < aTypesList.length; i++) { |
|
fnCallbacks[aTypesList[i]] = function(action) {} |
|
} |
|
// ______________________________ start tip |
|
fnCallbacks[aTypes.start] = function(action) { |
|
var node = d3.select(this) // selection |
|
var datum = node.datum() // datum |
|
|
|
createTooltip(action) |
|
displayTooltip(action) |
|
moveMoveTooltip(action) |
|
|
|
function createTooltip() { |
|
var tipDiv = d3.select("body") |
|
.selectAll("div.tooltip") |
|
.data(['divTooltip']) |
|
.enter() |
|
.append("div") |
|
.attr("class", "tooltip") |
|
.attr("viewBox", "0 0 10 10") |
|
.style("top", "-5px") |
|
.style("position", "absolute") |
|
.style("padding", "10px") |
|
.style("background", "rgba(255, 255, 255, .90)") |
|
.style("border", "1px solid lightgray") |
|
.style("pointer-events", "none") |
|
.style("z-index", "100") |
|
.style('border', '1px solid red') |
|
.style('color', 'grey') |
|
.classed('tooltip-hidden', true) |
|
.style("opacity", 0) |
|
} |
|
|
|
// https://github.com/1wheel/swoopy-drag/blob/master/lib/d3-jetpack.js |
|
function displayTooltip() { |
|
var d = action.datum |
|
var fieldFns = d3.keys(d) |
|
.filter(function(str){ |
|
var r = (typeof d[str] != 'object') && (d[str] != 'array') |
|
return r |
|
}) |
|
.map(function(str){ |
|
return function (d) { return str + ': ' + '<b>' + d[str] + '</b>' }}) |
|
var tipfn = function(action) { |
|
var s = String(action.datum.tip || action.datum.id || 'tip') |
|
return wordwrap(s, 20) |
|
} |
|
|
|
d3.select('.tooltip') |
|
.classed('tooltip-hidden', false) |
|
.style('opacity', 1) |
|
.html('') |
|
.selectAll('div') |
|
.data(fieldFns).enter() |
|
.append('div') |
|
.html(function(fieldFns) { |
|
return (fieldFns(d)) |
|
}) |
|
} |
|
function moveMoveTooltip() { |
|
var tooltip = d3.select('.tooltip') |
|
if (!tooltip.size()) return |
|
var e = d3.event, |
|
x = e.clientX, |
|
y = e.clientY, |
|
doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop, |
|
n = tooltip.node(), |
|
nBB = n.getBoundingClientRect() |
|
tooltip.style('top', (y+doctop-nBB.height-18)+"px"); |
|
tooltip.style('left', Math.min(Math.max(0, (x-nBB.width/2)), window.innerWidth - nBB.width)+"px"); |
|
} |
|
|
|
function wordwrap (line, maxCharactersPerLine) { |
|
var w = line.split(' '), |
|
lines = [], |
|
words = [], |
|
maxChars = maxCharactersPerLine || 40, |
|
l = 0; |
|
w.forEach(function(d) { |
|
if (l+d.length > maxChars) { |
|
lines.push(words.join(' ')); |
|
words.length = 0; |
|
l = 0; |
|
} |
|
l += d.length; |
|
words.push(d); |
|
}); |
|
if (words.length) { |
|
lines.push(words.join(' ')); |
|
} |
|
return lines; |
|
} |
|
} |
|
// ______________________________ move tip |
|
fnCallbacks[aTypes.move] = function(action) { |
|
var node = d3.select(this) // selection |
|
var datum = node.datum() // datum |
|
} |
|
// ______________________________ end tip |
|
fnCallbacks[aTypes.end] = function(action) { |
|
var node = d3.select(this) // selection |
|
var datum = node.datum() // datum |
|
|
|
d3.select('.tooltip') |
|
.classed('tooltip-hidden', true) |
|
.style('opacity', 0) |
|
d3.selectAll('.tooltipped') |
|
.classed('tooltipped', false) |
|
} |
|
// ______________________________ dispatcher |
|
var d3_event = d3.dispatch.apply(null, aTypesList) |
|
for (var i=0; i < qaTypesList.length; i++) { |
|
d3_event.on(qaTypesList[i], fnCallbacks[aTypesList[i]]) |
|
} |
|
d3_event.of = function(thiz, argumentz) { |
|
return function(e1) { |
|
d3_event.call(e1.type, thiz, e1) |
|
} |
|
} |
|
// ______________________________ started tip |
|
function started(d, i, nodes) { |
|
var datum = d, // d datum |
|
node = this, // elem |
|
parent = node.parentNode, |
|
origin = d3.mouse(parent), |
|
ox = d.x - origin[0] || 0, |
|
oy = d.y - origin[1] || 0, |
|
tiped = false |
|
|
|
var context = d3.select(d3_window(node)) // selection |
|
var a = { |
|
type: aTypes.start, |
|
datum: d |
|
} |
|
d3_event.of(node)(a) |
|
} |
|
// ______________________________ ended tip |
|
function ended(d, i, nodes) { |
|
|
|
var datum = d, // d datum |
|
node = this, // elem |
|
parent = node.parentNode, |
|
origin = d3.mouse(parent), |
|
ox = d.x - origin[0] || 0, |
|
oy = d.y - origin[1] || 0, |
|
tiped = false |
|
|
|
var a = { |
|
type: aTypes.end, |
|
} |
|
d3_event.of(node)(a) |
|
} |
|
// ______________________________ lib |
|
function prevent() { |
|
event.preventDefault(); |
|
} |
|
function d3_window(node) { |
|
return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); |
|
} |
|
// ______________________________ |
|
function d3Control(scope) { |
|
scope.each(function() { |
|
scope.on("mousemove.tip", started) |
|
scope.on("mouseout.tip", ended) |
|
}) |
|
} |
|
d3Control.on = function() { |
|
var value = d3_event.on.apply(d3_event, arguments); |
|
return value === d3_event ? d3Control : value; |
|
} |
|
return d3Control |
|
} |
|
|
|
|
|
/* ================================= */ |
|
/* dragControls */ |
|
/* ================================= */ |
|
// https://github.com/d3/d3-drag/blob/master/src/drag.js |
|
function dragControls (scope) { |
|
|
|
function prevent() { |
|
event.preventDefault(); |
|
} |
|
|
|
function d3_window(node) { |
|
return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); |
|
} |
|
|
|
var scope = scope // selection |
|
|
|
var dName = 'drag' |
|
var aTypes = { |
|
start: dName + 'start', // dragstart |
|
move: dName + 'move', // dragmove |
|
end: dName + 'end' // dragend |
|
} |
|
var aTypesList = Object.keys(aTypes).map(function(k){return aTypes[k]}) |
|
var qaTypesList = aTypesList.map(function(k){return k + "." + dName}) |
|
|
|
var fnCallbacks = {} |
|
for (var i = 0; i < aTypesList.length; i++) { |
|
fnCallbacks[aTypesList[i]] = function(action) {} |
|
} |
|
|
|
// ______________________________ dragend |
|
fnCallbacks[aTypes.end] = function(action) { |
|
var node = d3.select(this) |
|
node.datum().dx1 = action.dx1 |
|
node.datum().dy1 = action.dy1 |
|
} |
|
|
|
// ______________________________ dragmove |
|
fnCallbacks[aTypes.move] = function(action) { |
|
var node = d3.select(this) |
|
node |
|
.attr("transform", "translate(" + action.dx1 + "," + action.dy1 + ")") |
|
} |
|
// ______________________________ dispatcher |
|
var d3_event = d3.dispatch.apply(null, aTypesList) |
|
for (var i=0; i < qaTypesList.length; i++) { |
|
d3_event.on(qaTypesList[i], fnCallbacks[aTypesList[i]]) |
|
} |
|
d3_event.of = function(thiz, argumentz) { |
|
return function(e1) { |
|
d3_event.call(e1.type, thiz, e1) |
|
} |
|
} |
|
// ______________________________ listener |
|
function started(d, i, nodes) { |
|
var node = this, |
|
parent = node.parentNode, |
|
origin = d3.mouse(parent), |
|
ox = d.x - origin[0] || 0, |
|
oy = d.y - origin[1] || 0, |
|
dragged = false |
|
|
|
var context = d3.select(d3_window(node)) |
|
.on("dragstart.drag", prevent) |
|
.on("selectstart.drag", prevent) |
|
.on("mouseup", ended) |
|
.on("mousemove", moved) |
|
|
|
|
|
var emit = d3_event.of(node, arguments) |
|
|
|
// ______________________________ when moved |
|
function moved() { |
|
var p = d3.mouse(parent) |
|
|
|
var a = { |
|
type: aTypes.move, |
|
x0: origin[0] + ox, // first x |
|
y0: origin[1] + oy, |
|
x1: p[0] + ox, // new x position |
|
y1: p[1] + oy, |
|
dx: p[0] - origin[0], // delta x |
|
dy: p[1] - origin[1], |
|
dx1: (d.dx1 || 0) + p[0] - origin[0], // aggregated delta x |
|
dy1: (d.dy1 || 0) + p[1] - origin[1] |
|
} |
|
emit(a) |
|
} |
|
|
|
// ______________________________ when ended |
|
function ended() { |
|
context.on("mousemove", null) |
|
.on("mouseup", null); |
|
|
|
var p = d3.mouse(parent) |
|
var a = { |
|
type: aTypes.end, |
|
x0: origin[0] + ox, // first x |
|
y0: origin[1] + oy, |
|
x1: p[0] + ox, // new x position |
|
y1: p[1] + oy, |
|
dx: p[0] - origin[0], // delta x |
|
dy: p[1] - origin[1], |
|
dx1: (d.dx1 || 0) + p[0] - origin[0], // aggregated delta x |
|
dy1: (d.dy1 || 0) + p[1] - origin[1] |
|
} |
|
emit(a) |
|
} |
|
function afterended() { |
|
context.on("click.drag", null); |
|
} |
|
} |
|
|
|
function drag(selection) { |
|
selection.on("mousedown.drag", started) |
|
} |
|
drag.on = function() { |
|
var value = d3_event.on.apply(d3_event, arguments); |
|
return value === d3_event ? drag : value; |
|
}; |
|
|
|
return drag; |
|
} |
|
|
|
exports.stepControls = stepControls |
|
exports.tickControls = tickControls |
|
exports.mouseControls = mouseControls |
|
exports.kbdControls = kbdControls |
|
exports.dragControls = dragControls |
|
exports.posControls = posControls |
|
exports.tipControls = tipControls |
|
})); |
|
/* */ |
|
/* d3lanes-actions.js */ |
|
/* */ |
|
|
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesActions = global.d3lanesActions || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
|
|
// ____________________ merge_objects |
|
function merge_objects(ctt1,ctt2){ |
|
var i, obj = {} |
|
for (i = 0; i < arguments.length; i++) { |
|
Object.assign(obj, arguments[i]) |
|
} |
|
return obj; |
|
} |
|
|
|
// ____________________ keyMirror |
|
// https://github.com/STRML/keyMirror |
|
var keyMirror = function(obj, prefix) { |
|
var ret = {}; |
|
var key; |
|
if (!(obj instanceof Object && !Array.isArray(obj))) { |
|
throw new Error('keyMirror(...): Argument must be an object.'); |
|
} |
|
for (key in obj) { |
|
if (obj.hasOwnProperty(key)) { |
|
ret[key] = prefix + key; |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
// ____________________ action TYPES |
|
var cttsCourt = { |
|
ADD_KEYS_EVENT: '', |
|
FETCH_RECORDS: '', |
|
RELEASE_KEYBKEY: '', |
|
RESET_KEYS_EVENTS: '', |
|
RESIZE_SCREEN: '', |
|
RESIZE_HEIGHT: '', |
|
RESIZE_WIDTH: '', |
|
SET_KEYBKEY: '', |
|
SET_MODE: '', |
|
SET_NOTICE: '', |
|
SET_VIEW: '', |
|
START_KEYS_EVENTS: '', |
|
START_KEYBKEY_EVENTS: '', |
|
UPDATE_MOUSE_POS: '', |
|
} |
|
|
|
var cttsLanes = { |
|
DELETE_LANE: '', |
|
SET_LANE: '', |
|
SET_LANES: '', |
|
SET_MESSAGES: '', |
|
SET_RECORDS: '', |
|
WALK_DOWN_RECORDS: '', |
|
WALK_UP_RECORDS: '', |
|
SET_RECORDS_COLLECTION: '', |
|
SET_RECORDS_FETCHED: '', |
|
UPDATE_MESSAGES: '', |
|
} |
|
|
|
var cttsDebug = { |
|
SET_DEBUGMODE: '', |
|
SET_FPS: '', |
|
SWITCH_DEBUGMODE: '', |
|
} |
|
|
|
var cttsParticles = { |
|
CREATE_PARTICLES: '', |
|
START_PARTICLES: '', |
|
START_TICKER: '', |
|
STOP_PARTICLES: '', |
|
STOP_TICKER: '', |
|
TICK_PARTICLES: '', |
|
} |
|
|
|
// ____________________ actions COURT |
|
var ActionCreatorsCourt = { |
|
resizeHeight(height) { |
|
return { |
|
type: ActionTypes.RESIZE_HEIGHT, |
|
delta: height |
|
} |
|
}, |
|
resizeScreen(width, height) { |
|
return { |
|
type: ActionTypes.RESIZE_SCREEN, |
|
width: width, |
|
height: height |
|
} |
|
}, |
|
resizeWidth(width) { |
|
return { |
|
type: ActionTypes.RESIZE_WIDTH, |
|
delta: width |
|
} |
|
}, |
|
setKeybKey(keyCode) { |
|
return { |
|
type: ActionTypes.SET_KEYBKEY, |
|
keyCode: keyCode, |
|
} |
|
}, |
|
releaseKeybKey(keyCode) { |
|
return { |
|
type: ActionTypes.RELEASE_KEYBKEY, |
|
keyCode: keyCode, |
|
} |
|
}, |
|
setMode(currentMode) { |
|
return { |
|
type: ActionTypes.SET_MODE, |
|
currentMode: currentMode, |
|
} |
|
}, |
|
setView(currentView) { |
|
return { |
|
type: ActionTypes.SET_VIEW, |
|
currentView: currentView, |
|
} |
|
}, |
|
setNotice(notice) { |
|
return { |
|
type: ActionTypes.SET_NOTICE, |
|
notice: notice, |
|
} |
|
}, |
|
startKeybKeyEvents() { |
|
return { |
|
type: ActionTypes.START_KEYBKEY_EVENTS |
|
} |
|
}, |
|
updateMousePos(x, y) { |
|
return { |
|
type: ActionTypes.UPDATE_MOUSE_POS, |
|
x: x, |
|
y: y |
|
} |
|
}, |
|
} |
|
|
|
// ____________________ actions LANES |
|
var ActionCreatorsLanes = { |
|
setRecordsFetched(areRecordsFetched) { |
|
return { |
|
type: ActionTypes.SET_RECORDS_FETCHED, |
|
areRecordsFetched: areRecordsFetched, |
|
} |
|
}, |
|
setRecordsCollection(recordsCollection) { |
|
return { |
|
type: ActionTypes.SET_RECORDS_COLLECTION, |
|
recordsCollection: recordsCollection, |
|
} |
|
}, |
|
setRecords(argObj) { // SET_RECORDS |
|
return { |
|
type: ActionTypes.SET_RECORDS, |
|
itemSpan: argObj.itemSpan, |
|
mode: argObj.currentMode, |
|
} |
|
}, |
|
walkDownRecords(itemSpan, mode) { // WALK_DOWN_RECORDS |
|
return { |
|
type: ActionTypes.WALK_DOWN_RECORDS, |
|
itemSpan: itemSpan, |
|
mode: mode, |
|
} |
|
}, |
|
walkUpRecords(itemSpan, mode) { // WALK_UP_RECORDS |
|
return { |
|
type: ActionTypes.WALK_UP_RECORDS, |
|
itemSpan: itemSpan, |
|
mode: mode, |
|
} |
|
}, |
|
increaseCursorLow() { // INCREASE_CURSOR_LOW |
|
return { |
|
type: ActionTypes.INCREASE_CURSOR_LOW, |
|
} |
|
}, |
|
decreaseCursorLow() { |
|
return { |
|
type: ActionTypes.DECREASE_CURSOR_LOW, |
|
} |
|
}, |
|
increaseCursorHigh() { |
|
return { |
|
type: ActionTypes.INCREASE_CURSOR_HIGH, |
|
} |
|
}, |
|
decreaseCursorHigh() { |
|
return { |
|
type: ActionTypes.DECREASE_CURSOR_HIGH, |
|
} |
|
}, |
|
deleteLane(lane) { |
|
return { |
|
type: ActionTypes.DELETE_LANE, |
|
lane: lane, |
|
} |
|
}, |
|
setLane(lane) { |
|
return { |
|
type: ActionTypes.SET_LANE, |
|
lane: lane, |
|
} |
|
}, |
|
setLanes(lanes) { |
|
return { |
|
type: ActionTypes.SET_LANES, |
|
lanes: lanes, |
|
} |
|
}, |
|
setMessages(messages) { |
|
return { |
|
type: ActionTypes.SET_MESSAGES, |
|
messages: messages, |
|
} |
|
}, |
|
updateMessages(messages) { |
|
return { |
|
type: ActionTypes.UPDATE_MESSAGES, |
|
cursorLow: cursorLow, |
|
cursorHigh: cursorHigh, |
|
} |
|
}, |
|
} |
|
// ____________________ actions DEBUG |
|
var ActionCreatorsDebug = { |
|
setDebugMode(debugMode) { |
|
return { |
|
type: ActionTypes.SET_DEBUG_MODE, |
|
debugMode: debugMode, |
|
} |
|
}, |
|
setFps(fps) { |
|
return { |
|
type: ActionTypes.SET_FPS, |
|
fps: fps, |
|
} |
|
}, |
|
switchDebugMode() { |
|
return { |
|
type: ActionTypes.SWITCH_DEBUGMODE |
|
}; |
|
}, |
|
} |
|
// ____________________ actions PARTICLES |
|
var ActionCreatorsParticles = { |
|
createParticles(obj) { |
|
return { |
|
type: ActionTypes.CREATE_PARTICLES, // createParticles |
|
N: obj.particlesPerTick, |
|
x: obj.x, |
|
y: obj.y, |
|
randNormal: obj.randNormal, |
|
randNormal2: obj.randNormal2, |
|
xInit: obj.xInit, |
|
xEnd: obj.xEnd, |
|
lanes: obj.lanes, |
|
} |
|
}, |
|
startParticles() { |
|
return { |
|
type: ActionTypes.START_PARTICLES |
|
} |
|
}, |
|
startTicker() { |
|
return { |
|
type: ActionTypes.START_TICKER |
|
}; |
|
}, |
|
stopTicker() { |
|
return { |
|
type: ActionTypes.STOP_TICKER |
|
}; |
|
}, |
|
tickParticles(arg) { |
|
// console.log("arg: ", JSON.stringify(arg.lanes, null, 2)) |
|
return { |
|
type: ActionTypes.TICK_PARTICLES, // tickParticles |
|
width: arg.width, |
|
height: arg.height, |
|
gravity: arg.gravity, |
|
lanes: arg.lanes, |
|
} |
|
}, |
|
stopParticles() { |
|
return { |
|
type: ActionTypes.STOP_PARTICLES |
|
} |
|
}, |
|
} |
|
|
|
var ctts = merge_objects(cttsLanes, cttsCourt, cttsDebug, cttsParticles) |
|
var ActionTypes = keyMirror(ctts, '') |
|
var ActionCreators = merge_objects(ActionCreatorsCourt, ActionCreatorsParticles, ActionCreatorsDebug, ActionCreatorsLanes) |
|
|
|
exports.ActionTypes = ActionTypes; |
|
exports.ActionCreators = ActionCreators; |
|
}));/* */ |
|
/* d3lanes-component-court.js */ |
|
/* */ |
|
|
|
if (typeof require === "function") { |
|
var d3 = require('./d3.v4.0.0-alpha.40.js') |
|
} |
|
|
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesComponentCourt = global.d3lanesComponentCourt || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
function render(newState) { |
|
var state |
|
doRender(newState) |
|
|
|
function doRender(newState) { |
|
if (state === newState) return |
|
state = newState |
|
|
|
var svgContainer = d3.select(state.configReducer.containerElem) |
|
.selectAll('svg') |
|
.data(['svg']) |
|
|
|
var svgContainerNew = svgContainer.enter() |
|
.append("svg") |
|
.attr("id", state.configReducer.containerId) |
|
.style('width', state.courtReducer.svgWidth) |
|
.style('height', state.courtReducer.svgHeight) |
|
.attr('class', 'bar-chart') // |
|
.style('border', '1px solid red') |
|
.style('color', 'blue') |
|
.attr('viewbox',"0 0 3 2") |
|
|
|
|
|
var svg = d3.select('svg') |
|
|
|
var itemsGroup = d3.select('svg') |
|
.selectAll('g.notices') // items |
|
.data(['items']) |
|
|
|
itemsGroup.enter() |
|
.append("g") |
|
.classed("notices", true) // items |
|
|
|
// _________________________________ render Notice Update |
|
var errorNotice = (state.courtReducer.notice) ? state.courtReducer.notice : "" |
|
var noticeToShow = " " + |
|
"click particles arrow mode" + |
|
" - " + state.configReducer.modeLabels[state.configReducer.modes[state.courtReducer.currentMode]] + |
|
" - " + parseInt(svg.style("width")) + " x " + parseInt(svg.style("height")) + |
|
" - N: " + state.particlesReducer.particleIndex + |
|
" - fps: " + state.debugReducer.fps |
|
|
|
|
|
var winWidthPixels = parseInt(svg.style("width")) |
|
var winHeightPixels = parseInt(svg.style("height")) |
|
|
|
var fontSizeHw = 3 + "hw" |
|
var fontSize = winWidthPixels * 3/100 |
|
var fontname = 'sans-serif' |
|
|
|
var c=document.createElement('canvas'); |
|
var ctx=c.getContext('2d'); |
|
ctx.font = fontSize + 'px ' + fontname; |
|
|
|
var noticeLength = ctx.measureText(noticeToShow).width |
|
|
|
var vlocation = winHeightPixels - fontSize |
|
var hlocation = winWidthPixels - noticeLength |
|
|
|
// items elems |
|
var itemsElems = svgContainer |
|
.select("g.notices") |
|
.selectAll("g.notice") |
|
.data([noticeToShow]); |
|
|
|
// items elems enter |
|
var itemsElemsNew = itemsElems.enter() |
|
.append("g") |
|
.classed("notice", true) |
|
|
|
itemsElemsNew.each(function(d, i) { |
|
var itemElemNew = d3.select(this) |
|
.append("text") |
|
.classed("info", true) |
|
.style("font-family", fontname) |
|
.attr("x", function(d) { |
|
return hlocation; }) |
|
.attr("y", function(d) { |
|
return vlocation |
|
}) |
|
.style("font-size", function(d, i) { |
|
return fontSize |
|
}) |
|
.text(function(d) { return d }) |
|
.style("fill-opacity", 1) |
|
}) |
|
|
|
// items elems update |
|
itemsElems |
|
.select('text') |
|
.text(function(d) { return d }) |
|
.attr("x", function(d) { |
|
return hlocation; }) |
|
.attr("y", function(d) { |
|
return vlocation; }) |
|
|
|
// items elems exit |
|
itemsElems.exit() |
|
.select('text') |
|
.remove() |
|
|
|
} // render |
|
} |
|
|
|
exports.render = render; |
|
}))/* */ |
|
/* d3lanes-component-lanes.js */ |
|
/* */ |
|
|
|
if (typeof require === "function") { |
|
var d3 = require('./d3.v4.0.0-alpha.40.js') |
|
} |
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesComponentLanes = global.d3lanesComponentLanes || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
// _____________ coordsUtils |
|
function coordsUtils () { |
|
function index_hcoord_pct(arr, index) { |
|
return (index+1) * (100/(arr.length+1)); |
|
} |
|
function index_hcoord_pct_with_symbol(arr, index) { |
|
return index_hcoord_pct(arr, index) + "%"; |
|
} |
|
|
|
function horizontal_center(x1, x2) { |
|
if (x1 > x2) return (x2 - x1)/2 + x1 |
|
else return (x1 - x2)/2 + x2 |
|
} |
|
function horizontal_percent_to_coord(svg, percent) { |
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=874811 // _e_ |
|
if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { |
|
var xScrollWidth = svg[0].parentNode.scrollWidth; |
|
} else { |
|
var xScrollWidth = svg.property("scrollWidth"); |
|
} |
|
return xScrollWidth * Number.parseFloat(percent)/100; |
|
} |
|
function horizontal_coord_to_percent(svg, length) { |
|
var xScrollWidth = svg.property("scrollWidth"); |
|
return Number.parseFloat(length) / xScrollWidth; |
|
} |
|
|
|
var publicAPI = { |
|
hcoord_tagged_pct: function hcoord_tagged_pct(arr, val) { |
|
return index_hcoord_pct_with_symbol(arr, arr.indexOf(val)); |
|
}, |
|
hcoord_pct: function hcoord_pct(arr, val) { |
|
return index_hcoord_pct(arr, arr.indexOf(val)); |
|
}, |
|
hcenter_tagged_pct: function hcenter_tagged_pct(x1, x2) { |
|
return horizontal_center(x1, x2) + "%"; |
|
} |
|
} |
|
return publicAPI |
|
} |
|
|
|
// _____________ arrayUtils |
|
function arrayUtils () { |
|
function flattenArrByObjProp(a, p) { |
|
return a.reduce(function(prevArr, currVal, i, a) { |
|
if (prevArr.indexOf(currVal[p]) < 0) |
|
var r = prevArr.push(currVal[p]) |
|
return prevArr |
|
}, []); |
|
} |
|
|
|
function union_arrays (x, y) { |
|
var obj = {}; |
|
for (var i = x.length-1; i >= 0; -- i) |
|
obj[x[i]] = x[i]; |
|
for (var i = y.length-1; i >= 0; -- i) |
|
obj[y[i]] = y[i]; |
|
var res = [] |
|
for (var k in obj) { |
|
if (obj.hasOwnProperty(k)) |
|
res.push(obj[k]); |
|
} |
|
return res; |
|
} |
|
|
|
function array_names_from_props (arr, props) { |
|
var r = props.reduce( function(prevArr, currVal, i, a) { |
|
var q1 = flattenArrByObjProp(arr, currVal) |
|
var q = union_arrays(prevArr, q1) |
|
return q |
|
}, []) |
|
return r |
|
} |
|
|
|
var publicAPI = { |
|
array_names_from_props: function array_names_from_props (arr, props) { |
|
|
|
var r = props.reduce( function(prevArr, currVal, i, a) { |
|
var q1 = flattenArrByObjProp(arr, currVal) |
|
var q = union_arrays(prevArr, q1) |
|
return q |
|
}, []) |
|
return r |
|
}, |
|
} |
|
return publicAPI |
|
} |
|
|
|
// _____________ context |
|
var stateLanes = { |
|
lanesReducer: {} |
|
} |
|
var intransition = false |
|
|
|
// _____________ render |
|
function render(newState) { |
|
if (intransition == true) { |
|
return |
|
} |
|
if (JSON.stringify(stateLanes.lanesReducer.records) === JSON.stringify(newState.lanesReducer.records)) { |
|
return |
|
} |
|
|
|
|
|
// DATA |
|
// store previous - will not change during render |
|
var _messages0 = stateLanes.lanesReducer.records || [] |
|
var state = stateLanes = newState |
|
var _messages1 = state.lanesReducer.records |
|
var _fadeTime = state.configReducer.fadeFactor * state.configReducer.beatTime |
|
var _itemProps = state.configReducer.itemProps |
|
|
|
// SVG |
|
var svgContainer = d3.select('body') |
|
.selectAll('svg') |
|
.data(['svgContainer']) |
|
|
|
svgContainer |
|
.enter() |
|
.append("svg") |
|
.attr("id", state.configReducer.container) |
|
|
|
svgContainer |
|
.style('width', state.courtReducer.svgWidth) |
|
.style('height', state.courtReducer.svgHeight) |
|
|
|
var messagesGroup = d3.select('svg') |
|
.selectAll('g.messages') // items |
|
.data(['messages']) |
|
|
|
messagesGroup.enter() |
|
.append("g") |
|
.classed("messages", true) // items |
|
|
|
var actorsGroup = d3.select('svg') |
|
.selectAll('g.lanes') // items |
|
.data(['lanes']) |
|
|
|
actorsGroup.enter() |
|
.append("g") |
|
.classed("lanes", true) // items |
|
|
|
var marker = svgContainer.append("marker") |
|
.attr("id", "message-marker") |
|
.attr("viewBox", "0 0 10 10") |
|
.attr("refX", "10") |
|
.attr("refY", "5") |
|
.attr("markerWidth", "5") |
|
.attr("markerHeight", "4") |
|
.attr("orient", "auto") |
|
.append("path") |
|
.attr("class", "message-arrow") |
|
.attr("d", "M 0 0 L 10 5 L 0 10 z") |
|
|
|
// DATA |
|
var _laneItems0 = arrayUtils() |
|
.array_names_from_props(_messages0, _itemProps) |
|
|
|
var _laneObjs0 = _laneItems0.map(function(d, i) { |
|
return ({id: d, |
|
name: d, |
|
x0: parseFloat(coordsUtils().hcoord_pct(_laneItems0, d) |
|
* parseInt(svgContainer.style("width")) / 100).toFixed(0)})}) |
|
|
|
var _lanesObj0 = _laneItems0.reduce(function(total,d,currentIndex,arr) { |
|
var o = {} |
|
o[d] = {name: d, |
|
x0: parseFloat(coordsUtils().hcoord_pct(_laneItems0, d) |
|
* parseInt(svgContainer.style("width")) / 100).toFixed(0)} |
|
return (Object.assign({}, total, o))}, {}) |
|
|
|
|
|
var _laneItems1 = arrayUtils() |
|
.array_names_from_props(_messages1, _itemProps) |
|
|
|
var _laneObjs1 = _laneItems1.map(function(d, i) { |
|
|
|
var x0 = 0 |
|
if ( _lanesObj0.hasOwnProperty( d) ) { |
|
x0 = _lanesObj0[d].x0 |
|
} |
|
return ({id: d, |
|
name: d, |
|
x0: x0})}) |
|
|
|
// lane elems trasition |
|
var laneElemsTransition = d3.transition() |
|
.duration(_fadeTime) |
|
.ease(d3.easeLinear) |
|
|
|
// laneElems DATA |
|
var laneElems = svgContainer |
|
.select("g.lanes") |
|
.selectAll("g.actor") |
|
.data(_laneObjs1, function(d) { return d.id }) |
|
|
|
// laneElems EXIT |
|
laneElems.exit() |
|
.transition(laneElemsTransition) |
|
.style("opacity", function(d) { |
|
store.dispatch(actions.deleteLane(d)) |
|
return 0 |
|
}) |
|
.remove(function(){console.log("++++++++++++++++++")}) |
|
|
|
// laneElems UPDATE texts |
|
var actorTexts = laneElems.select("text") |
|
.attr("text-anchor", "middle") |
|
.attr("alignment-baseline", "middle") |
|
.style("font-size", function(d, i) { |
|
return parseInt(svgContainer.style("width")) * 2/100 |
|
}) |
|
.text(function(d) { return d.name }) |
|
.attr("dy", "20") |
|
.transition(laneElemsTransition) |
|
.attr("x", function(d, i) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.name) |
|
return r |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// laneElems UPDATE lines |
|
var actorLines = laneElems.select("line") |
|
.attr("x0", function(d, i) { |
|
var r = parseFloat(coordsUtils().hcoord_pct(_laneItems0, d.name) |
|
* parseInt(svgContainer.style("width")) / 100).toFixed(0) |
|
return r |
|
}) |
|
.attr("y1", function() { |
|
var text_bbox = this.parentNode.querySelector("text").getBBox(); |
|
return text_bbox.y + text_bbox.height; |
|
}) |
|
.attr("y2", "100%") |
|
.transition(laneElemsTransition) |
|
.attrTween("x1", function(d, i, a) { |
|
return function (t) { |
|
var r = parseFloat(coordsUtils().hcoord_pct(_laneItems1, d.name) |
|
* parseInt(svgContainer.style("width")) / 100).toFixed(0) |
|
var x = parseFloat(parseInt(d.x0) + t * (r - parseInt(d.x0))).toFixed(0) |
|
// dispatch lanes abscissa |
|
var l = {name: d.name, id: d.id, x: x } |
|
store.dispatch(actions.setLane(l)) |
|
return x |
|
} |
|
}) |
|
|
|
.attr("x2", function(d, i) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.name) |
|
return r |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// laneElems ENTER |
|
var newActorElements = laneElems |
|
.enter() |
|
.append("g") |
|
.classed("actor", true) |
|
|
|
// laneElems ENTER text |
|
newActorElements.append("text") |
|
.attr("class", "actor") |
|
.attr("text-anchor", "middle") |
|
.attr("alignment-baseline", "middle") |
|
.style("font-family", "sans-serif") |
|
.style("fill", "transparent") |
|
.style("font-size", function(d, i) { |
|
return parseInt(svgContainer.style("width")) * 2/100 |
|
}) |
|
.text(function(d) { return d.name }) |
|
.attr("dy", "20") |
|
|
|
.attr("x", function(d, i, a) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.name) |
|
return r |
|
}) |
|
.transition(laneElemsTransition) |
|
.style("fill", "black") |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// laneElems ENTER lines |
|
newActorElements.append("line") |
|
.attr("class", "actor") |
|
.attr("stroke", "lightgray") |
|
.style("stroke-width", "1px") |
|
.attr("stroke-width", 1) |
|
.attr("x0", function(d, i) { |
|
var r = parseFloat(coordsUtils().hcoord_pct(_laneItems0, d.name) |
|
* parseInt(svgContainer.style("width")) / 100).toFixed(0) |
|
return r |
|
}) |
|
.attr("x1", function(d, i, a) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.name) |
|
var x = parseFloat(coordsUtils().hcoord_pct(_laneItems1, d.name) |
|
* parseInt(svgContainer.style("width")) / 100).toFixed(0) |
|
var l = {name: d.name, id: d.id, x: x } |
|
store.dispatch(actions.setLane(l)) |
|
return r |
|
}) |
|
.attr("x2", function(d, i, a) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.name) |
|
return r |
|
}) |
|
.attr("y1", function(_d, i) { |
|
var text_bbox = this.parentNode.querySelector("text").getBBox(); |
|
return text_bbox.y + text_bbox.height; |
|
}) |
|
.attr("y2", function(d, i, a) { |
|
var r = "100%" |
|
return r |
|
}) |
|
|
|
// messageElements |
|
var messageElements = svgContainer |
|
.select("g.messages") |
|
.selectAll("g.message") |
|
.data(_messages1, function(d, i) { return d.id || (d.id = ++i); }) |
|
|
|
// message elems UPDATE texts |
|
messageElements.select('text') |
|
.transition(laneElemsTransition) |
|
.attr("x", function(d, i) { |
|
var r1 = coordsUtils().hcoord_pct(_laneItems1, d.from) |
|
var r2 = coordsUtils().hcoord_pct(_laneItems1, d.to) |
|
var r = coordsUtils().hcenter_tagged_pct(r1, r2) |
|
return r |
|
}) |
|
.attr("y", function(d, i, s) { |
|
var r = (i + 2) * state.configReducer.vstep - 10 |
|
return r // (i+1)*10 |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// message elems UPDATE lines |
|
messageElements.select('line') |
|
.transition(laneElemsTransition) |
|
.attr("x1", function(d, i, a) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.from) |
|
return r |
|
}) |
|
.attr("x2", function(d, i, a) { |
|
var r = coordsUtils().hcoord_tagged_pct(_laneItems1, d.to) |
|
return r |
|
}) |
|
.attr("y1", function(d, i) { |
|
var r = (i + 2) * state.configReducer.vstep; |
|
return r |
|
}) |
|
.attr("y2", function(d, i) { |
|
var r = (i + 2) * state.configReducer.vstep; |
|
return r |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// message elems UPDATE paths |
|
messageElements.select("path") |
|
.transition(laneElemsTransition) |
|
.attr("d", function(d, i) { |
|
var x_pc = coordsUtils().hcoord_tagged_pct(_laneItems1, d.from) |
|
var xScrollWidth = parseInt(svgContainer.style("width")) |
|
var t = xScrollWidth * Number.parseFloat(x_pc)/100 |
|
var x = t |
|
var rx = 40 |
|
var ry = 20 |
|
var y = (i + 2)*50 - ry |
|
var sweep_flag = 1 |
|
var r = [ |
|
"M", x, y, |
|
"a", rx, ry, 0, 1, sweep_flag, 0, ry*2, |
|
].join(" "); |
|
return r |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// message elems ENTER |
|
var newMessageElements = messageElements |
|
.enter() |
|
.append("g") |
|
.classed("message", true) |
|
|
|
// messageElems ENTER TEXTs |
|
newMessageElements.each(function(d, i) { |
|
var new_message = d3.select(this) |
|
.append("text") |
|
.attr("class", "message") |
|
.style("fill", "transparent") |
|
.style("font-size", function(d, i) { |
|
return parseInt(svgContainer.style("width")) * 2/100 |
|
}) |
|
.attr("dy", ".15em") |
|
.attr("text-anchor", d.from == d.to ? "end" : "middle") |
|
.attr("alignment-baseline", d.from == d.to ? "middle" : "autoMode") |
|
.text(d.msg) |
|
.attr("y", (i + 2) * state.configReducer.vstep - 10) |
|
.attr("x", function() { |
|
var x1 = coordsUtils().hcoord_pct(_laneItems1, d.from) |
|
var x2 = coordsUtils().hcoord_pct(_laneItems1, d.to) |
|
var r = coordsUtils().hcenter_tagged_pct(x1, x2) |
|
return r |
|
}) |
|
.transition(laneElemsTransition) |
|
.style("fill", "grey") |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
}) |
|
|
|
// messageElems ENTER PATHs |
|
newMessageElements.each(function(d, i) { |
|
var new_message = d3.select(this) |
|
if (d.from == d.to) { |
|
new_message.append("path") // new mPATHs |
|
.attr("fill-opacity", 0) |
|
.attr("stroke", "transparent") |
|
.each(function(d) { |
|
// this._current = d_to_arc(d, i); // store initial state |
|
}) |
|
.attr("d", function() { |
|
var x_pc_to = coordsUtils().hcoord_pct(_laneItems1, d.to) |
|
var xScrollWidth = parseInt(svgContainer.style("width")) |
|
var t = xScrollWidth * Number.parseFloat(x_pc_to)/100 |
|
var x = t |
|
var rx = 40 |
|
var ry = 20 |
|
var y = (i + 2)*50 - ry |
|
var sweep_flag = 1 |
|
var r = [ |
|
"M", x, y, |
|
"a", rx, ry, 0, 1, sweep_flag, 0, ry*2, |
|
].join(" "); |
|
return r |
|
}) |
|
.transition(laneElemsTransition) |
|
.attr("stroke", "grey") |
|
.attr("fill", "grey") |
|
.attrTween("marker-end", function(d) { |
|
return function (t) { |
|
if (t != 1) { |
|
return null |
|
} else { |
|
return "url(#message-marker)" |
|
} |
|
} |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
|
|
// messageElems ENTER LINEs |
|
} else { |
|
var line = new_message.append("line") |
|
.attr("class", "message") |
|
.attr("stroke", "transparent") |
|
.attr("stroke-width", 1) |
|
.attr("y1", function() { |
|
var r = (i + 2) * state.configReducer.vstep ; |
|
return r |
|
}) |
|
.attr("y2", function() { |
|
var r = (i + 2) * state.configReducer.vstep ; |
|
return r |
|
}) |
|
.attr("x1", coordsUtils().hcoord_tagged_pct(_laneItems1, d.from)) |
|
.attr("x2", coordsUtils().hcoord_tagged_pct(_laneItems1, d.to)) |
|
.transition(laneElemsTransition) |
|
.attr("stroke", "gray") |
|
.attr("fill", "grey") |
|
.attrTween("marker-end", function() { |
|
return function (t) { |
|
if (t != 1) { |
|
return null |
|
} else { |
|
return "url(#message-marker)" |
|
} |
|
} |
|
}) |
|
.on("start", function start() { |
|
intransition = true |
|
}) |
|
.on("end", function end() { |
|
intransition = false |
|
}) |
|
} |
|
}); |
|
|
|
// message elems EXIT |
|
messageElements.exit() |
|
.transition() |
|
.style("opacity", 0) |
|
.remove() |
|
|
|
} |
|
|
|
exports.render = render; |
|
}))/* */ |
|
/* d3lanes-component-particles.js */ |
|
/* */ |
|
|
|
if (typeof require === "function") { |
|
var d3 = require('./d3.v4.0.0-alpha.40.js') |
|
} |
|
|
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesComponentParticles = global.d3lanesComponentParticles || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
// _____________ context |
|
var stateParticles = { |
|
particlesReducer: {} |
|
} |
|
var rendering = false |
|
var intransition = false |
|
|
|
// _______________________ render |
|
function render(newState) { |
|
if (rendering == true) return |
|
if (newState.particlesReducer.particles.length == 0) return |
|
|
|
|
|
rendering = true |
|
var state = stateParticles= newState |
|
var particleRadio = state.particlesReducer.particleRadio || 6.33 |
|
|
|
var svgContainer = d3.select('body') |
|
.selectAll('svg') |
|
.data(['svgContainer']) |
|
|
|
svgContainer |
|
.enter() |
|
.append("svg") |
|
.attr("id", state.configReducer.container) |
|
|
|
svgContainer |
|
.style('width', state.courtReducer.svgWidth) |
|
.style('height', state.courtReducer.svgHeight) |
|
|
|
var itemsGroup = d3.select('svg') |
|
.selectAll('g.particles') // items |
|
.data(['items']) |
|
|
|
itemsGroup.enter() |
|
.append("g") |
|
.classed("particles", true) // items |
|
|
|
// _________________________________ render Particles |
|
var color = d3.scalePlasma() |
|
.domain([0, 1]) |
|
|
|
var particleElements = svgContainer |
|
.select("g.particles") |
|
.selectAll("circle") |
|
.data(state.particlesReducer.particles) |
|
.attr('cx', function(d, i, a) { return d.x }) |
|
.attr('cy', function(d, i, a) { return d.y }) |
|
.attr('r', function(d, i, a) { return particleRadio }) |
|
.style("fill", function (d) { |
|
var r = d.closestLaneUp.x / (d.closestLaneUp.x - d.closestLaneDown.x) |
|
return color( ((3*r)%10 / 10) + (Math.random()* 3 /10)) |
|
}) |
|
.style("fill-opacity", 0.2) |
|
.style("stroke", "none") |
|
|
|
|
|
var newParticleElements = particleElements |
|
.enter() |
|
.append("circle") |
|
.attr('cx', function(d, i, a) { |
|
return d.x }) |
|
.attr('cy', function(d, i, a) { return d.y }) |
|
.attr('r', function(d, i, a) { return particleRadio }) |
|
.style("fill", function (d) { |
|
var r = d.closestLaneUp.x / (d.closestLaneUp.x - d.closestLaneDown.x) |
|
return color( ((3*r)%10 / 10) + (Math.random()/10)) |
|
}) |
|
.style("stroke", "none") |
|
particleElements.exit() |
|
.remove() |
|
|
|
rendering = false |
|
} // render |
|
|
|
exports.render = render; |
|
}))/* */ |
|
/* d3lanes-reducer.js */ |
|
/* */ |
|
|
|
if (typeof require === "function") { |
|
var d3 = require('./d3.v4.0.0-alpha.40.js') |
|
var d3lanesActions = require('./d3lanes-actions.js') |
|
} |
|
|
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesReducer = global.d3lanesReducer || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
|
|
// _____________ adapted from redux combineReducers |
|
function combineReducers(reducers) { |
|
var reducerKeys = Object.keys(reducers) |
|
var finalReducers = {} |
|
for (var i = 0; i < reducerKeys.length; i++) { |
|
var key = reducerKeys[i] |
|
if (typeof reducers[key] === 'function') { |
|
finalReducers[key] = reducers[key] |
|
} |
|
} |
|
var finalReducerKeys = Object.keys(finalReducers) |
|
|
|
return function combination(state = {}, action) { |
|
var hasChanged = false |
|
var nextState = {} |
|
for (var i = 0; i < finalReducerKeys.length; i++) { |
|
var key = finalReducerKeys[i] |
|
var reducer = finalReducers[key] |
|
var previousStateForKey = state[key] |
|
var nextStateForKey = reducer(previousStateForKey, action) |
|
if (typeof nextStateForKey === 'undefined') { |
|
var errorMessage = getUndefinedStateErrorMessage(key, action) |
|
throw new Error(errorMessage) |
|
} |
|
nextState[key] = nextStateForKey |
|
hasChanged = hasChanged || nextStateForKey !== previousStateForKey |
|
} |
|
return hasChanged ? nextState : state |
|
} |
|
} |
|
|
|
|
|
// _____________ CONFIG |
|
var initialStateConfig = { |
|
modes: {autoMode: 'autoMode', walkMode: 'walkMode'}, |
|
modeLabels: {autoMode: 'auto', walkMode: 'walk'}, |
|
views: {lanesView: 'lanesView'}, |
|
gravity: 0.5, |
|
randNormal: d3.randomNormal(1.3, 2), |
|
randNormal2: d3.randomNormal(0.5, 1.8), |
|
containerElem: '#container', |
|
containerId: 'svgid', |
|
tickspan: 60, |
|
beatTime: 500, |
|
fadeFactor: 3, // times beat - fade items |
|
periodFactor: 4, // times beat - add items |
|
vstep: 50, |
|
itemSpan: 6, |
|
itemProps: ['to', 'from'], |
|
itemVal: 'msg', |
|
messageCollection_000: [ |
|
{id: "1", from: "customer", to: "barrista1", msg: "place order"}, |
|
{id: "2", from: "barrista1", to: "register", msg: "enter order"}, |
|
{id: "3", from: "register", to: "barrista1", msg: "give total"}, |
|
{id: "4", from: "barrista1", to: "barrista1", msg: "get cup making sure that it is fine for purpose"}, |
|
{id: "5", from: "barrista1", to: "barrista2", msg: "give cup"}, |
|
{id: "6", from: "barrista1", to: "customer", msg: "request money"}, |
|
{id: "7", from: "customer", to: "barrista1", msg: "pay order"}, |
|
{id: "8", from: "barrista2", to: "barrista2", msg: "get chai mix"}, |
|
{id: "9", from: "barrista2", to: "barrista2", msg: "add flavor"}, |
|
{id: "10", from: "barrista2", to: "barrista2", msg: "add milk"}, |
|
{id: "11", from: "barrista2", to: "barrista2", msg: "add ice"}, |
|
{id: "12", from: "barrista2", to: "barrista2", msg: "swirl"}, |
|
{id: "13", from: "barrista2", to: "customer", msg: "give tasty beverage"}, |
|
{id: "14", from: "customer", to: "tasty beverage", msg: "sip"}, |
|
{id: "15", from: "tasty beverage", to: "customer", msg: "burn"}, |
|
{id: "16", from: "customer", to: "customer", msg: "cry"}, |
|
{id: "17", from: "customer", to: "manager", msg: "complain"}, |
|
{id: "18", from: "manager", to: "barrista1", msg: "fire"}, |
|
{id: "19", from: "manager", to: "barrista2", msg: "fire"}, |
|
], |
|
messageCollection: [ |
|
{id: "1", from: "app", to: "store", msg: "create store"}, |
|
{id: "2", from: "store", to: "store", msg: "subscribe lanes listener"}, |
|
{id: "3", from: "store", to: "store", msg: "subscribe particles listener"}, |
|
{id: "4", from: "app", to: "app", msg: "start kbd controller"}, |
|
{id: "5", from: "app", to: "app", msg: "start mouse controller"}, |
|
{id: "6", from: "ticker", to: "ticker", msg: "subscribe tickParticles"}, |
|
{id: "7", from: "ticker", to: "ticker", msg: "subscribe setRecords"}, |
|
{id: "8", from: "ticker", to: "ticker", msg: "start auto"}, |
|
{id: "9", from: "store", to: "reducer", msg: "dispatch setRecords action"}, |
|
{id: "10", from: "reducer", to: "reducer", msg: "apply action logic"}, |
|
{id: "11", from: "reducer", to: "store", msg: "return new state"}, |
|
{id: "12", from: "ticker", to: "ticker", msg: "run listeners"}, |
|
{id: "13", from: "component", to: "UI", msg: "render lanes"}, |
|
{id: "14", from: "UI", to: "app", msg: "trigger left arrow event"}, |
|
{id: "15", from: "store", to: "reducer", msg: "dispatch setMode action"}, |
|
{id: "16", from: "reducer", to: "reducer", msg: "run action"}, |
|
{id: "17", from: "reducer", to: "store", msg: "return new state"}, |
|
{id: "18", from: "ticker", to: "ticker", msg: "run listeners"}, |
|
{id: "19", from: "UI", to: "app", msg: "send down arrow event"}, |
|
{id: "20", from: "store", to: "reducer", msg: "dispatch setRecods action"}, |
|
{id: "21", from: "reducer", to: "reducer", msg: "run action and get record"}, |
|
{id: "22", from: "reducer", to: "reducer", msg: "return new set"}, |
|
{id: "23", from: "ticker", to: "ticker", msg: "run listeners"}, |
|
{id: "24", from: "component", to: "UI", msg: "render lanes"}, |
|
{id: "25", from: "UI", to: "app", msg: "send right arrow event"}, |
|
{id: "26", from: "store", to: "reducer", msg: "dispatch setMode action"}, |
|
{id: "27", from: "reducer", to: "reducer", msg: "run action"}, |
|
{id: "28", from: "reducer", to: "reducer", msg: "return new mode auto"}, |
|
{id: "29", from: "ticker", to: "ticker", msg: "run listeners with new state"}, |
|
{id: "30", from: "component", to: "UI", msg: "render auto lanes"}, |
|
{id: "31", from: "store", to: "reducer", msg: "dispatch createParticles action"}, |
|
{id: "32", from: "reducer", to: "reducer", msg: "run action"}, |
|
{id: "33", from: "reducer", to: "store", msg: "return new state with particles"}, |
|
{id: "34", from: "ticker", to: "ticker", msg: "run particles listeners"}, |
|
{id: "35", from: "component", to: "UI", msg: "render particles"}, |
|
], |
|
} |
|
function configReducer(state = initialStateConfig, action) { |
|
if (actions == null) return state |
|
var ActionTypes = d3lanesActions.ActionTypes |
|
switch (action.type) { |
|
default: |
|
return state; |
|
} |
|
} |
|
|
|
// _____________ DEBUG |
|
var initialStateDebug = { |
|
debugMode: true, |
|
fps: 0 |
|
} |
|
function debugReducer(state = initialStateDebug, action) { |
|
if (actions == null) return state |
|
|
|
var ActionTypes = d3lanesActions.ActionTypes |
|
switch (action.type) { |
|
case ActionTypes.SET_FPS: |
|
return setFps(state, action) |
|
case ActionTypes.SWITCH_DEBUGMODE: |
|
console.log('SWITCH_DEBUGMODE') |
|
return switchDebugMode(state, action) |
|
default: |
|
return state; |
|
} |
|
} |
|
function setFps(state, action) { |
|
return Object.assign({}, state, { |
|
fps: action.fps |
|
}) |
|
} |
|
function switchDebugMode(state, action) { |
|
return Object.assign({}, state, { |
|
debugMode: !state.debugMode |
|
}) |
|
} |
|
|
|
// _____________ COURT |
|
var initialStateCourt = { |
|
svgHeight: 400, // |
|
svgWidth: 600, // |
|
keys: [], |
|
notice: 'auto lanes', |
|
currentMode: 'autoMode', |
|
currentView: 'lanesView', |
|
arrowKeysStarted: false, |
|
keybKeyEventsStarted: false, |
|
tickerStarted: false, |
|
lastTick: 0, |
|
mousePos: [null, null], |
|
} |
|
function courtReducer(state = initialStateCourt, action) { |
|
if (actions == null) return state |
|
var ActionTypes = d3lanesActions.ActionTypes |
|
switch (action.type) { |
|
case ActionTypes.SET_KEYBKEY: |
|
console.log('SET_KEYBKEY') |
|
var ks = state.keys |
|
ks[action.keyCode] = true |
|
return Object.assign({}, state, { |
|
keys: ks |
|
}); |
|
case ActionTypes.RELEASE_KEYBKEY: |
|
console.log('RELEASE_KEYBKEY') |
|
var ks = state.keys |
|
ks[action.keyCode] = false |
|
return Object.assign({}, state, { |
|
keys: ks |
|
}); |
|
case ActionTypes.START_KEYBKEY_EVENTS: |
|
console.log('START_KEYBKEY_EVENTS') |
|
return Object.assign({}, state, { |
|
keybKeyEventsStarted: true |
|
}); |
|
case ActionTypes.SET_MODE: |
|
console.log('SET_MODE') |
|
return Object.assign({}, state, { |
|
currentMode: action.currentMode, |
|
}); |
|
case ActionTypes.SET_VIEW: |
|
console.log('SET_VIEW') |
|
return Object.assign({}, state, { |
|
currentView: action.currentView, |
|
}); |
|
case ActionTypes.SET_NOTICE: |
|
console.log('SET_NOTICE') |
|
return Object.assign({}, state, { |
|
notice: action.notice, |
|
}); |
|
case ActionTypes.START_TICKER: |
|
console.log('START_TICKER') |
|
return Object.assign({}, state, { |
|
tickerStarted: true |
|
}); |
|
case ActionTypes.STOP_TICKER: |
|
console.log('STOP_TICKER') |
|
return Object.assign({}, state, { |
|
tickerStarted: false |
|
}); |
|
case ActionTypes.UPDATE_MOUSE_POS: |
|
return Object.assign({}, state, { |
|
mousePos: [action.x, action.y] |
|
}); |
|
case ActionTypes.RESIZE_SCREEN: |
|
console.log('RESIZE_SCREEN') |
|
return Object.assign({}, state, { |
|
svgWidth: action.width, |
|
svgHeight: action.height |
|
}); |
|
case ActionTypes.RESIZE_WIDTH: |
|
console.log('RESIZE_WIDTH') |
|
return Object.assign({}, state, { |
|
svgWidth: state.svgWidth + action.delta |
|
}); |
|
case ActionTypes.RESIZE_HEIGHT: |
|
console.log('RESIZE_HEIGHT') |
|
return Object.assign({}, state, { |
|
svgHeight: state.svgHeight + action.delta |
|
}); |
|
default: |
|
return state; |
|
} |
|
} |
|
// _____________ LANES |
|
var initialStateLanes = { |
|
lanes: [], |
|
lanesIndex: 0, |
|
messages: [], |
|
records: [], |
|
recordsCollection: [], |
|
areRecordsFetched: false, |
|
messagesCursorLow: 0, |
|
messagesCursorHigh: 0, |
|
} |
|
|
|
function lanesReducer(state = initialStateLanes, action) { |
|
if (actions == null) return state |
|
var ActionTypes = d3lanesActions.ActionTypes |
|
switch (action.type) { |
|
|
|
case ActionTypes.INCREASE_CURSOR_LOW: |
|
var r = Object.assign({}, state, |
|
{messagesCursorLow: ++state.messagesCursorLow} |
|
); |
|
return r |
|
case ActionTypes.REDUCE_CURSOR_LOW: |
|
var r = Object.assign({}, state, |
|
{messagesCursorLow: --state.messagesCursorLow} |
|
); |
|
return r |
|
case ActionTypes.INCREASE_CURSOR_HIGH: |
|
var r = Object.assign({}, state, |
|
{messagesCursorHigh: ++state.messagesCursorHigh} |
|
); |
|
return r |
|
case ActionTypes.REDUCE_CURSOR_HIGH: |
|
var r = Object.assign({}, state, |
|
{messagesCursorHigh: --state.messagesCursorHigh} |
|
); |
|
return r |
|
|
|
case ActionTypes.DELETE_LANE: // setLane |
|
|
|
var lanes = state.lanes |
|
var ls = lanes.filter(function( obj ) { |
|
return obj.id !== action.lane.id; |
|
}); |
|
|
|
var r = Object.assign({}, state, |
|
{lanes: ls}, |
|
{lanesIndex: ls.length} |
|
); |
|
return r |
|
|
|
|
|
case ActionTypes.SET_LANE: // setLane |
|
|
|
var lanes = state.lanes |
|
var ls = {} |
|
var result = lanes.filter(function( obj ) { |
|
return obj.id == action.lane.id; |
|
}); |
|
|
|
if (result.length === 0) { // add |
|
ls = {lanes: [ |
|
{ |
|
id: action.lane.id, |
|
name: action.lane.name, |
|
x: action.lane.x |
|
}, |
|
...lanes |
|
]} |
|
} else { // edit |
|
ls = {lanes: lanes.map(lane => |
|
lane.id === action.lane.id ? |
|
Object.assign({}, lane, { id: action.lane.id, name: action.lane.name, x: action.lane.x }) : |
|
lane |
|
)} |
|
} |
|
|
|
var r = Object.assign({}, state, |
|
ls, |
|
{ |
|
lanesIndex: ls.lanes.length |
|
}); |
|
return r |
|
|
|
case ActionTypes.SET_LANES: |
|
console.log('SET_LANES') |
|
return Object.assign({}, state, { |
|
lanes: action.lanes, |
|
lanesIndex: Object.keys(action.lanes).length |
|
}); |
|
case ActionTypes.FETCH_RECORDS: |
|
console.log('FETCH_RECORDS') |
|
var processRecord = function processRecord(d) { |
|
d.amount = +d.amount; |
|
d.risk = +d.risk; |
|
d.valueOf = function value() { |
|
return this.amount; |
|
} |
|
return d; |
|
} |
|
|
|
var processData = function processData(error, dataCsv) { |
|
if (store.getState().court.currentMode == 0) { // _tbd_ |
|
++timeTick |
|
++vLast |
|
store.dispatch(actions.setMessages(store.getState().configReducer.messageCollection.slice(0, |
|
store.getState().configReducer.messageCollection.length))) |
|
} |
|
} |
|
|
|
d3.queue() |
|
.defer(d3.csv, action.src, processRecord) |
|
.await(processData) |
|
|
|
return Object.assign({}, state); |
|
|
|
case ActionTypes.SET_MESSAGES: |
|
console.log('SET_MESSAGES') |
|
return Object.assign({}, state, { |
|
messages: action.messages, |
|
}); |
|
case ActionTypes.SET_RECORDS_FETCHED: |
|
console.log('SET_RECORDS_FETCHED') |
|
return Object.assign({}, state, { |
|
areRecordsFetched: action.areRecordsFetched |
|
}) |
|
|
|
case ActionTypes.SET_RECORDS_COLLECTION: |
|
console.log('SET_RECORDS_COLLECTION') |
|
return Object.assign({}, state, { |
|
recordsCollection: action.recordsCollection |
|
}) |
|
|
|
case ActionTypes.SET_RECORDS: |
|
console.log('SET_RECORDS') |
|
var vLow = state.messagesCursorLow |
|
var vHigh = state.messagesCursorHigh |
|
var itemSpan = action.itemSpan |
|
var mode = action.mode |
|
var r = state |
|
if (mode == 'autoMode') { |
|
var records = state.recordsCollection |
|
var numRecords = records.length |
|
if (vHigh >= vLow) vHigh = vHigh + 1 // add one to upper border |
|
if (vHigh > numRecords) vHigh = -1 // upper border |
|
if (((vHigh - vLow) > itemSpan) // all spteps full |
|
|| (vHigh == -1) // infinitum with vLow active |
|
|| (vLow == -1) // get always from reset |
|
) vLow = vLow + 1 // increase lower border |
|
if (vLow > numRecords) vLow = -1 // reset at end of cycle |
|
r = Object.assign({}, state, { |
|
records: state.recordsCollection.slice(vLow, vHigh), |
|
messagesCursorLow: vLow, |
|
messagesCursorHigh: vHigh, |
|
}) |
|
} |
|
return r |
|
|
|
case ActionTypes.WALK_UP_RECORDS: |
|
console.log('WALK_UP_RECORDS') |
|
var vLow = state.messagesCursorLow |
|
var vHigh = state.messagesCursorHigh |
|
var itemSpan = action.itemSpan |
|
var mode = action.mode |
|
var r = state |
|
if (mode == 'walkMode') { |
|
vLow = Math.max(0, --vLow) |
|
r = Object.assign({}, state, { |
|
records: state.recordsCollection.slice(vLow, vHigh), |
|
messagesCursorLow: vLow, |
|
messagesCursorHigh: vHigh, |
|
}) |
|
} |
|
return r |
|
|
|
case ActionTypes.WALK_DOWN_RECORDS: |
|
console.log('WALK_DOWN_RECORDS') |
|
var vLow = state.messagesCursorLow |
|
var vHigh = state.messagesCursorHigh |
|
var itemSpan = action.itemSpan |
|
var mode = action.mode |
|
var r = state |
|
if (mode == 'walkMode') { |
|
if ((vHigh - vLow) > itemSpan) ++vLow |
|
++vHigh |
|
r = Object.assign({}, state, { |
|
records: state.recordsCollection.slice(vLow, vHigh), |
|
messagesCursorLow: vLow, |
|
messagesCursorHigh: vHigh, |
|
}) |
|
} |
|
return r |
|
|
|
default: |
|
return state; |
|
} |
|
} |
|
|
|
// _____________ PARTICLES |
|
var initialStateParticles = { |
|
particles: [], |
|
particleIndex: 0, |
|
particlesGenerating: false, |
|
particlesPerTick: 33, |
|
particleRadio: 9, |
|
} |
|
function particlesReducer(state = initialStateParticles, action) { |
|
if (actions == null) return state |
|
var ActionTypes = d3lanesActions.ActionTypes |
|
switch (action.type) { |
|
case ActionTypes.START_PARTICLES: // startParticles |
|
return Object.assign({}, state, { |
|
particlesGenerating: true |
|
}); |
|
case ActionTypes.STOP_PARTICLES: // stopParticles |
|
return Object.assign({}, state, { |
|
particlesGenerating: false |
|
}); |
|
|
|
|
|
case ActionTypes.CREATE_PARTICLES: // createParticles |
|
var newParticles = state.particles.slice(0) |
|
var i |
|
for (i = 0; i < action.N; i++) { |
|
|
|
var ref = parseInt(action.x) |
|
var closestLaneUp = action.lanes |
|
.filter(function (d) {return d.x >= ref} ) |
|
.reduce(function (prev, curr) { |
|
return (Math.abs(curr.x - ref) < Math.abs(prev.x - ref) ? curr : prev); |
|
}, {id: 'end', x: action.xEnd}) |
|
|
|
var closestLaneDown = action.lanes |
|
.filter(function (d) {return d.x <= ref} ) |
|
.reduce(function (prev, curr) { |
|
return (Math.abs(curr.x - ref) < Math.abs(prev.x - ref) ? curr : prev); |
|
}, {id: 'init', x: action.xInit}) |
|
|
|
var particle = {id: state.particleIndex+i, |
|
x: action.x, |
|
y: action.y, |
|
closestLaneDown: closestLaneDown, |
|
closestLaneUp: closestLaneUp, |
|
}; |
|
|
|
particle.vector = [particle.id%2 ? - action.randNormal() : action.randNormal(), |
|
- action.randNormal2()*3.3]; |
|
|
|
newParticles.unshift(particle); |
|
} |
|
return Object.assign({}, state, { |
|
particles: newParticles, |
|
particleIndex: state.particleIndex+i+1 |
|
}); |
|
|
|
|
|
case ActionTypes.TICK_PARTICLES: // tickParticles |
|
var laneXs = action.lanes |
|
.map(function(l) { |
|
var x = parseInt(l.x) |
|
return x}) |
|
var svgWidth = action.width |
|
var svgHeight = action.height |
|
var gravity = action.gravity |
|
var movedParticles = state.particles |
|
.filter(function (p) { |
|
return (!(p.y > svgHeight)) |
|
}) |
|
.filter(function (p) { |
|
return (!(p.y < 0)) |
|
}) |
|
.map(function (p) { |
|
var vx = p.vector[0] |
|
var vy = p.vector[1] |
|
p.x += vx |
|
p.y += vy |
|
|
|
var ref = parseInt(p.x) |
|
|
|
var laneUp = action.lanes |
|
.filter(function(l) { |
|
return (l.id == p.closestLaneUp.id) |
|
}) |
|
p.closestLaneUp.x = (laneUp.length > 0 ) ? +laneUp[0].x : +p.closestLaneUp.x |
|
|
|
var laneDown = action.lanes |
|
.filter(function(l) { |
|
return (l.id == p.closestLaneDown.id) |
|
}) |
|
p.closestLaneDown.x = (laneDown.length > 0 ) ? +laneDown[0].x : +p.closestLaneDown.x |
|
|
|
if (ref < (p.closestLaneDown.x + state.particleRadio) || ref > (p.closestLaneUp.x - state.particleRadio)) { |
|
p.vector[0] = -p.vector[0] |
|
} |
|
p.vector[1] += gravity + 2 * gravity * (p.y - svgHeight) / svgHeight |
|
return p |
|
}); |
|
return Object.assign({}, state, { |
|
particles: movedParticles, |
|
particleIndex: movedParticles.length, |
|
}); |
|
default: |
|
return state; |
|
} |
|
} |
|
|
|
// _____________ combined reducer |
|
var reducer = combineReducers({ |
|
debugReducer: debugReducer, |
|
configReducer: configReducer, |
|
courtReducer: courtReducer, |
|
lanesReducer: lanesReducer, |
|
particlesReducer: particlesReducer, |
|
}) |
|
|
|
exports.reducer = reducer; |
|
}));/* */ |
|
/* d3lanes-store.js */ |
|
/* */ |
|
|
|
/* adapted from REDUX http://redux.js.org/ */ |
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3lanesStore = global.d3lanesStore || {}))); |
|
}(this, function (exports) { 'use strict'; |
|
|
|
var createStore = function createStore(reducer, initialState) { |
|
var currentReducer = reducer |
|
var currentState = initialState |
|
var currentListeners = [] |
|
var nextListeners = currentListeners |
|
var isDispatching = false |
|
|
|
// ______________________________ ensureCanMutateNextListeners |
|
function ensureCanMutateNextListeners() { |
|
if (nextListeners === currentListeners) { |
|
nextListeners = currentListeners.slice() |
|
} |
|
} |
|
|
|
// ______________________________ getState |
|
function getState() { |
|
return currentState |
|
} |
|
|
|
// redux compose |
|
function compose(...funcs) { |
|
if (funcs.length === 0) { |
|
return arg => arg |
|
} else { |
|
const last = funcs[funcs.length - 1] |
|
const rest = funcs.slice(0, -1) |
|
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) |
|
} |
|
} |
|
|
|
// ______________________________ subscribe |
|
function subscribe(listener) { |
|
|
|
if (typeof listener !== 'function') { |
|
throw new Error('Expected listener to be a function.') |
|
} |
|
|
|
var isSubscribed = true |
|
|
|
ensureCanMutateNextListeners() |
|
nextListeners.push(listener) |
|
|
|
return function unsubscribe() { |
|
if (!isSubscribed) { |
|
return |
|
} |
|
|
|
isSubscribed = false |
|
|
|
ensureCanMutateNextListeners() |
|
var index = nextListeners.indexOf(listener) |
|
nextListeners.splice(index, 1) |
|
} |
|
} |
|
|
|
// ______________________________ dispatch |
|
function dispatch(action) { |
|
|
|
if (typeof action.type === 'undefined') { |
|
throw new Error( |
|
'Actions may not have an undefined "type" property. ' + |
|
'Have you misspelled a constant?' |
|
) |
|
} |
|
|
|
if (isDispatching) { |
|
throw new Error('Reducers may not dispatch actions.') |
|
} |
|
|
|
try { |
|
isDispatching = true |
|
currentState = currentReducer(currentState, action) |
|
} finally { |
|
isDispatching = false |
|
} |
|
|
|
var listeners = currentListeners = nextListeners |
|
for (var i = 0; i < listeners.length; i++) { |
|
listeners[i]() |
|
} |
|
|
|
return action |
|
} |
|
|
|
return { |
|
compose: compose, |
|
dispatch: dispatch, |
|
subscribe: subscribe, |
|
getState: getState |
|
} |
|
} |
|
|
|
exports.createStore = createStore; |
|
})); |
|
|
|
/* */ |
|
/* index.js */ |
|
/* */ |
|
|
|
if (typeof require === "function") { |
|
var d3 = require('./d3.v4.0.0-alpha.40.js') |
|
var d3lanesComponentLanes = require('./d3lanes-component-lanes.js') |
|
var d3lanesComponentCourt = require('./d3lanes-component-court.js') |
|
var d3lanesComponentParticles = require('./d3lanes-component-particles.js') |
|
var d3lanesReducer = require('./d3lanes-reducer.js') |
|
var d3lanesStore = require('./d3lanes-store.js') |
|
var d3lanesActions = require('./d3lanes-actions.js') |
|
var d3lanesControls = require('./d3lanes-controls.js') |
|
} |
|
|
|
var store = d3lanesStore.createStore(d3lanesReducer.reducer, d3lanesReducer.reducer()) |
|
store.subscribe(store.compose(d3lanesComponentCourt.render, store.getState)) |
|
store.subscribe(store.compose(d3lanesComponentLanes.render, store.getState)) |
|
store.subscribe(store.compose(d3lanesComponentParticles.render, store.getState)) |
|
var actions = d3lanesActions.ActionCreators |
|
|
|
var svgContainer = d3.select(store.getState().configReducer.containerElem) |
|
.selectAll('svg') |
|
.data(['svg']) |
|
var svgContainerNew = svgContainer.enter() |
|
.append("svg") |
|
.attr("id", store.getState().configReducer.containerId) |
|
.style('width', store.getState().courtReducer.svgWidth) |
|
.style('height', store.getState().courtReducer.svgHeight) |
|
.style('background', 'oldlace') |
|
.attr('class', 'bar-chart') // |
|
.style('border', '1px solid darkgrey') |
|
.attr('viewbox',"0 0 3 2") |
|
|
|
d3lanesControls.kbdControls(store, d3.select('svg')).startKeybKeyEvents() |
|
d3lanesControls.mouseControls(store).startMouseEvents(d3.select('svg')) |
|
|
|
store.dispatch(actions.setRecordsCollection( |
|
store.getState().configReducer.messageCollection)) |
|
store.dispatch(actions.setRecordsFetched(true)) |
|
|
|
// jff |
|
store.dispatch(actions.startParticles()) |
|
store.dispatch(actions.createParticles({ |
|
particlesPerTick: store.getState().particlesReducer.particlesPerTick * 5, |
|
x: store.getState().courtReducer.svgWidth / 2, |
|
y: store.getState().courtReducer.svgWidth / 2, |
|
xInit: 0, |
|
xEnd: store.getState().courtReducer.svgWidth, |
|
randNormal: store.getState().configReducer.randNormal, |
|
randNormal2:store.getState().configReducer.randNormal2, |
|
lanes: [], |
|
})) |
|
store.dispatch(actions.stopParticles()) |
|
|
|
|
|
var ticker = d3lanesControls.tickControls(store) |
|
.subscribe( |
|
store.compose( |
|
store.dispatch, |
|
actions.tickParticles, |
|
function() { return { |
|
width: store.getState().courtReducer.svgWidth, |
|
height: store.getState().courtReducer.svgHeight, |
|
gravity: store.getState().configReducer.gravity, |
|
lanes: store.getState().lanesReducer.lanes |
|
} |
|
} |
|
)) |
|
.start() |
|
|
|
var walker = d3lanesControls.stepControls(store) |
|
.subscribe( |
|
store.compose( |
|
store.dispatch, |
|
actions.setRecords, |
|
function() { return { |
|
itemSpan: store.getState().configReducer.itemSpan, |
|
currentMode: store.getState().courtReducer.currentMode |
|
} |
|
} |
|
)) |
|
.start() |
|
|