var playoff = { settings: { 'winner_h_offset': 5, 'loser_h_offset': 20, 'place_winner_h_offset': 10, 'place_loser_h_offset': 15, 'finish_winner_h_offset': 5, 'finish_loser_h_offset': 20, 'winner_v_offset': -10, 'loser_v_offset': 10, 'place_winner_v_offset': 2, 'place_loser_v_offset': 9, 'finish_winner_v_offset': -4, 'finish_loser_v_offset': 4, 'loser_colour': '#ff0000', 'winner_colour': '#00ff00', 'place_loser_colour': '#dddd00', 'place_winner_colour': '#00dddd', 'finish_loser_colour': '#ff0000', 'finish_winner_colour': '#00ff00', 'fade_boxes': 0 }, drawLine: function(ctx, line) { ctx.beginPath(); ctx.moveTo(line[0], line[1]); ctx.lineTo(line[2], line[3]); ctx.stroke(); }, loadSettings: function(canvas, defaults) { for (var setting in defaults) { var attr = 'data-' + setting.replace(/_/g, '-'); var attr_value = canvas.getAttribute(attr); if (attr_value) { if (attr.substr(attr.length-6) == 'offset') { attr_value = parseInt(attr_value); } defaults[setting] = attr_value; } } return defaults; }, initEvents: function() { this.settings = this.loadSettings(document.getElementById('playoff_canvas'), this.settings); var fadeInterval = this.settings['fade_boxes']; if (fadeInterval > 0) { var highlightTrigger; var boxes = document.getElementsByClassName('playoff_matchbox'); var that = this; for (var b = 0; b < boxes.length; b++) { var box = boxes[b]; var highlightHandler = this.highlightBox; box.addEventListener('mouseenter', function(evt) { highlightTrigger = setTimeout(function() { var boxId = evt.target.getAttribute('data-id'); that.highlightBox(boxId); that.run(boxId); }, fadeInterval); }); box.addEventListener('mouseleave', function(evt) { clearTimeout(highlightTrigger); that.highlightBox(); that.run(); }); } } }, highlightBox: function(box) { var boxes = document.getElementsByClassName('playoff_matchbox'); var highlightBoxes = []; var attrArr = ['data-winner', 'data-loser', 'data-place-winner', 'data-place-loser', 'data-finish-winner', 'data-finish-loser']; for (var b = 0; b < boxes.length; b++) { var boxId = boxes[b].getAttribute('data-id'); if (box && (boxId == box)) { highlightBoxes.push(boxId); for (const attr of attrArr) { var attrVal = boxes[b].getAttribute(attr); if (attrVal) { highlightBoxes = highlightBoxes.concat(attrVal.split(' ')); } } } boxes[b].classList.remove('faded'); } if (box) { for (var b = 0; b < boxes.length; b++) { var fade = true; var boxId = boxes[b].getAttribute('data-id'); if (highlightBoxes.includes(boxId)) { fade = false; } else { for (const attr of attrArr) { var boxId = boxes[b].getAttribute(attr); if (boxId) { boxIds = boxId.split(' '); if (boxIds.includes(box)) { fade = false; } } } } if (fade) { boxes[b].classList.add('faded'); } } } }, run: function(highlightedBox) { var boxes = document.getElementsByClassName('playoff_matchbox'); var canvas = document.getElementById('playoff_canvas'); var lines = { 'winner': {}, 'loser': {}, 'place-winner': {}, 'place-loser': {}, 'finish-winner': {}, 'finish-loser': {}, 'winner-fade': {}, 'loser-fade': {}, 'place-winner-fade': {}, 'place-loser-fade': {}, 'finish-winner-fade': {}, 'finish-loser-fade': {} }; var boxes_idx = {}; for (var b = 0; b < boxes.length; b++) { var id = boxes[b].getAttribute('data-id'); boxes_idx[id] = boxes[b]; for (var attr in lines) { var value = boxes[b].getAttribute('data-' + attr); if (value) { if (highlightedBox && (highlightedBox != id && !value.split(' ').includes(highlightedBox))) { attr = attr + '-fade'; } if (!lines[attr][value]) { lines[attr][value] = []; } lines[attr][value].push(id); } } } var lineMethods = { 'place-winner': 'to', 'place-loser': 'to', 'finish-winner': 'midpoint', 'finish-loser': 'midpoint', 'winner': 'midpoint', 'loser': 'midpoint' }; var lineCalculator = { correctLines: function(hLines, vLine, comparator) { for (var l1 in hLines) { for (var l2 in hLines) { hLines[l1][2] = comparator(hLines[l1][2], hLines[l2][2]); hLines[l2][2] = hLines[l1][2]; } } for (var l1 in hLines) { vLine[0] = vLine[2] = comparator(hLines[l1][2], vLine[2]); vLine[1] = Math.min(vLine[1], hLines[l1][3]); vLine[3] = Math.max(vLine[3], hLines[l1][3]); } }, template: function() { return { hFrom: [], vFrom: [0, canvas.height, 0, 0], hTo: [], vTo: [canvas.width, canvas.height, canvas.width, 0], midpoints: [] }; }, from: function(from, to, hOffset, vOffset) { var lines = this.template(); for (var f = 0; f < from.length; f++) { var box = boxes_idx[from[f]]; var line = [ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset), Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset) ]; lines.hFrom.push(line); } this.correctLines(lines.hFrom, lines.vFrom, Math.max); for (var t = 0; t < to.length; t++) { var box = boxes_idx[to[t]]; var line = [ Math.floor(parseInt(box.offsetLeft)), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset), lines.vFrom[0], Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset) ]; lines.hTo.push(line); } this.correctLines(lines.hTo, lines.vTo, Math.min); lines.midpoints = [ [lines.vFrom[0], lines.vFrom[1]], [lines.vTo[0], lines.vTo[1]] ]; return lines; }, to: function(from, to, hOffset, vOffset) { var lines = this.template(); for (var t = 0; t < to.length; t++) { var box = boxes_idx[to[t]]; var line = [ parseInt(box.offsetLeft), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset), Math.floor(parseInt(box.offsetLeft) - hOffset), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset) ]; lines.hTo.push(line); } this.correctLines(lines.hTo, lines.vTo, Math.min); for (var f = 0; f < from.length; f++) { var box = boxes_idx[from[f]]; var line = [ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset), lines.vTo[0], Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset) ]; lines.hFrom.push(line); } this.correctLines(lines.hFrom, lines.vFrom, Math.max); lines.midpoints = [ [lines.vFrom[0], lines.vFrom[1]], [lines.vTo[0], lines.vTo[1]] ]; return lines; }, midpoint: function(from, to, hOffset, vOffset) { var lines = this.template(); for (var f = 0; f < from.length; f++) { var box = boxes_idx[from[f]]; var line = [ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset), Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset) ]; lines.hFrom.push(line); } this.correctLines(lines.hFrom, lines.vFrom, Math.max); for (var t = 0; t < to.length; t++) { var box = boxes_idx[to[t]]; var line = [ parseInt(box.offsetLeft), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset), Math.floor(parseInt(box.offsetLeft) - hOffset), Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset) ]; lines.hTo.push(line); } this.correctLines(lines.hTo, lines.vTo, Math.min); lines.midpoints = [ [ (lines.vFrom[0] + lines.vFrom[2]) / 2, (lines.vFrom[1] + lines.vFrom[3]) / 2 ], [ hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4, (lines.vFrom[1] + lines.vFrom[3]) / 2 ], [ hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4, (lines.vTo[1] + lines.vTo[3]) / 2 ], [ (lines.vTo[0] + lines.vTo[2]) / 2, (lines.vTo[1] + lines.vTo[3]) / 2 ] ]; for (var h in lines.hTo) { lines.hTo[h][2] = Math.max( lines.hTo[h][2], lines.midpoints[2][0] ); } for (var h in lines.hFrom) { lines.hFrom[h][2] = Math.min( lines.hFrom[h][2], lines.midpoints[0][0] ); } return lines; } }; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); for (var type in lines) { ctx.globalAlpha = 1.0; realType = type; if (type.endsWith('-fade')) { realType = type.replace('-fade', ''); ctx.globalAlpha = 0.3; } styleType = realType.replaceAll('-', '_'); ctx.strokeStyle = this.settings[styleType + '_colour']; for (var from in lines[type]) { var to = lines[type][from]; from = from.split(' '); var linesToDraw = lineCalculator[lineMethods[realType]]( from, to, this.settings[styleType + '_h_offset'], this.settings[styleType + '_v_offset']); for (var l in linesToDraw.hFrom) { this.drawLine(ctx, linesToDraw.hFrom[l]); } this.drawLine(ctx, linesToDraw.vFrom); for (var l in linesToDraw.hTo) { this.drawLine(ctx, linesToDraw.hTo[l]); } this.drawLine(ctx, linesToDraw.vTo); for (var m = 0; m < linesToDraw.midpoints.length-1; m++) { if (linesToDraw.midpoints[m][0] <= linesToDraw.midpoints[m+1][0]) { this.drawLine(ctx, [ linesToDraw.midpoints[m][0], linesToDraw.midpoints[m][1], linesToDraw.midpoints[m+1][0], linesToDraw.midpoints[m+1][1] ]); } } } } } }; playoff.initEvents(); playoff.run();