Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,30 @@ const Footer = () => {

return (
<div className="footer inline-flex">
{switchButton(router)}
<a href="https://github.com/alexisthual/symptoms-tracker">
<button className="btn btn-link">
<section className="navbar-section">
<a href="/" className="btn btn-link">
<FormattedMessage id="home" />
</a>
<a href="/about" className="btn btn-link">
<FormattedMessage id="about" />
</a>
<a
href="https://github.com/alexisthual/symptoms-tracker"
className="btn btn-link"
>
<FormattedMessage id="sourcecode" />
</button>
</a>
</a>
</section>
</div>

// <div className="footer inline-flex">
// {switchButton(router)}
// <a href="https://github.com/alexisthual/symptoms-tracker">
// <button className="btn btn-link">
// <FormattedMessage id="sourcecode" />
// </button>
// </a>
// </div>
);
};

Expand Down
10 changes: 5 additions & 5 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Link from "next/link";
import { FormattedMessage } from "react-intl";
import Head from "next/dist/next-server/lib/head";

interface IHeaderProps {
language?: string;
Expand Down Expand Up @@ -31,11 +32,10 @@ const Header = ({}: IHeaderProps) => (
<section className="navbar-section"></section>
<section className="navbar-center">
<Link href="/">
<button className="btn btn-link">
<h4>
<FormattedMessage id="title" />
</h4>
</button>
<div className="inline-flex flex-centered">
<img src="coronastatus_logo.png" height="100" />
<img src="coronastatus_logo_text_dark.png" height="100" />
</div>
</Link>
</section>
<section className="navbar-section px-2"></section>
Expand Down
215 changes: 215 additions & 0 deletions components/Map/chart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { geoPath, geoConicConformal } from "d3-geo";
import { scaleLinear, scaleQuantile } from "d3-scale";
import { axisLeft } from "d3-axis";
import { select, selectAll, mouse } from "d3-selection";
import { max, min, range } from "d3-array";

const colors = [
"#FFEBE1", // Not enough answers
"#FFD0BA",
"#FEBC9C",
"#FFA87F",
"#FF8D58",
"#FF8044",
"#FE6C27",
"#FF5200" // Enough answers
];
const LEGEND_HEIGHT = 300;
const LEGEND_SIZE = Math.ceil(LEGEND_HEIGHT / colors.length);

const REGION_HOVER_COLOR = "#333";

function addTooltip(svg) {
var tooltip = svg
.append("g") // Group for the whole tooltip
.attr("id", "tooltip")
.style("display", "none");

tooltip
.append("polyline") // The rectangle containing the text, it is 210px width and 60 height
.attr("points", "0,0 210,0 210,150 0,150 0,0")
.style("fill", "#222b1d")
.style("stroke", "black")
.style("opacity", "0.9")
.style("stroke-width", "1")
.style("padding", "1em");

tooltip
.append("line") // A line inserted between region name and score
.attr("x1", 40)
.attr("y1", 25)
.attr("x2", 160)
.attr("y2", 25)
.style("stroke", "#929292")
.style("stroke-width", "0.5")
.attr("transform", "translate(0, 5)");

var text = tooltip
.append("text") // Text that will contain all tspan (used for multilines)
.style("font-size", "13px")
.style("fill", "#c1d3b8")
.attr("transform", "translate(0, 20)");

text
.append("tspan") // Region name udpated by its id
.attr("x", 105) // ie, tooltip width / 2
.attr("y", 0)
.attr("id", "tooltip-region")
.attr("text-anchor", "middle")
.style("font-weight", "600")
.style("font-size", "16px");

text
.append("tspan") // Fixed text
.attr("x", 105) // ie, tooltip width / 2
.attr("y", 30)
.attr("text-anchor", "middle")
.style("fill", "929292")
.text("Réponses : ");

text
.append("tspan") // Score udpated by its id
.attr("id", "tooltip-score")
.style("fill", "#c1d3b8")
.style("font-weight", "bold");

text
.append("tspan") // Fixed text
.attr("x", 105) // ie, tooltip width / 2
.attr("y", 60)
.attr("text-anchor", "middle")
.style("fill", "929292")
.text("Malades :");

text
.append("tspan") // Score udpated by its id
.attr("id", "tooltip-ill")
.style("fill", "#c1d3b8")
.style("font-weight", "bold");

text
.append("tspan") // Fixed text
.attr("x", 105) // ie, tooltip width / 2
.attr("y", 90)
.attr("text-anchor", "middle")
.style("fill", "929292")
.text("Soignés :");

text
.append("tspan") // Score udpated by its id
.attr("id", "tooltip-recovered")
.style("fill", "#c1d3b8")
.style("font-weight", "bold");

text
.append("tspan") // Fixed text
.attr("x", 105) // ie, tooltip width / 2
.attr("y", 120)
.attr("text-anchor", "middle")
.style("fill", "929292")
.text("Bien portant :");

text
.append("tspan") // Score udpated by its id
.attr("id", "tooltip-well")
.style("fill", "#c1d3b8")
.style("font-weight", "bold");

return tooltip;
}

export const createChart = async ({ id, geojson, csv }) => {
const width = document.getElementById(id).offsetWidth * 0.95;
const height = 400;

const path = geoPath();

const projection = geoConicConformal() // Lambert-93
.center([2.454071, 46.279229]) // Center on France
.scale(2000)
.translate([width / 2, height / 2]);

path.projection(projection);

const svg = select(`#${id}`)
.append("svg")
.attr("id", "svg")
.attr("width", width)
.attr("height", height)
.attr("class", "Blues");

const deps = svg.append("g");

// Draw regions
deps
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("id", d => `code-${d.properties.code}`)
.attr("d", path);

var quantile = scaleQuantile()
.domain([0, 100])
.range(range(colors.length));

// Create the legend
var legend = svg.append("g").attr("transform", "translate(550, 25)");

legend
.selectAll()
.data(range(colors.length))
.enter()
.append("svg:rect")
.attr("height", `${LEGEND_SIZE}px`)
.attr("width", `${LEGEND_SIZE}px`)
.attr("x", 5)
.attr("y", d => d * LEGEND_SIZE)
.style("fill", d => colors[d]);

// Create the legend scale (from 0% to 100%)
const minLegend = min(csv, () => 0);
const maxLegend = max(csv, () => 100);
var legendScale = scaleLinear()
.domain([minLegend, maxLegend])
.range([0, colors.length * LEGEND_SIZE]);

const legendAxis = legend
.append("g")
.attr("class", "axis")
.call(axisLeft(legendScale));

const tooltip = addTooltip(svg);

//
csv.forEach(function(e, i) {
const { INSEE, TOTAL, REGION, ILL, WELL, RECOVERED, TARGET_PERCENT } = e;
const region = select(`#code-${INSEE}`);
const regionColor = colors[quantile(TARGET_PERCENT)];
region.style("fill", () => regionColor).style("stroke-width", "0.5");

region.on("mouseover", function(d) {
region.style("stroke", REGION_HOVER_COLOR);
tooltip.style("display", "block");
tooltip.select("#tooltip-region").text(REGION);
tooltip.select("#tooltip-score").text(TOTAL);
tooltip.select("#tooltip-ill").text(ILL);
tooltip.select("#tooltip-well").text(WELL);
tooltip.select("#tooltip-recovered").text(RECOVERED);
});

region.on("mouseout", function() {
region.style("fill", d => regionColor);
region.style("stroke", "none");
tooltip.style("display", "none");
legend.select("#cursor").style("display", "none");
});

region.on("mousemove", function() {
var [x, y] = mouse(this);
tooltip.attr("transform", "translate(" + (x + 20) + "," + (y - 75) + ")");
});
});

return Promise.resolve();
};
52 changes: 52 additions & 0 deletions components/Map/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useState, useEffect, useMemo } from "react";
import { FormattedMessage } from "react-intl";

import { createChart } from "./chart";
// Fow now, import FR data directly.
import csv from "../../public/map-data/fr/data.csv";
import map from "../../public/map-data/fr/map.json";

const FranceMap = () => {
const mapID = "map";
if (process.browser) {
const [ready, setReady] = useState(false);
useEffect(() => {
if (!ready) {
createChart({
id: mapID,
geojson: map,
csv
}).then(() => {
setReady(true);
});
}
}, [ready]);
}

const total = useMemo(() => {
return Object.values(csv).reduce(
(acc, { TOTAL }) => acc + Number(TOTAL),
0
);
}, [csv]);

return (
<>
<div>
<p className="h5 flex-centered">
{" "}
<FormattedMessage id="home.map.title" />{" "}
</p>
<div
id={mapID}
style={{ maxWidth: "100%", margin: "0 auto", width: "600px" }}
/>
<p className="empty-subtitle flex-centered">
<FormattedMessage id="home.total.submission" /> {total}
</p>
</div>
</>
);
};

export default FranceMap;
3 changes: 2 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
"send": "Send",
"sent": "Sent!",
"about": "About",
"home": "Home"
"home": "Home",
"home.total.submission": "Number total of submissions:"
}
14 changes: 10 additions & 4 deletions lang/fr.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"title": "Corona Status",
"disclaimer": "Ce site est une initiative personnelle et ne livre pas d'avis médical. Ce questionnaire vise à faciliter le suivi du développement des cas réels du COVID-19 au sein des populations confinées. Aucune donnée personnelle ou identifiante n'est collectée.",
"support": "Aidez-nous à estimer et prédire la propagation du COVID-19 en France en remplissant ce questionnaire. <b>Votre réponse est utile que soyez bien portant·e, souffrant·e ou rétabli·e</b>.",
"takesurvey": "Remplir le questionnaire",
"disclaimer": "Ce site est une initiative citoyenne et ne livre pas d'avis médical. Ce questionnaire vise à faciliter le suivi du développement des cas réels du COVID-19 au sein des populations confinées. Aucune donnée personnelle ou identifiante n'est collectée.",
"support": "Aidez-nous à estimer et prédire la propagation du COVID-19 en France en remplissant ce questionnaire. <b>Votre réponse est utile que soyez bien portant(.e), souffrant(.e) ou rétabli(.e) </b>.",
"takesurvey": "Je me signale",
"usage": "Aucune donnée personnelle ou identifiante n'est collectée. L'ensemble des résultats est publiquement accessible.",
"knowmore": "En savoir plus",
"modal.intro.title": "Je me signale",
"modal.intro.understand": "J'ai compris",
"modal.intro.content": "Vous vous apprêtez à remplir un formulaire destiné à collecter anonymement des renseignements sur votre état de santé actuel dans le cadre d'une campagne d'information collaborative sur l'évolution de la pandémie COVID-19. Aucune donnée personnelle ou identifiante n'est collectée. En cliquant sur le bouton suivant, vous acceptez de partager vos données anonymisées.",
"modal.title.success": "Merci pour votre participation !",
"modal.content.success": "Pour continuer de nous aider, vous pouvez :",
"modal.content.success.list.0": "faire remplir ce questionnaire aux personnes confinées avec vous",
Expand Down Expand Up @@ -130,5 +133,8 @@
"about.coronastatus.link": "En savoir plus sur le projet Corona Status.",
"share.title": "Partager sur les réseaux sociaux ",
"share.url": "https://coronastatus.fr",
"share.quote": "Aidez-nous à estimer et prédire la propagation du COVID-19 en France en remplissant ce questionnaire."
"share.quote": "Aidez-nous à estimer et prédire la propagation du COVID-19 en France en remplissant ce questionnaire.",
"home.total.submission": "Nombre total de réponses :",
"home.support.title": "Collecte de données participative",
"home.map.title": "Répartition des réponses"
}
22 changes: 19 additions & 3 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
const withCss = require("@zeit/next-css");
const withSass = require("@zeit/next-sass");
const withCss = require('@zeit/next-css');
const withSass = require('@zeit/next-sass');

module.exports = withSass(withCss());
module.exports = withSass(
withCss({
webpack(config) {
config.module.rules.push({
test: /\.csv$/,
loader: 'csv-loader',
options: {
dynamicTyping: true,
header: true,
skipEmptyLines: true,
},
});

return config;
},
}),
);
Loading