Skip to content

Instantly share code, notes, and snippets.

@strangesast
Last active May 11, 2019 01:44
Show Gist options
  • Save strangesast/6572414e6994576e661d37a3e7677ea5 to your computer and use it in GitHub Desktop.
Save strangesast/6572414e6994576e661d37a3e7677ea5 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html {
height: 100%;
box-sizing: border-box;
}
*, *:after, *:before {
box-sizing: inherit;
}
body {
margin: 0;
}
body svg {
height: 100%;
max-width: 100vh;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<p>Distribute open work orders among owners. Also include unclaimed W/O's</p>
<svg viewBox="0 0 1000 1000"></svg>
<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
var data = (function() {
const partIds = Array.from(Array(20), (_, i) => `part-${('0'.repeat(3) + i).slice(-3)}`);
const parts = partIds.reduce((acc, id) => {
const name = id[0].toUpperCase() + id.slice(1).split('-').join(' ');
return {...acc, [id]: {id, name}};
}, {});
const priorityCount = 3;
const priorityColorScheme = d3.schemeSet1;
const priorityIds = Array.from(Array(3), (_, i) => i);
const priorities = priorityIds.reduce((acc, id, i) => ({...acc, [id]: {id, name: id, color: priorityColorScheme[i]}}), {});
const workOrderIds = Array.from(Array(50)).reduce(acc => {
while (true) {
const id = `W${('0'.repeat(5) + Math.floor(Math.random() * 1e5)).slice(-5)}`;
if (acc.indexOf(id) < 0) {
return [...acc, id];
}
}
}, []);
const workOrders = workOrderIds.reduce((acc, id) => {
const priority = Math.floor(Math.random() * priorityCount);
const completed = Math.random() > 0.8;
const part = partIds[Math.floor(partIds.length * Math.random())];
return {...acc, [id]: {id, name: id, completed, part, priority }};
}, {});
const incompleteWorkOrderIds = [];
const completeWorkOrderIds = [];
for (const {id, completed} of Object.values(workOrders)) {
if (completed) {
completeWorkOrderIds.push(id);
} else {
incompleteWorkOrderIds.push(id);
}
}
const workerIds = Array.from(Array(10), (_, i) => ('0'.repeat(4) + Math.floor(Math.random() * 1e4)).slice(-4));
const workers = workerIds.reduce((acc, id) => {
const workOrders = [...take(Math.floor(Math.random() * 3), completeWorkOrderIds), ...take(Math.ceil(Math.random() * 3), incompleteWorkOrderIds)];
return {...acc, [id]: {id, name: `#${id}`, workOrders}};
}, {'none': {'name': 'Unclaimed', workOrders: []}});
return {
part: {
ids: partIds,
values: parts,
},
priority: {
ids: priorityIds,
values: priorities,
},
workOrder: {
ids: workOrderIds,
values: workOrders,
},
completedState: {
ids: [false, true],
values: {'true': {name: 'Completed'}, 'false': {name: 'Incomplete'}},
},
worker: {
ids: ['none', ...workerIds],
values: workers,
},
};
})();
function random(array) {
return array[Math.floor(Math.random() * array.length)];
}
function take(n, array) {
const subset = [];
while (n > 0 && array.length) {
const [el] = array.splice(Math.floor(array.length * Math.random()), 1);
subset.push(el);
n--;
}
return subset;
}
</script>
<script>
const width = 1000;
const height = 1000;
var svg = d3.select('svg');
// var color = d3.scaleOrdinal(d3.schemeCategory20);
// var nodes = d3.range(100).map(function() {
// var node = {};
// node.groupA = Math.floor(Math.random()*5);
// node.groupB = Math.floor(Math.random()*2);
// return node;
// })
// radially
const radius = 300;
function generatePts(arr) {
return arr.reduce((acc, id, i) => {
if (id === 'none') {
return {...acc, [id]: {x: 500, y: 500}};
}
return {
...acc,
[id]: {
y: 500 + Math.sin(Math.PI * 2 * i / arr.length) * radius,
x: 500 + Math.cos(Math.PI * 2 * i / arr.length) * radius,
}
};
}, {});
}
// function generatePts(arr) {
// return arr.reduce((acc, id, i) => ({...acc, [id]: {
// y: height / 2,
// x: width / (arr.length + 1) * (1 + i),
// }}), {});
// }
// priority
const foci = {
'priority': generatePts(data.priority.ids),
'worker': generatePts(['none', ...data.worker.ids]),
'completedState': generatePts([true, false]),
'part': generatePts(data.part.ids),
};
var currentView = Object.keys(foci)[0];
var simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.id))
.force('charge', d3.forceManyBody());
// .force('center', d3.forceCenter(width / 2, height / 2));
var nodes = Object.entries(data.workOrder.values).map(([id, wo]) => ({
id,
foci: {
'priority': wo['priority'],
'worker': data.worker.ids.find(id => data.worker.values[id].workOrders.includes(wo.id)) || 'none',
'completedState': wo['completed'].toString(),
'part': wo['part'],
},
x: 0,
y: 0,
}));
let node = svg.append('g')
.attr('class', 'nodes')
.selectAll('g.circle')
.data(nodes);
let nodeEnter = node.enter()
.append('g')
.classed('circle', true)
nodeEnter.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
nodeEnter.append('circle')
.attr('r', 8)
.attr('fill', d => data.priority.values[data.workOrder.values[d.id].priority].color)
.append('title')
.text(d => data.workOrder.values[d.id].name);
node = nodeEnter.merge(node);
let titles = svg.append('g')
.classed('titles', true)
titles.selectAll('g.title')
.data(data[currentView].ids)
.enter().append('g').classed('title', true)
.attr('transform', d => `translate(${foci[currentView][d].x},${foci[currentView][d].y})`)
.append('text')
.attr('text-anchor', 'middle')
.text(t => data[currentView].values[t].name);
simulation
.nodes(nodes)
.on('tick', ticked);
function onChange() {
if (!d3.event.active) {
simulation.alpha(0.7);
simulation.restart();
}
}
function changeView(nextView) {
if(nextView != currentView) {
currentView = nextView;
const sel = titles.selectAll('g.title').data(data[currentView].ids, v => typeof v === 'string' ? v : v.toString());
sel.exit().remove();
sel.enter().append('g').classed('title', true)
.attr('transform', d => `translate(${foci[currentView][d].x},${foci[currentView][d].y})`)
.append('text')
.attr('text-anchor', 'middle')
.text(t => data[currentView].values[t].name);
simulation.alpha(0.7);
simulation.restart();
}
}
var buttons = svg.selectAll(null)
.data(Object.keys(foci))
.enter()
.append('g')
.attr('transform', (_, i) => `translate(${i*120+50},${50})`)
.on('click', function(d) {
changeView(d);
})
.style('cursor','pointer')
buttons.append('rect')
.attr('width',100)
.attr('height',50)
.attr('fill','lightgrey')
buttons.append('text')
.text(function(d) { return d; })
.attr('dy', 30)
.attr('dx', 50)
.style('text-anchor','middle');
function ticked() {
var k = this.alpha() * 0.3;
for (const n of nodes) {
const key = n['foci'][currentView];
n.y += (foci[currentView][key].y - n.y) * k;
n.x += (foci[currentView][key].x - n.x) * k;
}
node.attr('transform', d => `translate(${d.x},${d.y})`);
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
setInterval(() => {
const subset = take(5, data.workOrder.ids.slice()).map(id => data.workOrder.values[id]);
for (const each of subset) {
data.workOrder.values[each.id].completed = !each.completed;
data.workOrder.values[each.id].priority = random(data.priority.ids.filter(id => id != each.priority));
}
node.filter(n => subset.includes(n.id));
// foci: {
// 'priority': wo['priority'],
// 'worker': data.worker.ids.find(id => data.worker.values[id].workOrders.includes(wo.id)) || 'none',
// 'completedState': wo['completed'].toString(),
// 'part': wo['part'],
// },
}, 1000);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment