Commit d711df11 authored by Richard Schwarz's avatar Richard Schwarz
Browse files

wdaww

parent a3905183
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+4 −0
Original line number Original line Diff line number Diff line
/node_modules*
*csv

package*

public/main.css

0 → 100644
+92 −0
Original line number Original line Diff line number Diff line
body{
	background:#767a83;
	/*margin-top:50px;*/
	font: 15px "Trebuchet MS";
	color:#c0c0c0;

}


#map_div{
    width:45%;
    height:50vh;
    /*border:1px solid red;*/
    /*margin-right:10px;*/
    float:left;
}

#text_div{
    width:55%;
    height:50vh;
    /*border:1px solid red;*/
    font-size: calc(2vw + 2vh);
    float:left;
}
#barchart_div{
  width:100%;
  height:20vh;
  /*border:1px solid red;*/
  font-size: calc(2vw + 2vh);
  float:left;
}
#heatmap_div{
  width:100%;
  height:27vh;
  /*border:1px solid red;*/
  float:left;
}

.headline, .subline{
  display: block;
  margin-top: 0.67em;
  margin-bottom: 0.67em;
  margin-left: 0;
  margin-right: 0;
  fill: white;
  font-family: 'PT Sans',sans-serif;
  opacity: 0.5;
}

.headline{
  font-weight: bold;
}

.subline{
  font-size: 50%;
  font-weight: thin;
}

.label{
  font-family: 'PT Sans',sans-serif;
  opacity: 1;
  font-size: 30%;
}


#list_div div, #info_div, #selection_div {
  border: 1px solid black;
  margin: 3px;
}

#side_by_side_flex {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

#list_div {
  flex: 1;
  max-width: 300px; /* Optional: limit the width of the list_div */
}

#info_div {
  flex: 2;
  text-align: left;
}

#selection_div {
  border: 1px solid black;
  padding: 10px;
  margin: 5px;
  clear: both;
}
 No newline at end of file

public/mcuD3.js

0 → 100644
+204 −0
Original line number Original line Diff line number Diff line
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm"; // Import D3

// Global variables
var data;
var listDiv, infoDiv, selectionDiv, networkDiv;
var form;

// Load the CSV file and initialize the application
d3.csv("./public/movie_stats.csv").then(function (csvData) {
    // Store loaded data in global variable
    data = csvData;

    // Initialize the views
    init();

    // Visualize the data
    visualization();
});

/*----------------------
INITIALIZE VISUALIZATION
----------------------*/
function init() {
    // Select the divs
    listDiv = d3.select("#list_div");
    infoDiv = d3.select("#info_div");
    selectionDiv = d3.select("#selection_div");
    networkDiv = d3.select("#network_div");

    // Create a form element to hold the checkboxes
    form = listDiv.append("form");
}

/*----------------------
BEGINNING OF VISUALIZATION
----------------------*/
function visualization() {
    drawCheckboxes();
    drawNetworkGraph();
}

/*----------------------
DRAW CHECKBOXES
----------------------*/
function drawCheckboxes() {
    // Add checkboxes to the form
    const rows = form.selectAll("div")
        .data(data)
        .enter()
        .append("div")
        .on("mouseover", function (event, d) {
            displayMovieInfo(d);
        });

    rows.append("label")
        .text(d => d.movie_title)
        .append("input")
        .attr("type", "checkbox")
        .attr("value", d => d.movie_title)
        .attr("name", "movies")
        .on("change", function (event, d) {
            updateSelectedMovies();
        });
}

/*----------------------
DISPLAY MOVIE INFO
----------------------*/
function displayMovieInfo(movie) {
    infoDiv.html(`
    <h2>${movie.movie_title}</h2>
    <p>MCU Phase: ${movie.mcu_phase}</p>
    <p>Release Date: ${movie.release_date}</p>
    <p>Tomato Meter: ${movie.tomato_meter}%</p>
    <p>Audience Score: ${movie.audience_score}%</p>
    <p>Movie Duration: ${movie.movie_duration} minutes</p>
    <p>Production Budget: ${movie.production_budget}</p>
    <p>Opening Weekend: ${movie.opening_weekend}</p>
    <p>Domestic Box Office: ${movie.domestic_box_office}</p>
    <p>Worldwide Box Office: ${movie.worldwide_box_office}</p>
  `);
}

/*----------------------
UPDATE SELECTED MOVIES
----------------------*/
function updateSelectedMovies() {
    const selectedMovies = form.selectAll("input:checked").nodes().map(node => node.value);
    selectionDiv.html(selectedMovies.join(", "));
}

/*----------------------
DRAW NETWORK GRAPH
----------------------*/
function drawNetworkGraph() {
    // Load nodes and edges in parallel
    Promise.all([
        d3.csv("./public/movies_characters_list.csv"),   // contains name, type
        d3.csv("./public/movies_characters_occurences.csv") // contains movie, character
    ]).then(([listData, edgesData]) => {
        const nodes = listData.map(d => ({
            id: d.name,
            group: d.type
        }));

        const links = edgesData.map(d => ({
            source: d.movie,
            target: d.character
        }));

        const width = 800;
        const height = 600;
        const radius = 10; // node radius

        // Build an adjacency list so we know the neighbors for each node
        const adjacency = {};
        nodes.forEach(n => {
            adjacency[n.id] = [];
        });

        links.forEach(l => {
            adjacency[l.source].push(l.target);
            adjacency[l.target].push(l.source);
        });

        const svg = networkDiv.append("svg")
            .attr("width", width)
            .attr("height", height);

        const simulation = d3.forceSimulation(nodes)
            .force("link", d3.forceLink(links).id(d => d.id).distance(20))
            // .force("charge", d3.forceManyBody().strength(-20))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("collide", d3.forceCollide().radius(20).strength(1))
            .force("gravity", d3.forceManyBody().strength(10))
            ;

        const link = svg.append("g")
            .attr("class", "links")
            .selectAll("line")
            .data(links)
            .enter()
            .append("line")
            .attr("stroke-width", 2)
            .attr("stroke", "#999");

        const node = svg.append("g")
            .attr("class", "nodes")
            .selectAll("circle")
            .data(nodes)
            .enter()
            .append("circle")
            .attr("r", radius)
            .attr("fill", d => d.group === "movie" ? "blue" : "red");

        node.append("title")
            .text(d => d.id);

        // On mouseover, highlight neighbors and dim others
        node.on("mouseover", function(event, d) {
            // Gather neighbors from adjacency list
            const neighbors = adjacency[d.id] || [];

            // Highlight this node and its neighbors, dim others
            node.attr("opacity", n => {
                return n.id === d.id || neighbors.includes(n.id) ? 1 : 0.2;
            });

            // Highlight links connected to this node or its neighbors
            link.attr("opacity", l => {
                // A link is visible if it connects the hovered node or a neighbor
                return (l.source.id === d.id || l.target.id === d.id ||
                        neighbors.includes(l.source.id) || neighbors.includes(l.target.id))
                       ? 1 : 0.2;
            });
        });

        // On mouseout, reset all to full opacity
        node.on("mouseout", function() {
            node.attr("opacity", 1);
            link.attr("opacity", 1);
        });

        simulation.on("tick", () => {
            link
                .attr("x1", d => d.source.x)
                .attr("y1", d => d.source.y)
                .attr("x2", d => d.target.x)
                .attr("y2", d => d.target.y);

            node
                .attr("cx", d => {
                    // Clamp x within [radius, width - radius]
                    d.x = Math.max(radius, Math.min(width - radius, d.x));
                    return d.x;
                })
                .attr("cy", d => {
                    // Clamp y within [radius, height - radius]
                    d.y = Math.max(radius, Math.min(height - radius, d.y));
                    return d.y;
                });
        });
    });
}
 No newline at end of file

public/myD3app.js

0 → 100644
+294 −0
Original line number Original line Diff line number Diff line
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm"; //import D3

//variable containing reference to data
var data;

//D3.js canvases
var textArea;
var barChartArea;
var heatMap;

//D3.js svg elements
var selectedAreaText;

//variables for selection
var selectedRegion;

var topValue;
var labelWidth;
var barWidth;

var myColorScale;



/*Loading data from CSV file and editing the properties to province codes. Unary operator plus is used to save the data as numbers (originally imported as string)*/
d3.csv("./public/criminality.csv", function (d) {
  return {
    date: d["Time Unit"],
    Czech_Republic: +d["Average"],
    Central_Bohemia_Region: +d["Central Bohemia Region"],
    South_Bohemian_Region: +d["South Bohemian Region"],
    Pilsen_Region: +d["The Pilsen Region"],
    Usti_Region: +d["The Ústí Region"],
    Hradec_Kralove_Region: +d["Hradec Králové Region"],
    Southern_Moravia_Region: +d["Southern Moravia Region"],
    Moravia_Silesia_Region: +d["Moravian- Silesian Region"],
    Olomouc_Region: +d["The Olomouc Region"],
    Zlin_Region: +d["Zlín Region"],
    Vysocina_Region: +d["Vysočina Region"],
    Pardubice_Region: +d["The Pardubice Region"],
    Liberec_Region: +d["Liberec Region"],
    Karlovy_Vary_Region: +d["Karlovy Vary Region"],
    City_of_Prague: +d["City of Prague"]
  }
})
  .then(function (csvData) {
    //store loaded data in global variable
    data = csvData;

    //load map and initialise the views
    init();

    // data visualization
    visualization();
  });

/*----------------------
INITIALIZE VISUALIZATION
----------------------*/
function init() {

  let width = screen.width;
  let height = screen.height;

  //init selections
  selectedRegion = 'Czech_Republic'

  //retrieve an SVG file
  d3.svg("public/map.svg")
    .then((d) => {
      d3.select("#map_div").node().append(d.documentElement)

      d3.select("#map_div").select("svg")
        .attr("id", "map")
        .attr("width", width / 2)
        .attr("height", height / 2)
        .attr("x", 0)
        .attr("y", 0);

      let map = d3.select("body").select("#map");

      map.selectAll("path")
        .style("fill", "lightgray")
        .style("stroke", "gray")
        .style("stroke-width", 3)
        .on("click", function () {
          mapClick(this.id);
        });
    })

  //d3 canvases for svg elements
  textArea = d3.select("#text_div").append("svg")
    .attr("width", d3.select("#text_div").node().clientWidth)
    .attr("height", d3.select("#text_div").node().clientHeight);

  barChartArea = d3.select("#barchart_div").append("svg")
    .attr("width", d3.select("#barchart_div").node().clientWidth)
    .attr("height", d3.select("#barchart_div").node().clientHeight);

  heatMap = d3.select("#heatmap_div").append("svg")
    .attr("width", d3.select("#heatmap_div").node().clientWidth)
    .attr("height", d3.select("#heatmap_div").node().clientHeight);

   //topValue calculation
   topValue = 0;

  for (let index = 0; index < data.length; index++) {
    for (let key in data[index]) {
      if (key != 'date') {
        if (topValue < data[index][key]) {
          topValue = data[index][key]
        }
      }
    }
  }

  console.log("Top value:" + topValue) 

  //colorScale setUp
  myColorScale = d3.scaleSequential().domain([0, topValue]).interpolator(d3.interpolatePlasma)
}


/*----------------------
BEGINNING OF VISUALIZATION
----------------------*/
function visualization() {

  drawTextInfo();

  drawBarChart(selectedRegion);

  drawHeatMap();

}

/*----------------------
TASKS:
1) Create a bar chart of the number of average crminality index over the time 
2) Create a heat map for all regions in the dataset
3) Connect SVG map with the bar chart (select region on map)
4) Animate bar chart transitions
5) Connect heatmap with map (implement choropleth) + indicator of selected time step
6) Add legend

----------------------*/

/*----------------------
TEXT INFORMATION
----------------------*/
function drawTextInfo() {
  //Draw headline
  textArea.append("text")
    .attr("dx", 20)
    .attr("dy", "3em")
    .attr("class", "headline")
    .text("Criminality Index in Czech Republic");

  //Draw source
  textArea.append("text")
    .attr("dx", 20)
    .attr("dy", "7.5em")
    .attr("class", "subline")
    .text("Data source: mapakriminality.cz")
    .on("click", function () { window.open("https://www.mapakriminality.cz/data/"); });;

  //Draw selection information
  selectedAreaText = textArea.append("text")
    .attr("dx", 20)
    .attr("dy", "10em")
    .attr("class", "subline")
    .text("Selected Region: " + selectedRegion.replace(/_/g, " "));


}


/*----------------------
BAR CHART
----------------------*/
function drawBarChart(region) {
  //get area width/height
  let thisCanvasHeight = barChartArea.node().clientHeight
  let thisCanvasWidth = barChartArea.node().clientWidth

  labelWidth = (1 / 8) * barChartArea.node().clientWidth;
  barWidth = ((7 / 8) * barChartArea.node().clientWidth) / data.length;

  for (let index = 0; index < data.length; index++) {
    var barHeight = (data[index][region] / topValue) * thisCanvasHeight;

    barChartArea.append('rect')
      .attr("x", labelWidth + (index * barWidth))
      .attr("y", thisCanvasHeight - barHeight)
      .attr("width", barWidth)
      .attr("height", barHeight)
      .attr("fill", "darkblue")
  }


  var year = "";
  for (let index = 0; index < data.length; index++) {
    if (data[index].date.substr(0, 4) != year) {
      year = data[index].date.substr(0, 4)
      barChartArea.append('text')
        .attr("x", labelWidth + (index * barWidth))
        .attr("y", thisCanvasHeight)
        .attr("class", "subline")
        .style("fill", "white")
        .text(year)
    }

  }

  var yscale = d3.scaleLinear()
    .domain([0, topValue])
    .range([thisCanvasHeight, 0]);

  barChartArea.append("g")
    .attr("transform", `translate(${labelWidth},0)`)
    .call(d3.axisLeft(yscale))

  //Square transition example
  /*barChartArea.append('rect')
    .attr("x", thisCanvasWidth / 3) 
    .attr("y", thisCanvasHeight / 3) 
    .attr("width", 80) 
    .attr("height", 80) 
    .attr("fill", "red" )
    .transition()
      .duration(5000)
      .attr("x", 2 * thisCanvasWidth / 3)
      .attr("y", 2 * thisCanvasHeight / 3)
      .attr("width", 40)
      .attr("height", 40) 
      .attr("fill", "blue" );*/
}

/*----------------------
HEAT MAP
----------------------*/
function drawHeatMap() {


  let thisCanvasHeight = heatMap.node().clientHeight

  //calculate heatmap row height
  var rowHeight = thisCanvasHeight / 14 //we have 14 regions

  //initialize starting position for the rows
  var yPosition = 0

  //iterate over different regions - i.e., columns of the data; skip date column and whole Czech Republic 
  for (var key in data[0]) {
    if (key != 'date' && key != 'Czech_Republic') {

      //append region label
      heatMap.append("text")
        .attr("x", labelWidth)
        .attr("y", yPosition + rowHeight)
        .attr("class", "subline")
        .attr("text-anchor", "end") //text alignment anchor - end means that the 'x' postion attribute will specify the position of the text end (value can be start/middle/end)
        .style('fill', 'white')
        .style("font-size", rowHeight)
        .text(key.replace(/_/g, " ")) //specify the text, the replace fuction with regex expression '/_/g' is used to find all underscores in the string and replace them with space character

      //iterate over the values for the region  
      for (let index = 0; index < data.length; index++) {

        //skip zero values (missing data for Prague)
        if (data[index][key] != 0) {

          //append rectagle representing the value to heatmap
          heatMap.append('rect')
            .attr("x", labelWidth + index * barWidth)
            .attr("y", yPosition)
            .attr("width", barWidth)
            .attr("height", rowHeight)
            .attr("fill", myColorScale(data[index][key]))
        }
      }

      //after each region, increase yPosition of the heatmap row
      yPosition += rowHeight
    }
  }
}

/*----------------------
INTERACTION
----------------------*/
function mapClick(region) {
  console.log(region)
}