Skip to content

Commit

Permalink
progress on analyzer tab
Browse files Browse the repository at this point in the history
  • Loading branch information
jekrch committed Jun 30, 2024
1 parent 29a994e commit 93125b1
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 41 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dist/assets/index-BQUPRy5n.css

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion dist/assets/index-Di97Rn61.css

This file was deleted.

4 changes: 2 additions & 2 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

<link rel="manifest" href="/manifest.json" />
<title>Eurovision Ranker</title>
<script type="module" crossorigin src="/assets/index-fm0Aj39Z.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Di97Rn61.css">
<script type="module" crossorigin src="/assets/index-DF-UPFT-.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BQUPRy5n.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
149 changes: 123 additions & 26 deletions src/components/modals/config/AnalyzeTab.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import React from 'react';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from '../../../redux/types';
import { countries } from '../../../data/Countries';
import { fetchCountryContestantsByYear } from '../../../utilities/ContestantRepository';
import { sortByVotes } from '../../../utilities/VoteProcessor';
import { findMostSimilarLists } from '../../../utilities/RankAnalyzer';
import { RankingComparison, findMostDissimilarLists, findMostSimilarLists } from '../../../utilities/RankAnalyzer';
import { getUrlParam } from '../../../utilities/UrlUtil';
import IconButton from '../../IconButton';
import { Country } from '../../../data/Country';
import { CountryContestant } from '../../../data/CountryContestant';
import Dropdown from '../../Dropdown';

const AnalyzeTab: React.FC = () => {
const year = useSelector((state: AppState) => state.year);
const [voteType, setVoteType] = useState('televote');
const [mostSimilarComparisons, setMostSimilarComparisons] = useState<RankingComparison[]>([]);
const [mostDissimilarComparisons, setMostDissimilarComparisons] = useState<RankingComparison[]>([]);
const [codeCountryNameMap, setCodeCountryNameMap] = useState<Map<string, Country[]>>(new Map());


// Get all country rank codes for the selected year and vote type
const getAllCountryRankCodes = async (voteType: string, round: string, voteYear: string) => {
Expand All @@ -20,7 +26,7 @@ const AnalyzeTab: React.FC = () => {
const countryContestants = await fetchCountryContestantsByYear(voteYear, '');

for (const country of countries) {
const concatenatedIds = await getRankingIds(voteYear, 'televote', 'final', countryContestants, country.key);
const concatenatedIds = await getRankingIds(voteYear, voteType, 'final', countryContestants, country.key);

if (codeCountryNameMap.has(concatenatedIds)) {
codeCountryNameMap.get(concatenatedIds)?.push(country);
Expand All @@ -47,49 +53,140 @@ const AnalyzeTab: React.FC = () => {
return sortedContestants.map((cc) => cc.id).join('');
};

// Find the most similar vote by country for the current ranking
// Find the most similar vote by country for the current ranking
const findMostSimilarVoteByCountry = async () => {
const voteYear = year;

const currentRankingCode = getUrlParam('r');
const codeCountryMap: Map<string, Country[]> = await getAllCountryRankCodes(voteType, 'final', year);
setCodeCountryNameMap(codeCountryMap);
const codeArrays = Array.from(codeCountryMap.keys());
const similarComparisons = await findMostSimilarLists(year, currentRankingCode!, codeArrays);
setMostSimilarComparisons(similarComparisons);
};

const codeCountryNameMap: Map<string, Country[]> = await getAllCountryRankCodes('televote', 'final', voteYear);

const codeArrays = Array.from(codeCountryNameMap.keys());

const mostSimilarComparisons = await findMostSimilarLists(voteYear, currentRankingCode!, codeArrays);

console.log(mostSimilarComparisons);

let countryNames = '';
// Find the most dissimilar vote by country for the current ranking
const findMostDissimilarVoteByCountry = async () => {
const currentRankingCode = getUrlParam('r');
const codeCountryMap: Map<string, Country[]> = await getAllCountryRankCodes(voteType, 'final', year);
setCodeCountryNameMap(codeCountryMap);
const codeArrays = Array.from(codeCountryMap.keys());
const dissimilarComparisons = await findMostDissimilarLists(year, currentRankingCode!, codeArrays);
setMostDissimilarComparisons(dissimilarComparisons);
};

for (const comparison of mostSimilarComparisons) {
const countries = codeCountryNameMap.get(comparison.list2Code);

function getCountryNamesFromComparisons(
mostSimilarComparisons: RankingComparison[],
codeCountryNameMap: Map<string, Country[]>
): string[] {
return mostSimilarComparisons
.flatMap(comparison => codeCountryNameMap.get(comparison.list2Code) ?? [])
.map(country => country.name);
}

// Get the title for the ranking link based on the vote type and country name
const getRankingTitle = (voteType: string, countryName: string) => {
return `Final ${voteType.charAt(0).toUpperCase() + voteType.slice(1)} from ${countryName}`;
};

for (const country of countries!) {
countryNames += `${country.name}, `;
}
}
// Get the URL for the ranking link based on the comparison, year, vote type, and country name
const getRankingUrl = (comparison: RankingComparison, countryName: string) => {
return `?r=${comparison.list2Code}&y=${year.substring(2, 4)}&n=${getRankingTitle(voteType, countryName).replaceAll(' ', '+')}&v=${voteType === 'televote' ? 'tv' : 'j'}`;
};

console.log(currentRankingCode);
console.log(countryNames);
// Format the percent similarity to the nearest tenth percent or percent if it's .0
const formatPercentSimilarity = (percent: number) => {
const roundedPercent = Math.round(percent * 10) / 10;
return roundedPercent % 1 === 0 ? roundedPercent.toFixed(0) : roundedPercent.toFixed(1);
};

return (
<div className="mb-0">
<p className="relative mb-[1em] mt-2 text-sm">
Compare your current ranking with others, including the Jury and/or Tele vote from each participating country
Compare your current ranking with the Jury or Tele vote from each participating country
</p>
<div className="mt-5 mb-[1.5em]">
<Dropdown
key="vote-type-selector"
className="z-50 ml-3 mx-auto mb-2"
menuClassName="w-auto"
value={voteType}
onChange={(v) => {
setVoteType(v);
setMostSimilarComparisons([]);
setMostDissimilarComparisons([]);
}}
options={['televote', 'jury']}
showSearch={false}
/>
<IconButton
className="ml-1 font-normal pl-[0.7em] rounded-md text-xs py-[0.5em] pr-[1em]"
className="ml-3 font-normal pl-[0.7em] rounded-md text-xs py-[0.5em] pr-[1em]"
onClick={async () => await findMostSimilarVoteByCountry()}
icon={undefined}
title="Most similar"
/>
<IconButton
className="ml-3 font-normal pl-[0.7em] rounded-md text-xs py-[0.5em] pr-[1em]"
onClick={async () => await findMostDissimilarVoteByCountry()}
icon={undefined}
title="Most dissimilar"
/>
</div>
{mostSimilarComparisons.length > 0 && (
<div className="mt-4">
<p className="text-sm">
Most similar {voteType} rankings:
</p>
<ul>
{mostSimilarComparisons.map((comparison, index) => (
<li key={index}>
{getCountryNamesFromComparisons([comparison], codeCountryNameMap).map((countryName, countryIndex) => (
<React.Fragment key={countryIndex}>
<a
href={getRankingUrl(comparison, countryName)}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline"
title={getRankingTitle(voteType, countryName)}
>
{countryName} ({formatPercentSimilarity(comparison.percentSimilarity)}%)
</a>
{countryIndex < getCountryNamesFromComparisons([comparison], codeCountryNameMap).length - 1 && ', '}
</React.Fragment>
))}
</li>
))}
</ul>
</div>
)}
{mostDissimilarComparisons.length > 0 && (
<div className="mt-4">
<p className="text-sm">
Most dissimilar {voteType} rankings:
</p>
<ul>
{mostDissimilarComparisons.map((comparison, index) => (
<li key={index}>
{getCountryNamesFromComparisons([comparison], codeCountryNameMap).map((countryName, countryIndex) => (
<React.Fragment key={countryIndex}>
<a
href={getRankingUrl(comparison, countryName)}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline"
title={getRankingTitle(voteType, countryName)}
>
{countryName} ({formatPercentSimilarity(comparison.percentSimilarity)}%)
</a>
{countryIndex < getCountryNamesFromComparisons([comparison], codeCountryNameMap).length - 1 && ', '}
</React.Fragment>
))}
</li>
))}
</ul>
</div>
)}
</div>
);
};

export default AnalyzeTab;
export default AnalyzeTab;
8 changes: 4 additions & 4 deletions src/components/modals/config/ConfigModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ const ConfigModal: React.FC<ConfigModalProps> = (props: ConfigModalProps) => {
label="Categories"
/>

{/* <TabButton
<TabButton
isActive={activeTab === 'analyze'}
onClick={() => setActiveTab('analyze')}
icon={faChartLine}
label="Analyze"
/> */}
/>
</ul>
</div>

Expand All @@ -91,9 +91,9 @@ const ConfigModal: React.FC<ConfigModalProps> = (props: ConfigModalProps) => {
<CategoriesTab/>
}

{/* {activeTab === 'analyze' &&
{activeTab === 'analyze' &&
<AnalyzeTab/>
} */}
}
</div>

</Modal>
Expand Down
6 changes: 2 additions & 4 deletions src/components/modals/config/RankingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import IconButton from '../../IconButton';
import { goToUrl } from '../../../utilities/UrlUtil';

const RankingsTab: React.FC = () => {
const dispatch = useDispatch();
const year = useSelector((state: AppState) => state.year);
const vote = useSelector((state: AppState) => state.vote);
const theme = useSelector((state: AppState) => state.theme);
const contestants = useSelector((state: AppState) => state.contestants);
const [rankingYear, setRankingYear] = useState(year);
const [voteSource, setVoteSource] = useState('All');
const [voteSourceOptions, setVoteSourceOptions] = useState<string[]>([
Expand All @@ -29,6 +26,7 @@ const RankingsTab: React.FC = () => {
useEffect(() => {
const updateVoteSourceOptions = async () => {
const yearContestants = await fetchCountryContestantsByYear(rankingYear);
console.log(yearContestants)
setVoteSourceOptions([
'All',
...yearContestants.map((cc) => cc.country).sort((a, b) => a.name.localeCompare(b.name)).map((c) => c.name),
Expand Down Expand Up @@ -136,7 +134,7 @@ const RankingsTab: React.FC = () => {
menuClassName=""
value={rankingYear ?? year}
onChange={(y) => setRankingYear(y)}
options={supportedYears.filter((i) => i !== '2024' && i !== '2020')}
options={supportedYears.filter((i) => i !== '2020')}
showSearch={true}
/>
<span className="ml-2 text-sm">{'from'}</span>
Expand Down
3 changes: 3 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ code {

.tada-animation {
animation: tada 12s ease-in-out infinite;
will-change: transform;
}

@keyframes bounceRight {
Expand Down Expand Up @@ -327,6 +328,7 @@ code {
align-items: center;
z-index: 100;
transition: transform 0.4s ease-in-out, opacity 0.4s ease-in-out;
will-change: transform;
opacity: 1;
}

Expand Down Expand Up @@ -454,6 +456,7 @@ https://codepen.io/chris22smith/pen/RZogMa */
.lyrics-container {
width: 100%;
transition: transform 0.3s ease-in-out;
will-change: transform;
}

.slide-out-left {
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/ContestantRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function fetchCountryContestantsByYear(
sanitizeYear(year) === cachedYear &&
!voteCodeHasSourceCountry(voteCode)
) {
return initialCountryContestantCache;
return JSON.parse(JSON.stringify(initialCountryContestantCache));;
}

return await fetchAndProcessCountryContestants(
Expand Down
34 changes: 33 additions & 1 deletion src/utilities/RankAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@ export async function findMostSimilarLists(
.filter(c => c.percentSimilarity === maxSimilarity);
}

/**
* Identifies the code from list2Codes that is most dissimilar to the list1code. If there
* are ties, all of the tied list2Codes are returned
*
* @param year
* @param list1Code
* @param list2Codes
* @returns
*/
export async function findMostDissimilarLists(
year: string,
list1Code: string,
list2Codes: string[]
): Promise<RankingComparison[]> {
const comparisons: RankingComparison[] = await Promise.all(
list2Codes.map(
list2Code => getRankingComparison(
year, list1Code, list2Code
)
)
);

const minSimilarity = Math.min(
...comparisons
.filter(c => !isNaN(c.percentSimilarity))
.map(c => c.percentSimilarity)
);

return comparisons
.filter(c => c.percentSimilarity === minSimilarity);
}

/**
* Identifies the code from list2Codes that is least similar to the list1code. If there
* are ties, all of the tied list2Codes are returned
Expand Down Expand Up @@ -211,7 +243,7 @@ function processRankComparisonForContestant(

let similarityScore = calculateSimilarityScore(
indexInList1,
indexInList2,
indexInList2,
listSize
);

Expand Down

0 comments on commit 93125b1

Please sign in to comment.