/* blueROCK - for digital rock Copyright (C) 2019 Dorian Zedler This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.4 import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.1 import QtGraphicalEffects 1.0 Item { id: control property var flowchartData property var allFlowchartData property int rounds: 0 property int refreshes: 0 property int roundRefreshes: 1 property int roundCount: 0 onFlowchartDataChanged: { prepareData() } function prepareData() { if(!control.enabled || control.flowchartData === undefined || control.flowchartData['route_names'] === undefined) return /*refreshes += 1 if(refreshes > 2){ roundRefreshes += 1 } console.log("refreshes: " + refreshes + " rounds: " + roundRefreshes) // create competition like data (just testing) for(var part in flowchartData['participants']){ if(flowchartData['participants'].hasOwnProperty(part)){ for(var r = 2 + roundRefreshes; r < 7; r++){ delete flowchartData['participants'][part]["result"+r] delete flowchartData['participants'][part]["result_rank"+r] } if(parseInt(flowchartData['participants'][part]["result_rank0"]) > 14 + refreshes) { delete flowchartData['participants'][part]["result_rank2"] } } } delete flowchartData['route_names'][2] delete flowchartData['route_names'][3] delete flowchartData['route_names'][4] delete flowchartData['route_names'][5] delete flowchartData['route_names'][6] */ //flowchartData['route_names'] = flowchartData['route_names'].slice(0,) // array to store the restructured data var allData = [] control.allFlowchartData = [] control.rounds = Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0 //console.log(JSON.stringify(flowchartData['route_names'])) for(var round in flowchartData['route_names']){ if(flowchartData['route_names'].hasOwnProperty(round) && parseInt(round) >= 0){ //console.log(round) if(parseInt(round) === 0){ // this is the first round // find pairs (always wors vs. best (1-16; 1-15; ...)) (they are sorted by the rank of the better athlete (1-2-3-4-5-6-7-8) var qualificationResults = [] for(var x = 0; x < flowchartData['participants'].length; x++){ qualificationResults.push(flowchartData['participants'][x]) } qualificationResults.sort(function(a, b) { return parseInt(a["result_rank0"]) - parseInt(b["result_rank0"]); }); var nextRoundPairs = [] var totalMatches = (parseInt(Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0) + 2) var nextRoundMatches = Math.pow(2, totalMatches-1) for(var i = 0; i < nextRoundMatches; i++){ nextRoundPairs.push([qualificationResults[i], qualificationResults[nextRoundMatches*2-i-1]]) } // build second round pairs (sorted by the rank of the better athlete and worst vs. best (1-8; 2-7; ... )) var sortedFirstRoundPairs = [] for(i = 0; i < nextRoundMatches; i += 1){ sortedFirstRoundPairs.push([nextRoundPairs.shift(), nextRoundPairs.pop()]) } // sort these pairs (containing two pairs of athletes) by the rank of the better athlete (1-4;2-3) var finalSortedFirstRoundPairs = [] for(i=0; i < nextRoundMatches/4; i++){ finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[i]) finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[nextRoundMatches/2-i-1]) } // convert the list of pairs of pairs of athletes back to a single list of pairs of athletes var finalFirstRoundPairs = [] for(i=0; i < finalSortedFirstRoundPairs.length; i++){ finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][0]) finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][1]) } // push the first round to all data finalFirstRoundPairs.push(2) finalFirstRoundPairs.push(flowchartData['route_names'][2]) allData.push(finalFirstRoundPairs) } else if(parseInt(round) > 0 ){ // this is not the first round var nextRound = [] // only used when the current round is the 1/2 final var smallFinal = [] var Final = [] for(var i = 0; i < allData[allData.length-1].length-2; i+=1){ var thisPair = allData[allData.length-1][i] var thisWinner var thisLooser var thisWinnerIsFirstOfNewPair = i%2 === 0 if(thisPair[0] === undefined || thisPair[1] === undefined){ continue } if(thisWinnerIsFirstOfNewPair){ nextRound.push([]) } //thisPair[0]["result_rank"] = thisPair[0]["result_rank"+round] //thisPair[1]["result_rank"] = thisPair[1]["result_rank"+round] if(parseInt(thisPair[0]["result_rank"+round]) < parseInt(thisPair[1]["result_rank"+round])){ thisWinner = thisPair[0] thisLooser = thisPair[1] } else if(parseInt(thisPair[0]["result_rank"+round]) > parseInt(thisPair[1]["result_rank"+round])) { thisWinner = thisPair[1] thisLooser = thisPair[0] } else { // no result yet!! //console.log("got no winner yet, rank 0: " + thisPair[0]["result_rank"+round] + " rank 1: " + thisPair[1]["result_rank"+round]) continue } //console.log(thisWinner['firstname']+" has won in round " + round) if(round - control.rounds === 2){ // if we are in the 1/2 final Final.push(thisWinner) smallFinal.push(thisLooser) } else { nextRound[nextRound.length-1].push(thisWinner) } } if(smallFinal.length > 0 && Final.length > 0){ // Final allData.push([Final, parseInt(round)+2, flowchartData['route_names'][String(parseInt(round)+2)] + " / " + flowchartData['route_names'][String(parseInt(round)+1)] ]) // small Final allData.push([smallFinal, parseInt(round)+1]) //break } else { nextRound.push(parseInt(round) + 1 ) nextRound.push(flowchartData['route_names'][parseInt(round) + 1]) allData.push(nextRound) } } } } control.allFlowchartData = allData control.roundCount = (parseInt(Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0) + 2) //console.log(JSON.stringify(allData)) } ListView { id: roundListView property int columnWidth: height * 0.3 property int columnHeight: height anchors { top: parent.top bottom: parent.bottom horizontalCenter: parent.horizontalCenter } width: Math.min((columnWidth + spacing) * model, control.width) spacing: app.height * 0.05 orientation: ListView.LeftToRight boundsBehavior: ListView.StopAtBounds model: control.roundCount delegate: Item { id: roundItem property int thisIndex: index property int thisRound: thisRoundIsValid ? control.allFlowchartData[roundItem.thisIndex][control.allFlowchartData[roundItem.thisIndex].length-2]:-1 property bool thisRoundIsValid: control.allFlowchartData !== undefined && control.allFlowchartData[roundItem.thisIndex] !== undefined && control.allFlowchartData[roundItem.thisIndex].length > 2 property bool thisIsLastRound: thisIndex === control.roundCount - 1 property bool thisIsSemiFinal: thisIndex === control.roundCount - 2 && rectRep.model === 2 property int tileSize: (roundItem.height / 8 - roundNameLa.height) * 1.45 width: roundListView.columnWidth height: roundListView.columnHeight Column { id: roundCol anchors.fill: parent Label { id: roundNameLa width: parent.width height: control.height * 0.05 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit font.pixelSize: height * 0.6 minimumPixelSize: 1 font.bold: true text: roundItem.thisRoundIsValid && control.allFlowchartData[roundItem.thisIndex][control.allFlowchartData[roundItem.thisIndex].length-1] !== undefined ? control.allFlowchartData[roundItem.thisIndex][control.allFlowchartData[roundItem.thisIndex].length-1] : "-" } Repeater { id: rectRep model: Math.max( Math.pow(2, control.roundCount-1) * Math.pow(0.5, (roundItem.thisIndex)), 2) delegate: Item { id: matchItm property bool lowerPart: (index%2 > 0) property var matchData: roundItem.thisRoundIsValid ? control.allFlowchartData[ thisIsSmallFinal ? roundItem.thisIndex+1 : roundItem.thisIndex ][ thisIsSmallFinal ? 0:matchItm.thisIndex]: undefined property int thisIndex: index property int thisRound: parseInt(roundItem.thisRound) - (thisIsSmallFinal ? 1:0) property var thisMatchData: thisMatchDataIsValid ? matchData:[] property bool thisMatchDataIsValid: (matchData !== undefined && matchData !== null && typeof matchData === "object" && matchData.length > 0) property bool thisMatchIsOver: thisMatchDataIsValid && thisMatchData[0]['result_rank'+thisRound] !== undefined && thisMatchData[1]['result_rank'+thisRound] !== undefined property bool thisIsFinal: roundItem.thisIsLastRound && thisIndex === rectRep.model - 2 property bool thisIsSmallFinal: roundItem.thisIsLastRound && thisIndex === rectRep.model - 1 property int winnerIndex: thisMatchIsOver ? (parseInt(thisMatchData[0]['result_rank'+thisRound]) < parseInt(thisMatchData[1]['result_rank'+thisRound]) ? 0:1) : -1 height: !roundItem.thisIsLastRound ? (roundItem.height - roundNameLa.height) / rectRep.model - roundCol.spacing : (thisIsFinal ? (roundItem.height - roundNameLa.height) * 0.5 + roundItem.tileSize * 0.5 : (roundItem.height - roundNameLa.height) - (roundItem.height - roundNameLa.height) * 0.5 + roundItem.tileSize * 0.5 ) width: roundItem.width onMatchDataChanged: { fadeInPa.start() } ParallelAnimation { id: fadeInPa NumberAnimation { target: matchItm; property: "opacity"; from: 0; to: 1.0; duration: 400 } NumberAnimation { target: matchItm; property: "scale"; from: 0.8; to: 1.0; duration: 400 } } Canvas { id: lineCanvas width: app.width; height: app.height contextType: "2d" visible: !roundItem.thisIsLastRound property int targetYFactor: matchItm.lowerPart ? -1:1 Path { id: firstPartPath startX: matchItm.width startY: matchItm.height * 0.5 PathQuad { id: firstPartPathQuad relativeX: roundListView.spacing * 0.5 relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor relativeControlX: relativeX relativeControlY: 0 } PathQuad { relativeX: roundListView.spacing * 0.5 relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor relativeControlX: 0 relativeControlY: relativeY } } onPaint: { context.strokeStyle = "lightgrey" context.lineWidth = matchRect.height * 0.05 context.path = firstPartPath; context.stroke(); } } RectangularGlow { id: effect anchors.fill: matchRect glowRadius: 0 spread: 0 opacity: 0.3 color: "black" cornerRadius: matchRect.radius } Rectangle { id: matchRect anchors { centerIn: !matchItm.thisIsFinal ? parent:undefined bottom: matchItm.thisIsFinal ? parent.bottom:undefined } //anchors.verticalCenterOffset: matchItm.lowerPart ? 10:10 width: parent.width height: roundItem.tileSize //scale: 0.9 color: Material.dialogColor border.color: "lightgrey" border.width: 0 radius: height * 0.2 Column { spacing: 0 anchors.fill: parent anchors.margins: matchRect.radius * 0.5 Repeater { // for the two athletes model: 2 delegate: RowLayout { height: parent.height / 2 - parent.spacing width: parent.width spacing: height * 0.1 Label { Layout.preferredHeight: parent.height Layout.preferredWidth: parent.width * 0.15 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft font.pixelSize: height * 0.7 fontSizeMode: Text.Fit minimumPixelSize: 1 text: matchItm.thisMatchData[index] !== undefined ? ( parseInt(matchItm.thisMatchData[index]['result_rank0']) < 10 ? matchItm.thisMatchData[index]['result_rank0'] + " ": matchItm.thisMatchData[index]['result_rank0'] ): "" } Label { Layout.preferredHeight: parent.height Layout.fillWidth: true height: parent.height verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft font.pixelSize: height * 0.5 font.bold: matchItm.winnerIndex === index elide: "ElideRight" color: matchItm.winnerIndex === index ? Material.color(Material.Green):Material.primaryTextColor text: matchItm.thisMatchData[index] !== undefined ? matchItm.thisMatchData[index]['firstname'] + " " + matchItm.thisMatchData[index]['lastname'] :"-" } Label { Layout.preferredHeight: parent.height height: parent.height verticalAlignment: Text.AlignVCenter font.pixelSize: height * 0.5 text: matchItm.thisMatchData[index] !== undefined && matchItm.thisMatchData[index]['result'+matchItm.thisRound] !== undefined ? ( parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]) ? (parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]).toFixed(2)) : matchItm.thisMatchData[index]['result'+matchItm.thisRound] ) : "-" } } } } } } } } Loader { id: blueRockBadgeLoader x: (parent.width - width) / 2 y: (parent.height - roundNameLa.height - height) / 2 + roundNameLa.height sourceComponent: roundItem.thisIsSemiFinal ? blueRockBadgeComponent:undefined Component { id: blueRockBadgeComponent BlueRockBadge { width: roundItem.width height: width * 0.25 } } } } } }