Learn how to create a visually appealing and insightful dashboard using Evidence.dev. This guide walks you through setting up data connections and generating dynamic content, ideal for developers up for a side project.

Creating a compelling and insightful dashboard is essential for presenting data in a way that is both informative and engaging. In this blog post, we'll walk through the process of building a one-pager statistics page for FC24 players using Evidence.dev. This dashboard will provide high-level statistics, highlight top players, and showcase data on countries and clubs.

Prerequisites

Before diving into the code, ensure you have the following:

Evidence.dev: An open-source data analytics framework. Get more info on how to get started on the website of the official documentation. FC24 Players Data: A CSV file containing player statistics.

The application repository

For people who really don't want to read the full article but just want to get started. Visit the repo below.

Get started... Clone it

Open your command line in the folder you just created and...

Run -> npm install

Run -> npm run sources

Run -> npm run dev

Or if you want to know more ->

???? What you will be building -> https://fc24.fact.ist/

Connecting to the data

First, we need to connect to our data. This is done using the settings option after opening up evidence. Go to settings by clicking on the three dots on the top right. We then create a csv connection which creates a folder with two YAML files in it. In that folder we can store the csv files we want to be part of our app.

Read more about using csv files in the docs.

Setting up the data

Second, we need to load our data from the CSV file into Evidence.dev. This is done using the select statement, which reads data from our CSV file into a variable named recs . See how the csv.-prefix tells evidence to use the csv folder for getting the data.

select * from csv.fc24_players

❔ Evidence.dev uses DuckDB in the browser under the hood. This means that every time you use 'npm run sources' the configured data connections and sets are imported into duckdb which then gets used by evidence and its capabilities.

Initializing the dashboard

The dashboard starts with a title and brief description, providing context about the FC24 statistics app. The recs variable is used to reference our data throughout the dashboard.

<h1>FC24 Statistics</h1> <p>This app hosts analyses and insights to understand the <a class="link" href="https://www.ea.com/nl-nl/games/ea-sports-fc/fc-24" target="_blank">FC24 </a> players and teams data better.</p> <hr class="uk-margin uk-divider-icon" />

High-level statistics

We calculate several high-level statistics such as total players, countries, clubs, and average metrics (e.g., age, height, weight, rating, wage, potential, value). This is achieved using SQL aggregate functions.

select count(*) as total_players, count(distinct nationality_name) as total_countries, count(distinct club_name) as total_clubs, avg(age) as average_age, avg(height_cm) as average_height, avg(weight_kg) as average_weight, avg(overall) as average_rating, avg(wage_eur) as average_wage, avg(potential) as average_potential, avg(value_eur) as average_value from ${recs} recs

These statistics are then displayed using the BigValue component, which provides a clean and visually appealing way to present key metrics.

<Grid cols=5> <div> <BigValue title="Total Players" data={totals} value=total_players fmt='#,##0' /> </div> <!-- Look into the code of the page to see the full script --> </Grid> <hr class="uk-margin uk-divider-icon" />

Click here for a full view of the page.

Top players based on rating

Next, we identify the top 12 players based on their overall rating. We use SQL to group and order the players by their rating.

select player_id as pid, 'https://cdn.fifacm.com/content/media/imgs/fc24/players/p' || player_id::INTEGER::VARCHAR ||'.png' as img, long_name, short_name, club_name, club_position, nationality_name, player_traits, avg(overall) as overall_rating, avg(potential) as potential_rating from ${recs} recs group by 1,2,3,4,5,6,7,8 order by 9 desc limit 12

We then display these top players in a grid format, including their image, name, club, and nationality.

<h3>Top 12 Players based on Rating</h3> <Grid cols=3> {#each player_potential as pp} <div> <div class="uk-card uk-card-default uk-card-body"> <img class="uk-preserve-width uk-border-circle uk-align-right" style="border:1px solid #ccc;" src="{pp.img}" width="80" alt=""> <span class="uk-preserve-width uk-border-circle uk-position-small uk-position-top-right uk-text-center" style="width:30px;height:30px;border:1px solid #ccc;padding:5px;"> {pp.overall_rating}</span> <h3 class="uk-card-title">{pp.short_name}</h3> <p><a class="link" href="/{pp.club_name}/">{pp.club_name}</a> / {pp.nationality_name}</p> </div> </div> {/each} </Grid> <hr class="uk-margin uk-divider-icon">

What does this code do? The provided code snippet is designed to display a section titled "Top 12 Players based on Rating" on a webpage. Here's a high-level summary of how it works: Section Header : <h3>Top 12 Players based on Rating</h3> : This line sets the title for the section, informing users that the following content will display the top 12 players based on their rating. Grid Layout : <Grid cols=3> : Initializes a grid layout with three columns, ensuring that the player cards will be arranged in a three-column format for better visual organization. Dynamic Content Generation : {#each player_potential as pp} : Begins a loop to iterate over the player_potential array. For each player object in this array, a block of HTML will be generated dynamically. Player Card Structure : <div><div class="uk-card uk-card-default uk-card-body"> : Creates a card for each player. The card is styled using UIkit classes to have a default appearance and a body section. Player Image : <img class="uk-preserve-width uk-border-circle uk-align-right" style="border:1px solid #ccc;" src="{pp.img}" width="80" alt=""> : Displays the player's image, styled to be circular, aligned to the right, and bordered. The image source URL is dynamically set from the player data. Player Rating Badge : <span class="uk-preserve-width uk-border-circle uk-position-small uk-position-top-right uk-text-center" style="width:30px;height:30px;border:1px solid #ccc;padding:5px;">{pp.overall_rating}</span> : Displays the player's overall rating inside a styled badge, positioned in the top-right corner of the card. Player Name and Club Information : <h3 class="uk-card-title">{pp.short_name}</h3> : Displays the player's short name as the card title.

<p><a class="link" href="/{pp.club_name}/">{pp.club_name}</a> / {pp.nationality_name}</p> : Displays the player's club name as a clickable link and their nationality. The link dynamically routes to a URL based on the club name. Closing Tags : {/each} : Closes the loop that iterates over the player data.

</Grid> : Closes the grid layout. Section Divider : <hr class="uk-margin uk-divider-icon"> : Adds a horizontal rule (divider) to visually separate this section from others on the webpage. The code dynamically generates and displays a grid of player cards, each containing an image, rating, name, club, and nationality of the top 12 players based on their rating. The grid layout and card styling are handled using UIkit classes, ensuring a consistent and visually appealing presentation. The dynamic content generation is facilitated by looping through the player_potential array, making the code flexible and easily updatable with new data.

Top 20 countries

We analyze the top 20 countries based on the number of players. This involves grouping by nationality and calculating various metrics.

select nationality_id, nationality_name, count(*) as total, count(distinct club_name) as total_clubs, avg(age) as average_age, avg(height_cm) as average_height, avg(weight_kg) as average_weight, avg(overall) as average_rating, avg(wage_eur) as average_wage, avg(potential) as average_potential, avg(value_eur) as average_value from ${recs} recs group by 1,2 order by 3 desc limit 20

We visualize the data using bar charts to compare the number of players and clubs for each country.

<h3>Top 20 Countries</h3> <Grid cols=2> <div> <BarChart title='Number of Players' data={by_country} x=nationality_name y=total swapXY=true colorPalette={['#06871A']} renderer=svg /> </div> <div> <BarChart title='Number of Clubs' data={by_country} x=nationality_name y=total_clubs swapXY=true colorPalette={['#06871A']} renderer=svg /> </div> </Grid> <hr class="uk-margin uk-divider-icon" />

Top 20 clubs - skills

Finally, we analyze the top 20 clubs based on various skills such as pace, shooting, passing, dribbling, defending, and physic. We rank the clubs and calculate normalized scores for each skill.

WITH RankedClubs AS ( SELECT club.club_name, club.club_team_id, AVG(club.overall)::INTEGER AS overall, AVG(club.pace)::INTEGER as pace, AVG(club.shooting)::INTEGER as shooting, AVG(club.passing)::INTEGER as passing, AVG(club.dribbling)::INTEGER as dribbling, AVG(club.defending)::INTEGER as defending, AVG(club.physic)::INTEGER as physic, AVG(club.mentality_composure)::INTEGER as composure FROM ${recs} club GROUP BY club.club_name, club.club_team_id ORDER BY overall DESC LIMIT 20 ) SELECT 'score' as ph, club_name, club_team_id, overall, 1 - ((MAX(overall) OVER () - overall) / (MAX(overall) OVER () - MIN(overall) OVER ())) + 0.1 as rank_overall, pace, 1 - ((MAX(pace) OVER () - pace) / (MAX(pace) OVER () - MIN(pace) OVER ())) + 0.1 as rank_pace, shooting, 1 - ((MAX(shooting) OVER () - shooting) / (MAX(shooting) OVER () - MIN(shooting) OVER ())) + 0.1 as rank_shooting, passing, 1 - ((MAX(passing) OVER () - passing) / (MAX(passing) OVER () - MIN(passing) OVER ())) + 0.1 as rank_passing, dribbling, 1 - ((MAX(dribbling) OVER () - dribbling) / (MAX(dribbling) OVER () - MIN(dribbling) OVER ())) + 0.1 as rank_dribbling, defending, 1 - ((MAX(defending) OVER () - defending) / (MAX(defending) OVER () - MIN(defending) OVER ())) + 0.1 as rank_defending, physic, 1 - ((MAX(physic) OVER () - physic) / (MAX(physic) OVER () - MIN(physic) OVER ())) + 0.1 as rank_physic, composure, 1 - ((MAX(composure) OVER () - composure) / (MAX(composure) OVER () - MIN(composure) OVER ())) + 0.1 as rank_composure FROM RankedClubs ORDER BY overall DESC

We display the data in a table, using background colors to indicate the relative strength of each skill.

<h3>Top 20 Clubs - Skills</h3> <table class="uk-table uk-table-small uk-table-divider"> <thead> <tr> <th style="width:2%;text-align:center;"></th> <th>Club</th> <th style="width:8%;text-align:center;">Overall</th> <th style="width:8%;text-align:center;">Pace</th> <th style="width:8%;text-align:center;">Shooting</th> <th style="width:8%;text-align:center;">Passing</th> <th style="width:8%;text-align:center;">Dribbling</th> <th style="width:8%;text-align:center;">Defending</th> </tr> </thead> <tbody> {#each by_club as hm} <tr> <td style="text-align:center;"> <img class="uk-preserve-width uk-border-circle" src="https://fifastatic.fifaindex.com/FIFA24/teams/light/ {hm.club_team_id}.png" width="24" height="24" alt=""> </td> <td>{hm.club_name}</td> <td style="text-align:center; background-color:rgba(0,163,20,{hm.rank_overall});"> {hm.overall} </td> <td style="text-align:center; background-color:rgba(0,163,20,{hm.rank_pace});"> {hm.pace} </td> <td style="text-align:center; background-color:rgba(0,163,20,{hm.rank_shooting});"> {hm.shooting} </td> <td style="text-align:center; background-color:rgba(0,163,20,{hm.rank_passing});"> {hm.passing} </td> <td style="text-align:center; background-color:rgba(0,163,20,{hm.rank_dribbling});"> {hm.dribbling} </td> <td style="text-align:center; background-color:rgba(0,163,20,{hm.rank_defending});"> {hm.defending} </td> </tr> {/each} </tbody> </table>

Conclusion

By leveraging Evidence.dev, we've created a comprehensive and visually appealing dashboard that provides valuable insights into FC24 player statistics. This dashboard includes high-level metrics, top player highlights, and detailed analyses of countries and clubs. Whether you're a data analyst or a football enthusiast, this dashboard offers a powerful tool for exploring and understanding FC24 data.

What should I have now? Click the button below to see what you built... Show me

Stay tuned for next updates and articles. You can also subscribe to get more content on data and information design in your inbox.