mirror of https://github.com/dirtbags/moth.git
Separated scoreboard html pages and added a projector scoreboard
This commit is contained in:
parent
df36cd9bec
commit
71bc7d56d7
|
@ -0,0 +1,339 @@
|
||||||
|
{
|
||||||
|
"points": [
|
||||||
|
[1429583964, "1", "nocode", 1],
|
||||||
|
[1429583994, "1", "nocode", 2],
|
||||||
|
[1429583998, "2", "nocode", 1],
|
||||||
|
[1429584021, "1", "nocode", 3],
|
||||||
|
[1429584035, "1", "nocode", 4],
|
||||||
|
[1429584085, "2", "js", 1],
|
||||||
|
[1429584118, "3", "codebreaking", 1],
|
||||||
|
[1429584201, "1", "nocode", 10],
|
||||||
|
[1429584403, "1", "js", 1],
|
||||||
|
[1429584466, "3", "net-re", 1],
|
||||||
|
[1429584776, "4", "nocode", 1],
|
||||||
|
[1429584851, "4", "nocode", 2],
|
||||||
|
[1429585153, "4", "nocode", 3],
|
||||||
|
[1429585279, "4", "nocode", 4],
|
||||||
|
[1429585297, "5", "nocode", 1],
|
||||||
|
[1429585326, "5", "nocode", 2],
|
||||||
|
[1429585381, "6", "nocode", 1],
|
||||||
|
[1429585386, "5", "nocode", 3],
|
||||||
|
[1429585440, "6", "nocode", 2],
|
||||||
|
[1429585521, "4", "nocode", 10],
|
||||||
|
[1429585664, "6", "nocode", 3],
|
||||||
|
[1429585689, "6", "nocode", 4],
|
||||||
|
[1429585744, "6", "nocode", 10],
|
||||||
|
[1429585819, "1", "net-re", 1],
|
||||||
|
[1429586010, "3", "js", 1],
|
||||||
|
[1429586189, "6", "js", 1],
|
||||||
|
[1429586546, "7", "nocode", 1],
|
||||||
|
[1429586574, "7", "nocode", 2],
|
||||||
|
[1429586648, "7", "nocode", 3],
|
||||||
|
[1429586656, "3", "js", 2],
|
||||||
|
[1429586661, "8", "nocode", 2],
|
||||||
|
[1429586700, "7", "nocode", 4],
|
||||||
|
[1429586756, "7", "nocode", 10],
|
||||||
|
[1429586770, "8", "nocode", 1],
|
||||||
|
[1429586844, "8", "js", 1],
|
||||||
|
[1429587055, "6", "net-re", 1],
|
||||||
|
[1429587072, "3", "nocode", 1],
|
||||||
|
[1429587088, "3", "nocode", 2],
|
||||||
|
[1429587102, "3", "nocode", 3],
|
||||||
|
[1429587117, "3", "nocode", 4],
|
||||||
|
[1429587137, "3", "nocode", 10],
|
||||||
|
[1429587268, "1", "codebreaking", 1],
|
||||||
|
[1429587514, "3", "nocode", 20],
|
||||||
|
[1429587821, "9", "nocode", 1],
|
||||||
|
[1429587843, "9", "nocode", 2],
|
||||||
|
[1429587892, "8", "codebreaking", 1],
|
||||||
|
[1429587983, "9", "nocode", 3],
|
||||||
|
[1429588070, "9", "nocode", 4],
|
||||||
|
[1429588116, "1", "codebreaking", 2],
|
||||||
|
[1429588141, "3", "net-re", 2],
|
||||||
|
[1429588196, "4", "nocode", 20],
|
||||||
|
[1429588236, "10", "nocode", 1],
|
||||||
|
[1429588260, "10", "nocode", 2],
|
||||||
|
[1429588376, "7", "nocode", 20],
|
||||||
|
[1429588386, "10", "nocode", 3],
|
||||||
|
[1429588519, "7", "js", 1],
|
||||||
|
[1429588613, "10", "nocode", 4],
|
||||||
|
[1429588960, "9", "nocode", 10],
|
||||||
|
[1429589154, "11", "nocode", 1],
|
||||||
|
[1429589189, "11", "nocode", 2],
|
||||||
|
[1429589215, "11", "nocode", 3],
|
||||||
|
[1429589325, "11", "nocode", 10],
|
||||||
|
[1429589534, "11", "nocode", 4],
|
||||||
|
[1429589605, "4", "bonus", 30],
|
||||||
|
[1429589805, "3", "net-re", 3],
|
||||||
|
[1429590136, "10", "net-re", 1],
|
||||||
|
[1429589817, "10", "js", 1],
|
||||||
|
[1429589867, "10", "codebreaking", 1],
|
||||||
|
[1429590137, "9", "js", 1],
|
||||||
|
[1429590009, "1", "net-re", 2],
|
||||||
|
[1429590285, "10", "net-re", 2],
|
||||||
|
[1429590406, "11", "js", 1],
|
||||||
|
[1429590574, "4", "nocode", 30],
|
||||||
|
[1429590610, "3", "codebreaking", 2],
|
||||||
|
[1429590642, "9", "codebreaking", 1],
|
||||||
|
[1429590666, "11", "nocode", 30],
|
||||||
|
[1429590775, "3", "codebreaking", 4],
|
||||||
|
[1429590900, "1", "net-re", 3],
|
||||||
|
[1429590909, "11", "net-re", 1],
|
||||||
|
[1429591066, "1", "bonus", 10],
|
||||||
|
[1429591073, "11", "net-re", 2],
|
||||||
|
[1429591159, "11", "net-re", 3],
|
||||||
|
[1429591392, "1", "net-re", 4],
|
||||||
|
[1429591600, "11", "net-re", 4],
|
||||||
|
[1429591615, "11", "js", 2],
|
||||||
|
[1429591620, "11", "codebreaking", 1],
|
||||||
|
[1429591953, "3", "net-re", 4],
|
||||||
|
[1429592104, "1", "net-re", 5],
|
||||||
|
[1429592528, "12", "nocode", 1],
|
||||||
|
[1429592555, "12", "nocode", 3],
|
||||||
|
[1429592595, "12", "nocode", 2],
|
||||||
|
[1429592634, "12", "nocode", 4],
|
||||||
|
[1429592777, "1", "net-re", 6],
|
||||||
|
[1429592818, "11", "codebreaking", 2],
|
||||||
|
[1429592954, "12", "nocode", 10],
|
||||||
|
[1429593070, "11", "codebreaking", 4],
|
||||||
|
[1429593288, "11", "nocode", 20],
|
||||||
|
[1429593393, "9", "nocode", 20],
|
||||||
|
[1429593815, "11", "bonus", 10],
|
||||||
|
[1429593885, "3", "net-re", 5],
|
||||||
|
[1429594213, "12", "js", 1],
|
||||||
|
[1429594226, "3", "net-re", 6],
|
||||||
|
[1429594312, "12", "codebreaking", 1],
|
||||||
|
[1429594645, "1", "bonus", 10],
|
||||||
|
[1429594642, "12", "codebreaking", 2],
|
||||||
|
[1429595082, "1", "net-re", 7],
|
||||||
|
[1429595683, "3", "net-re", 7],
|
||||||
|
[1429596041, "9", "nocode", 30],
|
||||||
|
[1429596823, "3", "nocode", 50],
|
||||||
|
[1429597683, "1", "net-re", 8],
|
||||||
|
[1429594279, "10", "codebreaking", 2],
|
||||||
|
[1429594357, "10", "codebreaking", 4],
|
||||||
|
[1429594399, "10", "codebreaking", 5],
|
||||||
|
[1429594404, "9", "net-re", 2],
|
||||||
|
[1429594431, "9", "net-re", 1],
|
||||||
|
[1429594458, "13", "nocode", 1],
|
||||||
|
[1429594529, "13", "js", 1],
|
||||||
|
[1429595408, "10", "net-re", 4],
|
||||||
|
[1429595408, "13", "net-re", 1],
|
||||||
|
[1429595431, "13", "codebreaking", 1],
|
||||||
|
[1429595560, "13", "nocode", 2],
|
||||||
|
[1429595692, "10", "codebreaking", 6],
|
||||||
|
[1429596133, "14", "js", 1],
|
||||||
|
[1429597686, "13", "codebreaking", 2],
|
||||||
|
[1429597777, "13", "codebreaking", 4],
|
||||||
|
[1429598384, "9", "net-re", 3],
|
||||||
|
[1429598816, "14", "js", 2],
|
||||||
|
[1429598849, "13", "js", 2],
|
||||||
|
[1429599165, "15", "nocode", 1],
|
||||||
|
[1429599169, "11", "net-re", 5],
|
||||||
|
[1429599246, "15", "nocode", 2],
|
||||||
|
[1429599491, "11", "net-re", 6],
|
||||||
|
[1429599556, "14", "codebreaking", 1],
|
||||||
|
[1429599570, "15", "nocode", 4],
|
||||||
|
[1429599617, "15", "nocode", 3],
|
||||||
|
[1429599687, "11", "net-re", 7],
|
||||||
|
[1429599708, "15", "nocode", 10],
|
||||||
|
[1429600044, "11", "net-re", 10],
|
||||||
|
[1429600115, "11", "bonus", 10],
|
||||||
|
[1429600186, "14", "codebreaking", 2],
|
||||||
|
[1429600409, "11", "net-re", 20],
|
||||||
|
[1429600635, "11", "net-re", 30],
|
||||||
|
[1429600960, "15", "js", 1],
|
||||||
|
[1429593857, "14", "codebreaking", 4],
|
||||||
|
[1429594041, "16", "nocode", 1],
|
||||||
|
[1429594056, "16", "nocode", 2],
|
||||||
|
[1429594100, "16", "nocode", 3],
|
||||||
|
[1429594113, "16", "nocode", 4],
|
||||||
|
[1429594126, "14", "net-re", 1],
|
||||||
|
[1429594238, "14", "net-re", 2],
|
||||||
|
[1429594326, "9", "codebreaking", 2],
|
||||||
|
[1429594335, "1", "net-re", 10],
|
||||||
|
[1429594387, "16", "nocode", 10],
|
||||||
|
[1429594721, "14", "nocode", 1],
|
||||||
|
[1429594776, "14", "nocode", 2],
|
||||||
|
[1429594989, "14", "nocode", 3],
|
||||||
|
[1429595014, "14", "nocode", 4],
|
||||||
|
[1429595105, "14", "nocode", 10],
|
||||||
|
[1429595464, "15", "js", 2],
|
||||||
|
[1429596277, "6", "codebreaking", 1],
|
||||||
|
[1429596292, "14", "nocode", 20],
|
||||||
|
[1429596364, "17", "nocode", 1],
|
||||||
|
[1429596400, "17", "js", 1],
|
||||||
|
[1429596776, "15", "net-re", 1],
|
||||||
|
[1429597027, "15", "net-re", 2],
|
||||||
|
[1429597044, "1", "net-re", 20],
|
||||||
|
[1429597067, "16", "net-re", 20],
|
||||||
|
[1429597214, "16", "js", 1],
|
||||||
|
[1429597234, "16", "codebreaking", 1],
|
||||||
|
[1429597287, "17", "nocode", 2],
|
||||||
|
[1429597423, "16", "codebreaking", 2],
|
||||||
|
[1429597488, "17", "nocode", 3],
|
||||||
|
[1429597627, "17", "nocode", 4],
|
||||||
|
[1429597659, "16", "codebreaking", 4],
|
||||||
|
[1429597676, "1", "codebreaking", 4],
|
||||||
|
[1429597757, "1", "codebreaking", 5],
|
||||||
|
[1429597772, "16", "codebreaking", 5],
|
||||||
|
[1429597844, "17", "nocode", 10],
|
||||||
|
[1429597882, "1", "codebreaking", 6],
|
||||||
|
[1429597895, "16", "codebreaking", 6],
|
||||||
|
[1429598048, "1", "codebreaking", 7],
|
||||||
|
[1429598063, "16", "codebreaking", 7],
|
||||||
|
[1429598111, "1", "codebreaking", 8],
|
||||||
|
[1429598123, "16", "codebreaking", 8],
|
||||||
|
[1429598176, "16", "codebreaking", 20],
|
||||||
|
[1429598188, "1", "codebreaking", 20],
|
||||||
|
[1429598227, "14", "net-re", 4],
|
||||||
|
[1429599449, "1", "nocode", 20],
|
||||||
|
[1429599466, "16", "nocode", 20],
|
||||||
|
[1429600544, "17", "nocode", 20],
|
||||||
|
[1429600741, "14", "net-re", 5],
|
||||||
|
[1429600892, "14", "net-re", 6],
|
||||||
|
[1429601090, "11", "net-re", 100],
|
||||||
|
[1429601316, "11", "net-re", 200],
|
||||||
|
[1429601600, "11", "net-re", 250],
|
||||||
|
[1429601700, "14", "net-re", 7],
|
||||||
|
[1429601907, "11", "net-re", 300],
|
||||||
|
[1429602270, "11", "js", 3],
|
||||||
|
[1429602581, "14", "net-re", 10],
|
||||||
|
[1429603096, "18", "codebreaking", 1],
|
||||||
|
[1429603148, "18", "nocode", 1],
|
||||||
|
[1429603173, "18", "nocode", 2],
|
||||||
|
[1429603273, "18", "js", 1],
|
||||||
|
[1429603277, "18", "nocode", 3],
|
||||||
|
[1429603325, "18", "nocode", 4],
|
||||||
|
[1429603478, "9", "codebreaking", 4],
|
||||||
|
[1429603509, "18", "nocode", 10],
|
||||||
|
[1429603853, "9", "codebreaking", 5],
|
||||||
|
[1429603979, "18", "codebreaking", 2],
|
||||||
|
[1429604211, "18", "codebreaking", 4],
|
||||||
|
[1429604515, "9", "codebreaking", 6],
|
||||||
|
[1429604535, "18", "codebreaking", 5],
|
||||||
|
[1429605613, "15", "net-re", 4],
|
||||||
|
[1429605764, "13", "nocode", 4],
|
||||||
|
[1429605779, "3", "nocode", 30],
|
||||||
|
[1429606493, "13", "nocode", 10],
|
||||||
|
[1429606596, "3", "nocode", 80],
|
||||||
|
[1429606712, "11", "nocode", 50],
|
||||||
|
[1429606728, "3", "nocode", 90],
|
||||||
|
[1429606762, "11", "nocode", 80],
|
||||||
|
[1429606877, "16", "nocode", 50],
|
||||||
|
[1429606962, "16", "nocode", 30],
|
||||||
|
[1429607144, "4", "nocode", 50],
|
||||||
|
[1429607428, "19", "nocode", 1],
|
||||||
|
[1429607453, "19", "nocode", 2],
|
||||||
|
[1429607877, "11", "nocode", 90],
|
||||||
|
[1429607943, "19", "nocode", 3],
|
||||||
|
[1429607997, "19", "nocode", 4],
|
||||||
|
[1429608039, "16", "nocode", 80],
|
||||||
|
[1429608054, "19", "js", 1],
|
||||||
|
[1429608083, "19", "nocode", 10],
|
||||||
|
[1429608226, "16", "nocode", 90],
|
||||||
|
[1429608430, "13", "nocode", 3],
|
||||||
|
[1429608475, "3", "codebreaking", 7],
|
||||||
|
[1429608514, "20", "nocode", 1],
|
||||||
|
[1429608537, "20", "nocode", 2],
|
||||||
|
[1429608614, "20", "nocode", 4],
|
||||||
|
[1429608689, "19", "nocode", 20],
|
||||||
|
[1429608707, "20", "js", 1],
|
||||||
|
[1429608843, "3", "net-re", 250],
|
||||||
|
[1429609088, "20", "net-re", 1],
|
||||||
|
[1429609128, "20", "codebreaking", 1],
|
||||||
|
[1429609272, "19", "codebreaking", 1],
|
||||||
|
[1429609341, "20", "nocode", 10],
|
||||||
|
[1429609586, "19", "net-re", 1],
|
||||||
|
[1429609876, "19", "nocode", 30],
|
||||||
|
[1429609981, "16", "net-re", 2],
|
||||||
|
[1429610335, "16", "net-re", 1],
|
||||||
|
[1429610755, "20", "js", 2],
|
||||||
|
[1429610777, "19", "js", 2],
|
||||||
|
[1429610922, "1", "js", 2],
|
||||||
|
[1429611472, "16", "js", 2],
|
||||||
|
[1429611695, "3", "net-re", 400],
|
||||||
|
[1429612382, "13", "net-re", 250],
|
||||||
|
[1429612600, "20", "codebreaking", 7],
|
||||||
|
[1429612771, "16", "nocode", 100],
|
||||||
|
[1429612773, "1", "nocode", 100],
|
||||||
|
[1429612818, "16", "bonus", 10],
|
||||||
|
[1429612825, "1", "bonus", 10],
|
||||||
|
[1429613065, "1", "net-re", 200],
|
||||||
|
[1429613837, "10", "net-re", 200],
|
||||||
|
[1429613970, "1", "net-re", 250],
|
||||||
|
[1429614300, "13", "net-re", 10],
|
||||||
|
[1429614363, "16", "net-re", 3],
|
||||||
|
[1429614610, "10", "codebreaking", 20],
|
||||||
|
[1429614873, "13", "net-re", 20],
|
||||||
|
[1429614939, "16", "net-re", 4],
|
||||||
|
[1429615440, "16", "net-re", 5],
|
||||||
|
[1429594321, "10", "net-re", 100],
|
||||||
|
[1429593963, "10", "net-re", 250],
|
||||||
|
[1429598332, "16", "net-re", 6],
|
||||||
|
[1429598544, "21", "nocode", 1],
|
||||||
|
[1429598631, "21", "nocode", 4],
|
||||||
|
[1429598680, "21", "nocode", 10],
|
||||||
|
[1429598834, "21", "nocode", 20],
|
||||||
|
[1429598884, "21", "nocode", 30],
|
||||||
|
[1429598978, "21", "codebreaking", 1],
|
||||||
|
[1429598994, "11", "codebreaking", 6],
|
||||||
|
[1429599012, "11", "codebreaking", 7],
|
||||||
|
[1429599029, "11", "codebreaking", 8],
|
||||||
|
[1429599154, "21", "codebreaking", 2],
|
||||||
|
[1429599164, "11", "net-re", 400],
|
||||||
|
[1429599813, "21", "codebreaking", 4],
|
||||||
|
[1429600483, "11", "js", 10],
|
||||||
|
[1429600712, "21", "codebreaking", 5],
|
||||||
|
[1429601019, "21", "js", 1],
|
||||||
|
[1429601089, "21", "js", 2],
|
||||||
|
[1429601324, "21", "nocode", 2],
|
||||||
|
[1429601491, "21", "nocode", 3],
|
||||||
|
[1429601511, "22", "codebreaking", 1],
|
||||||
|
[1429602117, "11", "nocode", 100],
|
||||||
|
[1429602389, "1", "net-re", 100],
|
||||||
|
[1429602462, "23", "nocode", 200],
|
||||||
|
[1429602515, "23", "nocode", 1],
|
||||||
|
[1429602525, "23", "nocode", 2],
|
||||||
|
[1429602540, "23", "nocode", 3],
|
||||||
|
[1429602550, "23", "nocode", 4],
|
||||||
|
[1429602582, "23", "nocode", 10],
|
||||||
|
[1429602685, "23", "codebreaking", 1],
|
||||||
|
[1429602737, "21", "nocode", 200],
|
||||||
|
[1429602747, "23", "codebreaking", 2],
|
||||||
|
[1429602767, "23", "codebreaking", 4],
|
||||||
|
[1429602824, "11", "net-re", 700],
|
||||||
|
[1429602900, "16", "net-re", 100],
|
||||||
|
[1429602990, "16", "net-re", 200],
|
||||||
|
[1429603039, "22", "nocode", 200],
|
||||||
|
[1429603308, "23", "codebreaking", 5],
|
||||||
|
[1429603374, "23", "js", 1],
|
||||||
|
[1429603607, "16", "net-re", 250],
|
||||||
|
[1429603931, "11", "net-re", 800]
|
||||||
|
],
|
||||||
|
"teams": {
|
||||||
|
"1": "INNSOC",
|
||||||
|
"2": "Lost",
|
||||||
|
"3": "TeamSoloTF",
|
||||||
|
"4": "BEnergy",
|
||||||
|
"5": "Dreamweaver",
|
||||||
|
"6": "Ghirahim",
|
||||||
|
"7": "Awesome ",
|
||||||
|
"8": "sn3",
|
||||||
|
"9": "CybrK0pS",
|
||||||
|
"10": "dio1911",
|
||||||
|
"11": "InvaderZed",
|
||||||
|
"12": "Energy.gov",
|
||||||
|
"13": "PPPL",
|
||||||
|
"14": "OR_Ninjas",
|
||||||
|
"15": "Placebo",
|
||||||
|
"16": "WiFI-Ninja",
|
||||||
|
"17": "OST-1",
|
||||||
|
"18": "WestCoast3",
|
||||||
|
"19": "Dathcha",
|
||||||
|
"20": "Last Mohigan",
|
||||||
|
"21": "cloud",
|
||||||
|
"22": "Stephen",
|
||||||
|
"23": "Prima"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Scoreboard</title>
|
||||||
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
|
<style>
|
||||||
|
#info {
|
||||||
|
max-width: 20em;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.8;
|
||||||
|
bottom: 1em;
|
||||||
|
right: 10em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="d3.js" async></script>
|
||||||
|
<script src="scoreboard-llnl.js" async></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var type = "original";
|
||||||
|
var interval = 60000;
|
||||||
|
|
||||||
|
function preinit()
|
||||||
|
{
|
||||||
|
type = "total";
|
||||||
|
interval = 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateType(newType)
|
||||||
|
{
|
||||||
|
type = newType;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var sb = document.getElementById("scoreboard");
|
||||||
|
scoreboard(sb, true, type, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", preinit);
|
||||||
|
window.addEventListener("load", init);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Scoreboard</h1>
|
||||||
|
<div id="scoreboard"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Scoreboard</title>
|
||||||
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
|
<style>
|
||||||
|
#info {
|
||||||
|
max-width: 20em;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.8;
|
||||||
|
bottom: 1em;
|
||||||
|
right: 10em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="d3.js" async></script>
|
||||||
|
<script src="scoreboard-llnl.js" async></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var type = "original";
|
||||||
|
var interval = 60000;
|
||||||
|
|
||||||
|
function preinit()
|
||||||
|
{
|
||||||
|
type = "time";
|
||||||
|
interval = 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateType(newType)
|
||||||
|
{
|
||||||
|
type = newType;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var sb = document.getElementById("scoreboard");
|
||||||
|
scoreboard(sb, true, type, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", preinit);
|
||||||
|
window.addEventListener("load", init);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Scoreboard</h1>
|
||||||
|
<div id="scoreboard"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -0,0 +1,531 @@
|
||||||
|
function loadJSON(url, callback) {
|
||||||
|
function loaded(e) {
|
||||||
|
callback(e.target.response);
|
||||||
|
}
|
||||||
|
var xhr = new XMLHttpRequest()
|
||||||
|
xhr.onload = loaded;
|
||||||
|
xhr.open("GET", url, true);
|
||||||
|
xhr.responseType = "json";
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toObject(arr) {
|
||||||
|
var rv = {};
|
||||||
|
for (var i = 0; i < arr.length; ++i)
|
||||||
|
if (arr[i] !== undefined) rv[i] = arr[i];
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateInterval;
|
||||||
|
|
||||||
|
function scoreboard(element, continuous, mode, interval) {
|
||||||
|
if(updateInterval)
|
||||||
|
{
|
||||||
|
clearInterval(updateInterval);
|
||||||
|
}
|
||||||
|
function update(state) {
|
||||||
|
console.log("Updating");
|
||||||
|
var teamnames = state["teams"];
|
||||||
|
var pointslog = state["points"];
|
||||||
|
var highscore = {};
|
||||||
|
var teams = {};
|
||||||
|
|
||||||
|
function pointsCompare(a, b) {
|
||||||
|
return a[0] - b[0];
|
||||||
|
}
|
||||||
|
pointslog.sort(pointsCompare);
|
||||||
|
var minTime = pointslog[0][0];
|
||||||
|
var maxTime = pointslog[pointslog.length - 1][0];
|
||||||
|
|
||||||
|
var allQuestions = {};
|
||||||
|
|
||||||
|
for (var i in pointslog)
|
||||||
|
{
|
||||||
|
var entry = pointslog[i];
|
||||||
|
var timestamp = entry[0];
|
||||||
|
var teamhash = entry[1];
|
||||||
|
var category = entry[2];
|
||||||
|
var points = entry[3];
|
||||||
|
|
||||||
|
var catPoints = {};
|
||||||
|
if(category in allQuestions)
|
||||||
|
{
|
||||||
|
catPoints = allQuestions[category];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
catPoints["total"] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(points in catPoints))
|
||||||
|
{
|
||||||
|
catPoints[points] = 1;
|
||||||
|
catPoints["total"] = catPoints["total"] + points;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
catPoints[points] = catPoints[points] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
allQuestions[category] = catPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dole out points
|
||||||
|
for (var i in pointslog) {
|
||||||
|
var entry = pointslog[i];
|
||||||
|
var timestamp = entry[0];
|
||||||
|
var teamhash = entry[1];
|
||||||
|
var category = entry[2];
|
||||||
|
var points = entry[3];
|
||||||
|
|
||||||
|
var team = teams[teamhash] || {__hash__: teamhash};
|
||||||
|
|
||||||
|
// Add points to team's points for that category
|
||||||
|
team[category] = (team[category] || 0) + points;
|
||||||
|
|
||||||
|
// Record highest score in a category
|
||||||
|
highscore[category] = Math.max(highscore[category] || 0, team[category]);
|
||||||
|
|
||||||
|
teams[teamhash] = team;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by team score
|
||||||
|
function teamScore(t) {
|
||||||
|
var score = 0;
|
||||||
|
|
||||||
|
for (var category in highscore) {
|
||||||
|
score += (t[category] || 0) / highscore[category];
|
||||||
|
}
|
||||||
|
// XXX: This function really shouldn't have side effects.
|
||||||
|
t.__score__ = score;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
function pointScore(points, category)
|
||||||
|
{
|
||||||
|
return points / highscore[category]
|
||||||
|
}
|
||||||
|
function teamCompare(a, b) {
|
||||||
|
return teamScore(a) - teamScore(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
var winners = [];
|
||||||
|
for (var i in teams) {
|
||||||
|
winners.push(teams[i]);
|
||||||
|
}
|
||||||
|
if (winners.length == 0) {
|
||||||
|
// No teams!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
winners.sort(teamCompare);
|
||||||
|
winners.reverse();
|
||||||
|
|
||||||
|
// Clear out the element we're about to populate
|
||||||
|
while (element.lastChild) {
|
||||||
|
element.removeChild(element.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate!
|
||||||
|
var topActualScore = winners[0].__score__;
|
||||||
|
|
||||||
|
|
||||||
|
if(mode == "time")
|
||||||
|
{
|
||||||
|
var colorScale = d3.schemeCategory10;
|
||||||
|
|
||||||
|
var teamLines = {};
|
||||||
|
var reverseTeam = {};
|
||||||
|
for(var i in pointslog)
|
||||||
|
{
|
||||||
|
var entry = pointslog[i];
|
||||||
|
var timestamp = entry[0];
|
||||||
|
var teamhash = entry[1];
|
||||||
|
var category = entry[2];
|
||||||
|
var points = entry[3];
|
||||||
|
var teamname = teamnames[teamhash];
|
||||||
|
reverseTeam[teamname] = teamhash;
|
||||||
|
points = pointScore(points, category);
|
||||||
|
|
||||||
|
if(!(teamname in teamLines))
|
||||||
|
{
|
||||||
|
var teamHistory = [[timestamp, points, category, entry[3], [minTime, 0, category, 0]]];
|
||||||
|
teamLines[teamname] = teamHistory;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var teamHistory = teamLines[teamname];
|
||||||
|
teamHistory.push([timestamp, points + teamHistory[teamHistory.length - 1][1], category, entry[3], teamHistory[teamHistory.length - 1]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(teamLines);
|
||||||
|
|
||||||
|
var graph = document.createElement("svg");
|
||||||
|
graph.id = "graph";
|
||||||
|
graph.style.width="90%";
|
||||||
|
graph.style.height="40em";
|
||||||
|
graph.style.backgroundColor = "white";
|
||||||
|
graph.style.display = "table";
|
||||||
|
var holdingDiv = document.createElement("div");
|
||||||
|
holdingDiv.align="center";
|
||||||
|
holdingDiv.id="holding";
|
||||||
|
element.appendChild(holdingDiv);
|
||||||
|
holdingDiv.appendChild(graph);
|
||||||
|
|
||||||
|
var margins = 40;
|
||||||
|
|
||||||
|
var width = graph.offsetWidth;
|
||||||
|
var height = graph.offsetHeight;
|
||||||
|
|
||||||
|
//var xScale = d3.scaleLinear().range([minTime, maxTime]);
|
||||||
|
//var yScale = d3.scaleLinear().range([0, topActualScore]);
|
||||||
|
var originTime = (maxTime - minTime) / 60;
|
||||||
|
var xScale = d3.scaleLinear().range([margins, width - margins]);
|
||||||
|
xScale.domain([0, originTime]);
|
||||||
|
var yScale = d3.scaleLinear().range([height - margins, margins]);
|
||||||
|
yScale.domain([0, topActualScore]);
|
||||||
|
|
||||||
|
graph = d3.select("#graph");
|
||||||
|
graph.remove();
|
||||||
|
graph = d3.select("#holding").append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height);
|
||||||
|
//.attr("style", "background: white");
|
||||||
|
|
||||||
|
|
||||||
|
//graph.append("g")
|
||||||
|
// .attr("transform", "translate(" + margins + ", 0)")
|
||||||
|
// .call(d3.axisLeft(yScale))
|
||||||
|
// .style("stroke", "white");;
|
||||||
|
|
||||||
|
var maxNumEntry = 10;
|
||||||
|
//var curEntry = 0;
|
||||||
|
var winningTeams = [];
|
||||||
|
for(entry in winners)
|
||||||
|
{
|
||||||
|
var curEntry = entry;
|
||||||
|
if(curEntry >= maxNumEntry)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entry = teamnames[winners[entry].__hash__];
|
||||||
|
winningTeams.push(entry);
|
||||||
|
//console.log(curEntry);
|
||||||
|
//console.log(entry);
|
||||||
|
|
||||||
|
//var isTop = false;
|
||||||
|
//for(var x=0; x < maxNumEntry; x++)
|
||||||
|
//{
|
||||||
|
// var teamhash = reverseTeam[entry];
|
||||||
|
// if(winners[x].__hash__ == teamhash)
|
||||||
|
// {
|
||||||
|
// curEntry = x;
|
||||||
|
// isTop = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//if(!isTop)
|
||||||
|
//{
|
||||||
|
// continue;
|
||||||
|
//}
|
||||||
|
|
||||||
|
var curTeam = teamLines[entry];
|
||||||
|
var lastEntry = curTeam[curTeam.length - 1];
|
||||||
|
//curTeam.append()
|
||||||
|
curTeam.push([maxTime, lastEntry[1], lastEntry[2], lastEntry[3], lastEntry]);
|
||||||
|
var curLayer = graph.append("g");
|
||||||
|
curLayer.selectAll("line")
|
||||||
|
.data(curTeam)
|
||||||
|
.enter()
|
||||||
|
.append("line")
|
||||||
|
.style("stroke", colorScale[curEntry])
|
||||||
|
.attr("stroke-width", 4)
|
||||||
|
.attr("class", "team_" + entry)
|
||||||
|
.style("z-index", maxNumEntry - curEntry)
|
||||||
|
.attr("x1",
|
||||||
|
function(d)
|
||||||
|
{
|
||||||
|
return xScale((d[4][0] - minTime) / 60);
|
||||||
|
})
|
||||||
|
.attr("x2",
|
||||||
|
function(d)
|
||||||
|
{
|
||||||
|
return xScale((d[0] - minTime) / 60);
|
||||||
|
})
|
||||||
|
.attr("y1",
|
||||||
|
function(d)
|
||||||
|
{
|
||||||
|
return yScale(d[4][1]);
|
||||||
|
})
|
||||||
|
.attr("y2",
|
||||||
|
function(d)
|
||||||
|
{
|
||||||
|
return yScale(d[1]);
|
||||||
|
})
|
||||||
|
.on("mouseover", handleMouseover)
|
||||||
|
.on("mouseout", handleMouseout);
|
||||||
|
|
||||||
|
curLayer.selectAll("circle")
|
||||||
|
.data(curTeam)
|
||||||
|
.enter()
|
||||||
|
.append("circle")
|
||||||
|
.style("fill", colorScale[curEntry])
|
||||||
|
.style("z-index", maxNumEntry - curEntry)
|
||||||
|
.attr("class", "team_" + entry)
|
||||||
|
.attr("r", 5)
|
||||||
|
.attr("cx",
|
||||||
|
function(d)
|
||||||
|
{
|
||||||
|
return xScale((d[0] - minTime) / 60);
|
||||||
|
})
|
||||||
|
.attr("cy",
|
||||||
|
function(d)
|
||||||
|
{
|
||||||
|
return yScale(d[1]);
|
||||||
|
})
|
||||||
|
.on("mouseover", handleMouseoverCircle)
|
||||||
|
.on("mouseout", handleMouseoutCircle);
|
||||||
|
|
||||||
|
curEntry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var axisG = graph.append("g");
|
||||||
|
axisG
|
||||||
|
.attr("transform", "translate(0," + (height - margins) + ")")
|
||||||
|
.call(d3.axisBottom(xScale));
|
||||||
|
//.style("stroke", "white");
|
||||||
|
axisG.selectAll("path").style("stroke", "white");
|
||||||
|
axisG.selectAll("line").style("stroke", "white");
|
||||||
|
axisG.selectAll("text").style("fill", "white");
|
||||||
|
|
||||||
|
graph.append("text")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("transform", "translate(" + (width / 2) + ", " + (height - margins / 8) + ")")
|
||||||
|
.style("fill", "white")
|
||||||
|
.text("Time (minutes)");
|
||||||
|
|
||||||
|
var legend = graph.append("g");
|
||||||
|
var legendRowHeight = 40;
|
||||||
|
legend.selectAll("rect")
|
||||||
|
.data(winningTeams)
|
||||||
|
.enter()
|
||||||
|
.append("rect")
|
||||||
|
.attr("class", function(d){ return "team_" + d; })
|
||||||
|
.attr("fill", function(d, i){ return colorScale[i]; })
|
||||||
|
.style("z-index", function(d, i){ return i; })
|
||||||
|
.attr("x", margins)
|
||||||
|
.attr("y", function(d, i){ return margins + legendRowHeight * i; })
|
||||||
|
.attr("height", legendRowHeight)
|
||||||
|
.attr("width", 150)
|
||||||
|
.on("mouseover", handleMouseoverLegend)
|
||||||
|
.on("mouseout", handleMouseoutLegend);
|
||||||
|
|
||||||
|
legend.selectAll("text")
|
||||||
|
.data(winningTeams)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
//.attr("class", function(d){ return "team_" + d; })
|
||||||
|
.attr("fill", "black")
|
||||||
|
.style("z-index", function(d, i){ return i; })
|
||||||
|
.attr("dx", margins)
|
||||||
|
.attr("dy", function(d, i){ return margins + legendRowHeight * (i + .5); })
|
||||||
|
.text(function(d){ return d; })
|
||||||
|
.attr("dominant-baseline", "central")
|
||||||
|
.style("pointer-events", "none");
|
||||||
|
|
||||||
|
|
||||||
|
function handleMouseover(d, i)
|
||||||
|
{
|
||||||
|
d3.select("body").selectAll(".tooltip").remove();
|
||||||
|
var curClass = d3.select(this).attr("class");
|
||||||
|
d3.select("body").selectAll("." + curClass)
|
||||||
|
.style("stroke", "white")
|
||||||
|
.style("fill", "white");
|
||||||
|
d3.select("body").selectAll("text")
|
||||||
|
.style("stroke-width", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseout(d, i)
|
||||||
|
{
|
||||||
|
d3.select("body").selectAll(".tooltip").remove();
|
||||||
|
var curClass = d3.select(this).attr("class");
|
||||||
|
var zIndex = d3.select(this).style("z-index");
|
||||||
|
d3.select("body").selectAll("." + curClass)
|
||||||
|
.style("stroke", colorScale[maxNumEntry - zIndex])
|
||||||
|
.style("fill", colorScale[maxNumEntry - zIndex]);
|
||||||
|
d3.select("body").selectAll("text")
|
||||||
|
.style("stroke-width", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tooltipPadding = 10;
|
||||||
|
function handleMouseoverCircle(d, i)
|
||||||
|
{
|
||||||
|
d3.select("body").selectAll(".tooltip").remove();
|
||||||
|
var curClass = d3.select(this).attr("class");
|
||||||
|
d3.select("body").selectAll("." + curClass)
|
||||||
|
.style("stroke", "white")
|
||||||
|
.style("fill", "white");
|
||||||
|
d3.select("body").selectAll("text")
|
||||||
|
.style("stroke-width", 0);
|
||||||
|
|
||||||
|
graph.append("g").append("text")
|
||||||
|
.attr("class", "tooltip")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.style("fill", "red")
|
||||||
|
.style("stroke-width", -4)
|
||||||
|
.style("stroke", "black")
|
||||||
|
.style("font-weight", "bolder")
|
||||||
|
.style("font-size", "large")
|
||||||
|
.attr("dx",
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
return xScale((d[0] - minTime) / 60);
|
||||||
|
})
|
||||||
|
.attr("dy",
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
return yScale(d[1]) - tooltipPadding;
|
||||||
|
})
|
||||||
|
.text(function(){ return d[2] + " " + d[3]; })
|
||||||
|
.style("pointer-events", "none");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseoutCircle(d, i)
|
||||||
|
{
|
||||||
|
d3.select("body").selectAll(".tooltip").remove();
|
||||||
|
var curClass = d3.select(this).attr("class");
|
||||||
|
var zIndex = d3.select(this).style("z-index");
|
||||||
|
d3.select("body").selectAll("." + curClass)
|
||||||
|
.style("stroke", colorScale[maxNumEntry - zIndex])
|
||||||
|
.style("fill", colorScale[maxNumEntry - zIndex]);
|
||||||
|
d3.select("body").selectAll("text")
|
||||||
|
.style("stroke-width", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseoverLegend(d, i)
|
||||||
|
{
|
||||||
|
d3.select("body").selectAll(".tooltip").remove();
|
||||||
|
var curClass = d3.select(this).attr("class");
|
||||||
|
d3.select("body").selectAll("." + curClass)
|
||||||
|
.style("stroke", "white")
|
||||||
|
.style("fill", "white");
|
||||||
|
d3.select("body").selectAll("text")
|
||||||
|
.style("stroke-width", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseoutLegend(d, i)
|
||||||
|
{
|
||||||
|
d3.select("body").selectAll(".tooltip").remove();
|
||||||
|
var curClass = d3.select(this).attr("class");
|
||||||
|
var zIndex = d3.select(this).style("z-index");
|
||||||
|
d3.select("body").selectAll("." + curClass)
|
||||||
|
.style("stroke", colorScale[zIndex])
|
||||||
|
.style("fill", colorScale[zIndex]);
|
||||||
|
d3.select("body").selectAll("text")
|
||||||
|
.style("stroke-width", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(mode == "original")
|
||||||
|
{
|
||||||
|
// (100 / ncats) * (ncats / topActualScore);
|
||||||
|
var maxWidth = 100 / topActualScore;
|
||||||
|
for (var i in winners) {
|
||||||
|
var team = winners[i];
|
||||||
|
var row = document.createElement("div");
|
||||||
|
var ncat = 0;
|
||||||
|
for (var category in highscore) {
|
||||||
|
var catHigh = highscore[category];
|
||||||
|
var catTeam = team[category] || 0;
|
||||||
|
var catPct = catTeam / catHigh;
|
||||||
|
var width = maxWidth * catPct;
|
||||||
|
|
||||||
|
var bar = document.createElement("span");
|
||||||
|
bar.classList.add("cat" + ncat);
|
||||||
|
bar.style.width = width + "%";
|
||||||
|
bar.textContent = category + ": " + catTeam;
|
||||||
|
bar.title = bar.textContent;
|
||||||
|
|
||||||
|
row.appendChild(bar);
|
||||||
|
ncat += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var te = document.createElement("span");
|
||||||
|
te.classList.add("teamname");
|
||||||
|
te.textContent = teamnames[team.__hash__];
|
||||||
|
row.appendChild(te);
|
||||||
|
|
||||||
|
element.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(mode == "total")
|
||||||
|
{
|
||||||
|
var colorScale = d3.schemeCategory20;
|
||||||
|
|
||||||
|
var numCats = 0;
|
||||||
|
for(entry in allQuestions)
|
||||||
|
{
|
||||||
|
numCats++;
|
||||||
|
}
|
||||||
|
var maxWidth = 100 / (0.0 + numCats);
|
||||||
|
//console.log(maxWidth);
|
||||||
|
|
||||||
|
for (var i in winners) {
|
||||||
|
var team = winners[i];
|
||||||
|
var row = document.createElement("div");
|
||||||
|
var ncat = 0;
|
||||||
|
for (var category in allQuestions) {
|
||||||
|
var catHigh = allQuestions[category];
|
||||||
|
var catTeam = team[category] || 0;
|
||||||
|
var catPct = (0.0 + catTeam) / (0.0 + catHigh["total"]);
|
||||||
|
var width = maxWidth * catPct;
|
||||||
|
var bar = document.createElement("span");
|
||||||
|
|
||||||
|
var numLeft = catHigh["total"] - catTeam;
|
||||||
|
|
||||||
|
//bar.classList.add("cat" + ncat);
|
||||||
|
bar.style.backgroundColor = colorScale[ncat];
|
||||||
|
bar.style.color = "white";
|
||||||
|
bar.style.width = width + "%";
|
||||||
|
bar.textContent = category + ": " + catTeam;
|
||||||
|
bar.title = bar.textContent;
|
||||||
|
|
||||||
|
row.appendChild(bar);
|
||||||
|
|
||||||
|
ncat++;
|
||||||
|
|
||||||
|
width = maxWidth * (1 - catPct);
|
||||||
|
if(width > 0)
|
||||||
|
{
|
||||||
|
var noBar = document.createElement("span");
|
||||||
|
//noBar.classList.add("cat" + ncat);
|
||||||
|
noBar.style.backgroundColor = colorScale[ncat];
|
||||||
|
noBar.style.width = width + "%";
|
||||||
|
noBar.textContent = numLeft;
|
||||||
|
noBar.title = bar.textContent;
|
||||||
|
|
||||||
|
row.appendChild(noBar);
|
||||||
|
}
|
||||||
|
ncat += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var te = document.createElement("span");
|
||||||
|
te.classList.add("teamname");
|
||||||
|
te.textContent = teamnames[team.__hash__];
|
||||||
|
row.appendChild(te);
|
||||||
|
|
||||||
|
element.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function once() {
|
||||||
|
loadJSON("points.json", update);
|
||||||
|
}
|
||||||
|
if (continuous) {
|
||||||
|
updateInterval = setInterval(once, interval);
|
||||||
|
}
|
||||||
|
once();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Scoreboard</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; height: 100%; overflow: hidden;">
|
||||||
|
<div style="position:absolute; left: 0; right: 0; bottom: 0; top: 0px;">
|
||||||
|
<iframe id="content" src="scoreboard.html" style="border:none;" height="100%" width="100%"></iframe>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
var cycle = ["scoreboard.html", "scoreboard-llnl-all.html", "scoreboard-llnl-timeline.html"];
|
||||||
|
var current = 0;
|
||||||
|
var timeout = 25000;
|
||||||
|
var curTimeout;
|
||||||
|
curTimeout = setInterval(update, timeout);
|
||||||
|
function update()
|
||||||
|
{
|
||||||
|
if(curTimeout)
|
||||||
|
{
|
||||||
|
clearInterval(curTimeout);
|
||||||
|
}
|
||||||
|
current++;
|
||||||
|
if(current >= cycle.length)
|
||||||
|
{
|
||||||
|
current = 0;
|
||||||
|
}
|
||||||
|
//console.log(cycle[current]);
|
||||||
|
document.getElementById("content").src = cycle[current];
|
||||||
|
curTimeout = setInterval(update, timeout);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -3,75 +3,18 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Scoreboard</title>
|
<title>Scoreboard</title>
|
||||||
<link rel="stylesheet" href="style.css" type="text/css">
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
<style>
|
|
||||||
#info {
|
|
||||||
max-width: 20em;
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0.8;
|
|
||||||
bottom: 1em;
|
|
||||||
right: 10em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="d3.js" async></script>
|
|
||||||
<script src="scoreboard.js" async></script>
|
<script src="scoreboard.js" async></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var type = "original";
|
|
||||||
var interval = 60000;
|
|
||||||
|
|
||||||
function preinit()
|
|
||||||
{
|
|
||||||
var url = location.href;
|
|
||||||
url = new URL(url);
|
|
||||||
type = url.searchParams.get("type");
|
|
||||||
interval = url.searchParams.get("interval");
|
|
||||||
if(!type)
|
|
||||||
{
|
|
||||||
type = "original";
|
|
||||||
}
|
|
||||||
if(!interval)
|
|
||||||
{
|
|
||||||
interval = 60000;
|
|
||||||
}
|
|
||||||
if(type == "original")
|
|
||||||
{
|
|
||||||
document.getElementById("originalButton").checked = true;
|
|
||||||
}
|
|
||||||
else if(type == "total")
|
|
||||||
{
|
|
||||||
document.getElementById("totalButton").checked = true;
|
|
||||||
}
|
|
||||||
else if(type == "time")
|
|
||||||
{
|
|
||||||
document.getElementById("timelineButton").checked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateType(newType)
|
|
||||||
{
|
|
||||||
type = newType;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var sb = document.getElementById("scoreboard");
|
var sb = document.getElementById("scoreboard");
|
||||||
scoreboard(sb, true, type, interval);
|
scoreboard(sb, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("load", preinit);
|
|
||||||
window.addEventListener("load", init);
|
window.addEventListener("load", init);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Cyber Fire</h1>
|
<h1>Scoreboard</h1>
|
||||||
<h2>
|
|
||||||
Scoreboard Type:
|
|
||||||
<input type="radio" name="type" id="originalButton" value="original" onclick="updateType('original');"> Scored Points
|
|
||||||
<input type="radio" name="type" id="totalButton" value="total" onclick="updateType('total');"> All Points
|
|
||||||
<input type="radio" name="type" id="timelineButton" value="timeline" onclick="updateType('time');"> Timeline
|
|
||||||
</h2>
|
|
||||||
<div id="scoreboard"></div>
|
<div id="scoreboard"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,531 +1,164 @@
|
||||||
function loadJSON(url, callback) {
|
function loadJSON(url, callback) {
|
||||||
function loaded(e) {
|
function loaded(e) {
|
||||||
callback(e.target.response);
|
callback(e.target.response);
|
||||||
}
|
}
|
||||||
var xhr = new XMLHttpRequest()
|
var xhr = new XMLHttpRequest()
|
||||||
xhr.onload = loaded;
|
xhr.onload = loaded;
|
||||||
xhr.open("GET", url, true);
|
xhr.open("GET", url, true);
|
||||||
xhr.responseType = "json";
|
xhr.responseType = "json";
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toObject(arr) {
|
function scoreboardHistoryPush(pointslog) {
|
||||||
var rv = {};
|
let pointsHistory = JSON.parse(localStorage.getItem("pointsHistory")) || [];
|
||||||
for (var i = 0; i < arr.length; ++i)
|
if (pointsHistory.length >= 20) {
|
||||||
if (arr[i] !== undefined) rv[i] = arr[i];
|
pointsHistory.shift();
|
||||||
return rv;
|
}
|
||||||
|
pointsHistory.push(pointslog);
|
||||||
|
localStorage.setItem("pointsHistory", JSON.stringify(pointsHistory));
|
||||||
}
|
}
|
||||||
|
|
||||||
var updateInterval;
|
function scoreboard(element, continuous) {
|
||||||
|
function update(state) {
|
||||||
|
let teamNames = state["teams"];
|
||||||
|
let pointsLog = state["points"];
|
||||||
|
|
||||||
function scoreboard(element, continuous, mode, interval) {
|
// Establish scores, calculate category maximums
|
||||||
if(updateInterval)
|
let categories = {};
|
||||||
{
|
let maxPointsByCategory = {};
|
||||||
clearInterval(updateInterval);
|
let totalPointsByTeamByCategory = {};
|
||||||
}
|
for (let entry of pointsLog) {
|
||||||
function update(state) {
|
let entryTimeStamp = entry[0];
|
||||||
console.log("Updating");
|
let entryTeamHash = entry[1];
|
||||||
var teamnames = state["teams"];
|
let entryCategory = entry[2];
|
||||||
var pointslog = state["points"];
|
let entryPoints = entry[3];
|
||||||
var highscore = {};
|
|
||||||
var teams = {};
|
|
||||||
|
|
||||||
function pointsCompare(a, b) {
|
// Populate list of all categories
|
||||||
return a[0] - b[0];
|
categories[entryCategory] = entryCategory;
|
||||||
|
|
||||||
|
// Add points to team's points for that category
|
||||||
|
let points = totalPointsByTeamByCategory[entryTeamHash] || {};
|
||||||
|
let categoryPoints = points[entryCategory] || 0;
|
||||||
|
categoryPoints += entryPoints;
|
||||||
|
points[entryCategory] = categoryPoints;
|
||||||
|
totalPointsByTeamByCategory[entryTeamHash] = points;
|
||||||
|
|
||||||
|
// Calculate maximum points scored in each category
|
||||||
|
let m = maxPointsByCategory[entryCategory] || 0;
|
||||||
|
maxPointsByCategory[entryCategory] = Math.max(m, categoryPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate overall scores
|
||||||
|
let overallScore = {};
|
||||||
|
let orderedOverallScores = [];
|
||||||
|
for (let teamHash in teamNames) {
|
||||||
|
var score = 0;
|
||||||
|
for (let cat in categories) {
|
||||||
|
var catPoints = totalPointsByTeamByCategory[teamHash][cat] || 0;
|
||||||
|
if (catPoints > 0) {
|
||||||
|
score += catPoints / maxPointsByCategory[cat];
|
||||||
}
|
}
|
||||||
pointslog.sort(pointsCompare);
|
}
|
||||||
var minTime = pointslog[0][0];
|
overallScore[teamHash] = score;
|
||||||
var maxTime = pointslog[pointslog.length - 1][0];
|
orderedOverallScores.push([score, teamHash]);
|
||||||
|
}
|
||||||
|
orderedOverallScores.sort();
|
||||||
|
orderedOverallScores.reverse();
|
||||||
|
|
||||||
var allQuestions = {};
|
// Clear out the element we're about to populate
|
||||||
|
while (element.lastChild) {
|
||||||
for (var i in pointslog)
|
element.removeChild(element.lastChild);
|
||||||
{
|
|
||||||
var entry = pointslog[i];
|
|
||||||
var timestamp = entry[0];
|
|
||||||
var teamhash = entry[1];
|
|
||||||
var category = entry[2];
|
|
||||||
var points = entry[3];
|
|
||||||
|
|
||||||
var catPoints = {};
|
|
||||||
if(category in allQuestions)
|
|
||||||
{
|
|
||||||
catPoints = allQuestions[category];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
catPoints["total"] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!(points in catPoints))
|
|
||||||
{
|
|
||||||
catPoints[points] = 1;
|
|
||||||
catPoints["total"] = catPoints["total"] + points;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
catPoints[points] = catPoints[points] + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
allQuestions[category] = catPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dole out points
|
|
||||||
for (var i in pointslog) {
|
|
||||||
var entry = pointslog[i];
|
|
||||||
var timestamp = entry[0];
|
|
||||||
var teamhash = entry[1];
|
|
||||||
var category = entry[2];
|
|
||||||
var points = entry[3];
|
|
||||||
|
|
||||||
var team = teams[teamhash] || {__hash__: teamhash};
|
|
||||||
|
|
||||||
// Add points to team's points for that category
|
|
||||||
team[category] = (team[category] || 0) + points;
|
|
||||||
|
|
||||||
// Record highest score in a category
|
|
||||||
highscore[category] = Math.max(highscore[category] || 0, team[category]);
|
|
||||||
|
|
||||||
teams[teamhash] = team;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by team score
|
|
||||||
function teamScore(t) {
|
|
||||||
var score = 0;
|
|
||||||
|
|
||||||
for (var category in highscore) {
|
|
||||||
score += (t[category] || 0) / highscore[category];
|
|
||||||
}
|
|
||||||
// XXX: This function really shouldn't have side effects.
|
|
||||||
t.__score__ = score;
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
function pointScore(points, category)
|
|
||||||
{
|
|
||||||
return points / highscore[category]
|
|
||||||
}
|
|
||||||
function teamCompare(a, b) {
|
|
||||||
return teamScore(a) - teamScore(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
var winners = [];
|
|
||||||
for (var i in teams) {
|
|
||||||
winners.push(teams[i]);
|
|
||||||
}
|
|
||||||
if (winners.length == 0) {
|
|
||||||
// No teams!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
winners.sort(teamCompare);
|
|
||||||
winners.reverse();
|
|
||||||
|
|
||||||
// Clear out the element we're about to populate
|
|
||||||
while (element.lastChild) {
|
|
||||||
element.removeChild(element.lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate!
|
|
||||||
var topActualScore = winners[0].__score__;
|
|
||||||
|
|
||||||
|
|
||||||
if(mode == "time")
|
|
||||||
{
|
|
||||||
var colorScale = d3.schemeCategory10;
|
|
||||||
|
|
||||||
var teamLines = {};
|
|
||||||
var reverseTeam = {};
|
|
||||||
for(var i in pointslog)
|
|
||||||
{
|
|
||||||
var entry = pointslog[i];
|
|
||||||
var timestamp = entry[0];
|
|
||||||
var teamhash = entry[1];
|
|
||||||
var category = entry[2];
|
|
||||||
var points = entry[3];
|
|
||||||
var teamname = teamnames[teamhash];
|
|
||||||
reverseTeam[teamname] = teamhash;
|
|
||||||
points = pointScore(points, category);
|
|
||||||
|
|
||||||
if(!(teamname in teamLines))
|
|
||||||
{
|
|
||||||
var teamHistory = [[timestamp, points, category, entry[3], [minTime, 0, category, 0]]];
|
|
||||||
teamLines[teamname] = teamHistory;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var teamHistory = teamLines[teamname];
|
|
||||||
teamHistory.push([timestamp, points + teamHistory[teamHistory.length - 1][1], category, entry[3], teamHistory[teamHistory.length - 1]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log(teamLines);
|
|
||||||
|
|
||||||
var graph = document.createElement("svg");
|
|
||||||
graph.id = "graph";
|
|
||||||
graph.style.width="90%";
|
|
||||||
graph.style.height="40em";
|
|
||||||
graph.style.backgroundColor = "white";
|
|
||||||
graph.style.display = "table";
|
|
||||||
var holdingDiv = document.createElement("div");
|
|
||||||
holdingDiv.align="center";
|
|
||||||
holdingDiv.id="holding";
|
|
||||||
element.appendChild(holdingDiv);
|
|
||||||
holdingDiv.appendChild(graph);
|
|
||||||
|
|
||||||
var margins = 40;
|
|
||||||
|
|
||||||
var width = graph.offsetWidth;
|
|
||||||
var height = graph.offsetHeight;
|
|
||||||
|
|
||||||
//var xScale = d3.scaleLinear().range([minTime, maxTime]);
|
|
||||||
//var yScale = d3.scaleLinear().range([0, topActualScore]);
|
|
||||||
var originTime = (maxTime - minTime) / 60;
|
|
||||||
var xScale = d3.scaleLinear().range([margins, width - margins]);
|
|
||||||
xScale.domain([0, originTime]);
|
|
||||||
var yScale = d3.scaleLinear().range([height - margins, margins]);
|
|
||||||
yScale.domain([0, topActualScore]);
|
|
||||||
|
|
||||||
graph = d3.select("#graph");
|
|
||||||
graph.remove();
|
|
||||||
graph = d3.select("#holding").append("svg")
|
|
||||||
.attr("width", width)
|
|
||||||
.attr("height", height);
|
|
||||||
//.attr("style", "background: white");
|
|
||||||
|
|
||||||
|
|
||||||
//graph.append("g")
|
|
||||||
// .attr("transform", "translate(" + margins + ", 0)")
|
|
||||||
// .call(d3.axisLeft(yScale))
|
|
||||||
// .style("stroke", "white");;
|
|
||||||
|
|
||||||
var maxNumEntry = 10;
|
|
||||||
//var curEntry = 0;
|
|
||||||
var winningTeams = [];
|
|
||||||
for(entry in winners)
|
|
||||||
{
|
|
||||||
var curEntry = entry;
|
|
||||||
if(curEntry >= maxNumEntry)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
entry = teamnames[winners[entry].__hash__];
|
|
||||||
winningTeams.push(entry);
|
|
||||||
//console.log(curEntry);
|
|
||||||
//console.log(entry);
|
|
||||||
|
|
||||||
//var isTop = false;
|
|
||||||
//for(var x=0; x < maxNumEntry; x++)
|
|
||||||
//{
|
|
||||||
// var teamhash = reverseTeam[entry];
|
|
||||||
// if(winners[x].__hash__ == teamhash)
|
|
||||||
// {
|
|
||||||
// curEntry = x;
|
|
||||||
// isTop = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//if(!isTop)
|
|
||||||
//{
|
|
||||||
// continue;
|
|
||||||
//}
|
|
||||||
|
|
||||||
var curTeam = teamLines[entry];
|
|
||||||
var lastEntry = curTeam[curTeam.length - 1];
|
|
||||||
//curTeam.append()
|
|
||||||
curTeam.push([maxTime, lastEntry[1], lastEntry[2], lastEntry[3], lastEntry]);
|
|
||||||
var curLayer = graph.append("g");
|
|
||||||
curLayer.selectAll("line")
|
|
||||||
.data(curTeam)
|
|
||||||
.enter()
|
|
||||||
.append("line")
|
|
||||||
.style("stroke", colorScale[curEntry])
|
|
||||||
.attr("stroke-width", 4)
|
|
||||||
.attr("class", "team_" + entry)
|
|
||||||
.style("z-index", maxNumEntry - curEntry)
|
|
||||||
.attr("x1",
|
|
||||||
function(d)
|
|
||||||
{
|
|
||||||
return xScale((d[4][0] - minTime) / 60);
|
|
||||||
})
|
|
||||||
.attr("x2",
|
|
||||||
function(d)
|
|
||||||
{
|
|
||||||
return xScale((d[0] - minTime) / 60);
|
|
||||||
})
|
|
||||||
.attr("y1",
|
|
||||||
function(d)
|
|
||||||
{
|
|
||||||
return yScale(d[4][1]);
|
|
||||||
})
|
|
||||||
.attr("y2",
|
|
||||||
function(d)
|
|
||||||
{
|
|
||||||
return yScale(d[1]);
|
|
||||||
})
|
|
||||||
.on("mouseover", handleMouseover)
|
|
||||||
.on("mouseout", handleMouseout);
|
|
||||||
|
|
||||||
curLayer.selectAll("circle")
|
|
||||||
.data(curTeam)
|
|
||||||
.enter()
|
|
||||||
.append("circle")
|
|
||||||
.style("fill", colorScale[curEntry])
|
|
||||||
.style("z-index", maxNumEntry - curEntry)
|
|
||||||
.attr("class", "team_" + entry)
|
|
||||||
.attr("r", 5)
|
|
||||||
.attr("cx",
|
|
||||||
function(d)
|
|
||||||
{
|
|
||||||
return xScale((d[0] - minTime) / 60);
|
|
||||||
})
|
|
||||||
.attr("cy",
|
|
||||||
function(d)
|
|
||||||
{
|
|
||||||
return yScale(d[1]);
|
|
||||||
})
|
|
||||||
.on("mouseover", handleMouseoverCircle)
|
|
||||||
.on("mouseout", handleMouseoutCircle);
|
|
||||||
|
|
||||||
curEntry++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var axisG = graph.append("g");
|
|
||||||
axisG
|
|
||||||
.attr("transform", "translate(0," + (height - margins) + ")")
|
|
||||||
.call(d3.axisBottom(xScale));
|
|
||||||
//.style("stroke", "white");
|
|
||||||
axisG.selectAll("path").style("stroke", "white");
|
|
||||||
axisG.selectAll("line").style("stroke", "white");
|
|
||||||
axisG.selectAll("text").style("fill", "white");
|
|
||||||
|
|
||||||
graph.append("text")
|
|
||||||
.attr("text-anchor", "middle")
|
|
||||||
.attr("transform", "translate(" + (width / 2) + ", " + (height - margins / 8) + ")")
|
|
||||||
.style("fill", "white")
|
|
||||||
.text("Time (minutes)");
|
|
||||||
|
|
||||||
var legend = graph.append("g");
|
|
||||||
var legendRowHeight = 40;
|
|
||||||
legend.selectAll("rect")
|
|
||||||
.data(winningTeams)
|
|
||||||
.enter()
|
|
||||||
.append("rect")
|
|
||||||
.attr("class", function(d){ return "team_" + d; })
|
|
||||||
.attr("fill", function(d, i){ return colorScale[i]; })
|
|
||||||
.style("z-index", function(d, i){ return i; })
|
|
||||||
.attr("x", margins)
|
|
||||||
.attr("y", function(d, i){ return margins + legendRowHeight * i; })
|
|
||||||
.attr("height", legendRowHeight)
|
|
||||||
.attr("width", 150)
|
|
||||||
.on("mouseover", handleMouseoverLegend)
|
|
||||||
.on("mouseout", handleMouseoutLegend);
|
|
||||||
|
|
||||||
legend.selectAll("text")
|
|
||||||
.data(winningTeams)
|
|
||||||
.enter()
|
|
||||||
.append("text")
|
|
||||||
//.attr("class", function(d){ return "team_" + d; })
|
|
||||||
.attr("fill", "black")
|
|
||||||
.style("z-index", function(d, i){ return i; })
|
|
||||||
.attr("dx", margins)
|
|
||||||
.attr("dy", function(d, i){ return margins + legendRowHeight * (i + .5); })
|
|
||||||
.text(function(d){ return d; })
|
|
||||||
.attr("dominant-baseline", "central")
|
|
||||||
.style("pointer-events", "none");
|
|
||||||
|
|
||||||
|
|
||||||
function handleMouseover(d, i)
|
|
||||||
{
|
|
||||||
d3.select("body").selectAll(".tooltip").remove();
|
|
||||||
var curClass = d3.select(this).attr("class");
|
|
||||||
d3.select("body").selectAll("." + curClass)
|
|
||||||
.style("stroke", "white")
|
|
||||||
.style("fill", "white");
|
|
||||||
d3.select("body").selectAll("text")
|
|
||||||
.style("stroke-width", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseout(d, i)
|
|
||||||
{
|
|
||||||
d3.select("body").selectAll(".tooltip").remove();
|
|
||||||
var curClass = d3.select(this).attr("class");
|
|
||||||
var zIndex = d3.select(this).style("z-index");
|
|
||||||
d3.select("body").selectAll("." + curClass)
|
|
||||||
.style("stroke", colorScale[maxNumEntry - zIndex])
|
|
||||||
.style("fill", colorScale[maxNumEntry - zIndex]);
|
|
||||||
d3.select("body").selectAll("text")
|
|
||||||
.style("stroke-width", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tooltipPadding = 10;
|
|
||||||
function handleMouseoverCircle(d, i)
|
|
||||||
{
|
|
||||||
d3.select("body").selectAll(".tooltip").remove();
|
|
||||||
var curClass = d3.select(this).attr("class");
|
|
||||||
d3.select("body").selectAll("." + curClass)
|
|
||||||
.style("stroke", "white")
|
|
||||||
.style("fill", "white");
|
|
||||||
d3.select("body").selectAll("text")
|
|
||||||
.style("stroke-width", 0);
|
|
||||||
|
|
||||||
graph.append("g").append("text")
|
|
||||||
.attr("class", "tooltip")
|
|
||||||
.attr("text-anchor", "middle")
|
|
||||||
.style("fill", "red")
|
|
||||||
.style("stroke-width", -4)
|
|
||||||
.style("stroke", "black")
|
|
||||||
.style("font-weight", "bolder")
|
|
||||||
.style("font-size", "large")
|
|
||||||
.attr("dx",
|
|
||||||
function()
|
|
||||||
{
|
|
||||||
return xScale((d[0] - minTime) / 60);
|
|
||||||
})
|
|
||||||
.attr("dy",
|
|
||||||
function()
|
|
||||||
{
|
|
||||||
return yScale(d[1]) - tooltipPadding;
|
|
||||||
})
|
|
||||||
.text(function(){ return d[2] + " " + d[3]; })
|
|
||||||
.style("pointer-events", "none");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseoutCircle(d, i)
|
|
||||||
{
|
|
||||||
d3.select("body").selectAll(".tooltip").remove();
|
|
||||||
var curClass = d3.select(this).attr("class");
|
|
||||||
var zIndex = d3.select(this).style("z-index");
|
|
||||||
d3.select("body").selectAll("." + curClass)
|
|
||||||
.style("stroke", colorScale[maxNumEntry - zIndex])
|
|
||||||
.style("fill", colorScale[maxNumEntry - zIndex]);
|
|
||||||
d3.select("body").selectAll("text")
|
|
||||||
.style("stroke-width", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseoverLegend(d, i)
|
|
||||||
{
|
|
||||||
d3.select("body").selectAll(".tooltip").remove();
|
|
||||||
var curClass = d3.select(this).attr("class");
|
|
||||||
d3.select("body").selectAll("." + curClass)
|
|
||||||
.style("stroke", "white")
|
|
||||||
.style("fill", "white");
|
|
||||||
d3.select("body").selectAll("text")
|
|
||||||
.style("stroke-width", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseoutLegend(d, i)
|
|
||||||
{
|
|
||||||
d3.select("body").selectAll(".tooltip").remove();
|
|
||||||
var curClass = d3.select(this).attr("class");
|
|
||||||
var zIndex = d3.select(this).style("z-index");
|
|
||||||
d3.select("body").selectAll("." + curClass)
|
|
||||||
.style("stroke", colorScale[zIndex])
|
|
||||||
.style("fill", colorScale[zIndex]);
|
|
||||||
d3.select("body").selectAll("text")
|
|
||||||
.style("stroke-width", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(mode == "original")
|
|
||||||
{
|
|
||||||
// (100 / ncats) * (ncats / topActualScore);
|
|
||||||
var maxWidth = 100 / topActualScore;
|
|
||||||
for (var i in winners) {
|
|
||||||
var team = winners[i];
|
|
||||||
var row = document.createElement("div");
|
|
||||||
var ncat = 0;
|
|
||||||
for (var category in highscore) {
|
|
||||||
var catHigh = highscore[category];
|
|
||||||
var catTeam = team[category] || 0;
|
|
||||||
var catPct = catTeam / catHigh;
|
|
||||||
var width = maxWidth * catPct;
|
|
||||||
|
|
||||||
var bar = document.createElement("span");
|
|
||||||
bar.classList.add("cat" + ncat);
|
|
||||||
bar.style.width = width + "%";
|
|
||||||
bar.textContent = category + ": " + catTeam;
|
|
||||||
bar.title = bar.textContent;
|
|
||||||
|
|
||||||
row.appendChild(bar);
|
|
||||||
ncat += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var te = document.createElement("span");
|
|
||||||
te.classList.add("teamname");
|
|
||||||
te.textContent = teamnames[team.__hash__];
|
|
||||||
row.appendChild(te);
|
|
||||||
|
|
||||||
element.appendChild(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(mode == "total")
|
|
||||||
{
|
|
||||||
var colorScale = d3.schemeCategory20;
|
|
||||||
|
|
||||||
var numCats = 0;
|
|
||||||
for(entry in allQuestions)
|
|
||||||
{
|
|
||||||
numCats++;
|
|
||||||
}
|
|
||||||
var maxWidth = 100 / (0.0 + numCats);
|
|
||||||
//console.log(maxWidth);
|
|
||||||
|
|
||||||
for (var i in winners) {
|
|
||||||
var team = winners[i];
|
|
||||||
var row = document.createElement("div");
|
|
||||||
var ncat = 0;
|
|
||||||
for (var category in allQuestions) {
|
|
||||||
var catHigh = allQuestions[category];
|
|
||||||
var catTeam = team[category] || 0;
|
|
||||||
var catPct = (0.0 + catTeam) / (0.0 + catHigh["total"]);
|
|
||||||
var width = maxWidth * catPct;
|
|
||||||
var bar = document.createElement("span");
|
|
||||||
|
|
||||||
var numLeft = catHigh["total"] - catTeam;
|
|
||||||
|
|
||||||
//bar.classList.add("cat" + ncat);
|
|
||||||
bar.style.backgroundColor = colorScale[ncat];
|
|
||||||
bar.style.color = "white";
|
|
||||||
bar.style.width = width + "%";
|
|
||||||
bar.textContent = category + ": " + catTeam;
|
|
||||||
bar.title = bar.textContent;
|
|
||||||
|
|
||||||
row.appendChild(bar);
|
|
||||||
|
|
||||||
ncat++;
|
|
||||||
|
|
||||||
width = maxWidth * (1 - catPct);
|
|
||||||
if(width > 0)
|
|
||||||
{
|
|
||||||
var noBar = document.createElement("span");
|
|
||||||
//noBar.classList.add("cat" + ncat);
|
|
||||||
noBar.style.backgroundColor = colorScale[ncat];
|
|
||||||
noBar.style.width = width + "%";
|
|
||||||
noBar.textContent = numLeft;
|
|
||||||
noBar.title = bar.textContent;
|
|
||||||
|
|
||||||
row.appendChild(noBar);
|
|
||||||
}
|
|
||||||
ncat += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var te = document.createElement("span");
|
|
||||||
te.classList.add("teamname");
|
|
||||||
te.textContent = teamnames[team.__hash__];
|
|
||||||
row.appendChild(te);
|
|
||||||
|
|
||||||
element.appendChild(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function once() {
|
// Set up scoreboard structure
|
||||||
loadJSON("points.json", update);
|
let spansByTeamByCategory = {};
|
||||||
|
for (let pair of orderedOverallScores) {
|
||||||
|
let teamHash = pair[1];
|
||||||
|
let teamName = teamNames[teamHash];
|
||||||
|
let teamRow = document.createElement("div");
|
||||||
|
let ncat = 0;
|
||||||
|
spansByTeamByCategory[teamHash] = {};
|
||||||
|
for (let cat in categories) {
|
||||||
|
let catSpan = document.createElement("span");
|
||||||
|
catSpan.classList.add("cat" + ncat);
|
||||||
|
catSpan.style.width = "0%";
|
||||||
|
catSpan.textContent = cat + ": 0";
|
||||||
|
|
||||||
|
spansByTeamByCategory[teamHash][cat] = catSpan;
|
||||||
|
|
||||||
|
teamRow.appendChild(catSpan);
|
||||||
|
ncat += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var te = document.createElement("span");
|
||||||
|
te.classList.add("teamname");
|
||||||
|
te.textContent = teamName;
|
||||||
|
teamRow.appendChild(te);
|
||||||
|
|
||||||
|
element.appendChild(teamRow);
|
||||||
}
|
}
|
||||||
if (continuous) {
|
|
||||||
updateInterval = setInterval(once, interval);
|
// How many categories are there?
|
||||||
|
var numCategories = 0;
|
||||||
|
for (var cat in categories) {
|
||||||
|
numCategories += 1;
|
||||||
}
|
}
|
||||||
once();
|
|
||||||
|
// Replay points log, displaying scoreboard at each step
|
||||||
|
let replayTimer = null;
|
||||||
|
let replayIndex = 0;
|
||||||
|
function replayStep(event) {
|
||||||
|
if (replayIndex > pointsLog.length) {
|
||||||
|
clearInterval(replayTimer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay log up until replayIndex
|
||||||
|
let totalPointsByTeamByCategory = {};
|
||||||
|
for (let index = 0; index < replayIndex; index += 1) {
|
||||||
|
let entry = pointsLog[index];
|
||||||
|
let entryTimeStamp = entry[0];
|
||||||
|
let entryTeamHash = entry[1];
|
||||||
|
let entryCategory = entry[2];
|
||||||
|
let entryPoints = entry[3];
|
||||||
|
|
||||||
|
// Add points to team's points for that category
|
||||||
|
let points = totalPointsByTeamByCategory[entryTeamHash] || {};
|
||||||
|
let categoryPoints = points[entryCategory] || 0;
|
||||||
|
categoryPoints += entryPoints;
|
||||||
|
points[entryCategory] = categoryPoints;
|
||||||
|
totalPointsByTeamByCategory[entryTeamHash] = points;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out everybody's score
|
||||||
|
for (let teamHash in teamNames) {
|
||||||
|
for (let cat in categories) {
|
||||||
|
let totalPointsByCategory = totalPointsByTeamByCategory[teamHash] || {};
|
||||||
|
let points = totalPointsByCategory[cat] || 0;
|
||||||
|
if (points > 0) {
|
||||||
|
let score = points / maxPointsByCategory[cat];
|
||||||
|
let span = spansByTeamByCategory[teamHash][cat];
|
||||||
|
let width = (100.0 / numCategories) * score;
|
||||||
|
|
||||||
|
span.style.width = width + "%";
|
||||||
|
span.textContent = cat + ": " + points;
|
||||||
|
span.title = span.textContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replayIndex += 1;
|
||||||
|
}
|
||||||
|
replayStep();
|
||||||
|
replayTimer = setInterval(replayStep, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
function once() {
|
||||||
|
loadJSON("points.json", update);
|
||||||
|
}
|
||||||
|
if (continuous) {
|
||||||
|
setInterval(once, 60000);
|
||||||
|
}
|
||||||
|
once();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue