Browse Source

Initial commit

master
Pierre Vanhulst 3 years ago
commit
162b1453ef
  1. 13
      .editorconfig
  2. 90
      .gitignore
  3. 22
      README.md
  4. 12
      jsconfig.json
  5. 70
      nuxt.config.js
  6. 22
      package.json
  7. 7
      src/assets/README.md
  8. 15
      src/assets/base.css
  9. 6
      src/assets/cantons.json
  10. 13
      src/assets/parties.json
  11. 1
      src/assets/results.json
  12. 6
      src/assets/scenarios.json
  13. 18
      src/assets/utils.js
  14. 80
      src/components/CandidatePixel.vue
  15. 79
      src/components/Logo.vue
  16. 7
      src/components/README.md
  17. 7
      src/layouts/README.md
  18. 13
      src/layouts/default.vue
  19. 8
      src/middleware/README.md
  20. 6
      src/pages/README.md
  21. 190
      src/pages/canton/_canton.vue
  22. 25
      src/pages/index.vue
  23. 7
      src/plugins/README.md
  24. 11
      src/static/README.md
  25. BIN
      src/static/favicon.ico
  26. 1278
      src/static/results.csv
  27. 10
      src/store/README.md
  28. 7719
      yarn.lock

13
.editorconfig

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

90
.gitignore vendored

@ -0,0 +1,90 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# Mac OSX
.DS_Store
# Vim swap files
*.swp

22
README.md

@ -0,0 +1,22 @@
# EF2019
> Small website showing the various results of the Swiss 201
## Build Setup
``` bash
# install dependencies
$ yarn install
# serve with hot reload at localhost:3000
$ yarn dev
# build for production and launch server
$ yarn build
$ yarn start
# generate static project
$ yarn generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).

12
jsconfig.json

@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"],
"~~/*": ["./*"],
"@@/*": ["./*"]
}
},
"exclude": ["node_modules", ".nuxt", "dist"]
}

70
nuxt.config.js

@ -0,0 +1,70 @@
import cantons from './src/assets/cantons.json';
export default {
axios: {
baseURL: 'http://localhost:3000'
},
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: process.env.npm_package_name || '',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [
'@/assets/base.css'
],
/*
** Plugins to load before mounting the App
*/
plugins: [
],
/*
** Nuxt.js dev-modules
*/
buildModules: [
],
srcDir: 'src',
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
},
generate: {
routes: ['/'].concat(cantons.map(c => `/canton/${c.code}`))
}
}

22
package.json

@ -0,0 +1,22 @@
{
"name": "EF2019",
"version": "1.0.0",
"description": "Small website showing the various results of the Swiss 201",
"author": "Pierre Vanhulst",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"@nuxtjs/axios": "^5.3.6",
"csv-parse": "^4.6.3",
"csv-parser": "^2.3.1",
"nuxt": "^2.0.0",
"pug": "^2.0.4",
"pug-plain-loader": "^1.0.0"
},
"devDependencies": {}
}

7
src/assets/README.md

@ -0,0 +1,7 @@
# ASSETS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).

15
src/assets/base.css

@ -0,0 +1,15 @@
html {
box-sizing: border-box;
}
html *, html *:after, html *:before {
box-sizing: inherit;
}
body {
font-family: Arial, Helvetica, sans-serif;
color: #232323;
line-height: 2;
margin: 0;
background: #e3e3e3;
}

6
src/assets/cantons.json

@ -0,0 +1,6 @@
[
{ "nameFR": "Zurich", "code": "ZH" },
{ "nameFR": "Zug", "code": "ZG" },
{ "nameFR": "Valais", "code": "VS" },
{ "nameFR": "Vaud", "code": "VD" }
]

13
src/assets/parties.json

@ -0,0 +1,13 @@
[
{ "id": "party_abbr_3", "nameFR": "Vert", "color": "#84b415" },
{ "id": "party_abbr_2", "nameFR": "PS", "color": "#e9534b" },
{ "id": "party_abbr_8", "nameFR": "PDC", "color": "#d1832a" },
{ "id": "party_abbr_9", "nameFR": "PLR", "color": "#376fb0" },
{ "id": "party_abbr_4", "nameFR": "PEV", "color": "#f8da01" },
{ "id": "party_abbr_7", "nameFR": "PCS", "color": "#2f9aa6" },
{ "id": "party_abbr_12", "nameFR": "Natio", "color": "#e9534b" },
{ "id": "party_abbr_6", "nameFR": "PBD", "color": "#e0c31f" },
{ "id": "party_abbr_5", "nameFR": "VertsLib", "color": "#bfbf3c" },
{ "id": "party_abbr_1", "nameFR": "E-Gauche", "color": "#e32219" },
{ "id": "party_abbr_11", "nameFR": "UDC", "color": "#49873c" }
]

1
src/assets/results.json

File diff suppressed because one or more lines are too long

6
src/assets/scenarios.json

@ -0,0 +1,6 @@
[
{ "id": "scenario_base", "nameFR": "Base" },
{ "id": "scenario_genders", "nameFR": "Genre" },
{ "id": "scenario_genders_ages", "nameFR": "Âge et genre" },
{ "id": "scenario_ages_urban", "nameFR": "Âge et domicile" }
]

18
src/assets/utils.js

@ -0,0 +1,18 @@
/**
* Substract matching elements of an array from another array
* @param {*} firstArray
* @param {*} secondArray
*/
export function differenceArrays (mainArray, substractArray, filter = 'IDCandidate') {
return mainArray.filter(entry => {
const entriesToSubstract = substractArray.map(s => s[filter]);
return !entriesToSubstract.includes(entry[filter])
})
}
export function similarityArrays (mainArray, substractArray, filter = 'IDCandidate') {
return mainArray.filter(entry => {
const entriesToSubstract = substractArray.map(s => s[filter]);
return entriesToSubstract.includes(entry[filter])
})
}

80
src/components/CandidatePixel.vue

@ -0,0 +1,80 @@
<template lang="pug">
div.candidate(
@mouseover="hovered = true"
@mouseout="hovered = false"
:key="candidate.IDCandidate"
:style="{ background: candidate.party.color }"
) {{ candidate.lastName.substr(0, 1).toUpperCase() }}
div.tooltip(v-show="hovered")
aside.tooltip__avatar(v-if="candidate.imageUrl" :style="{ 'background-image': `url(${candidate.imageUrl}` }")
div.tooltip__description
h4.tooltip__name {{ candidate.lastName }}, {{ candidate.firstName }}
dl
dt # de voix
dd {{ candidate.votes }}
</template>
<script>
export default {
props: {
candidate: {
required: true
}
},
data () {
return {
hovered: false
}
}
}
</script>
<style scoped>
.candidate {
width: 25px;
height: 25px;
margin: 5px;
line-height: 25px;
text-align: center;
font-weight: bold;
color: white;
position: relative;
}
.candidate:hover {
cursor: pointer;
}
.tooltip {
min-width: 10rem;
color: black;
position: absolute;
pointer-events: none;
z-index: 10;
background: rgba(255, 255, 255, .9);
display: block;
font-size: small;
top: 50%;
right: -1px;
transform: translate(100%, -50%);
box-shadow: 2px 2px 2px rgba(0, 0, 0, .3);
border-radius: 3px;
font-weight: normal;
text-align: left;
}
.tooltip__avatar {
width: 100%;
height: 150px;
margin-right: 1rem;
overflow: hidden;
background-size: cover;
background-position: center;
}
.tooltip__description {
padding: .25rem;
}
</style>

79
src/components/Logo.vue

@ -0,0 +1,79 @@
<template>
<div class="VueToNuxtLogo">
<div class="Triangle Triangle--two" />
<div class="Triangle Triangle--one" />
<div class="Triangle Triangle--three" />
<div class="Triangle Triangle--four" />
</div>
</template>
<style>
.VueToNuxtLogo {
display: inline-block;
animation: turn 2s linear forwards 1s;
transform: rotateX(180deg);
position: relative;
overflow: hidden;
height: 180px;
width: 245px;
}
.Triangle {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
}
.Triangle--one {
border-left: 105px solid transparent;
border-right: 105px solid transparent;
border-bottom: 180px solid #41b883;
}
.Triangle--two {
top: 30px;
left: 35px;
animation: goright 0.5s linear forwards 3.5s;
border-left: 87.5px solid transparent;
border-right: 87.5px solid transparent;
border-bottom: 150px solid #3b8070;
}
.Triangle--three {
top: 60px;
left: 35px;
animation: goright 0.5s linear forwards 3.5s;
border-left: 70px solid transparent;
border-right: 70px solid transparent;
border-bottom: 120px solid #35495e;
}
.Triangle--four {
top: 120px;
left: 70px;
animation: godown 0.5s linear forwards 3s;
border-left: 35px solid transparent;
border-right: 35px solid transparent;
border-bottom: 60px solid #fff;
}
@keyframes turn {
100% {
transform: rotateX(0deg);
}
}
@keyframes godown {
100% {
top: 180px;
}
}
@keyframes goright {
100% {
left: 70px;
}
}
</style>

7
src/components/README.md

@ -0,0 +1,7 @@
# COMPONENTS
**This directory is not required, you can delete it if you don't want to use it.**
The components directory contains your Vue.js Components.
_Nuxt.js doesn't supercharge these components._

7
src/layouts/README.md

@ -0,0 +1,7 @@
# LAYOUTS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Application Layouts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).

13
src/layouts/default.vue

@ -0,0 +1,13 @@
<template lang="pug">
div
nav
nuxt-link(to="/"): h1 Élections Fédérales 2019
nuxt
</template>
<style scoped>
div {
max-width: 50rem;
margin: auto;
}
</style>

8
src/middleware/README.md

@ -0,0 +1,8 @@
# MIDDLEWARE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your application middleware.
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).

6
src/pages/README.md

@ -0,0 +1,6 @@
# PAGES
This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).

190
src/pages/canton/_canton.vue

@ -0,0 +1,190 @@
<template lang="pug">
article
header.options
div
span Veuillez sélectionner un scénario
select(v-model="selectedScenario")
option(v-for="scenario in scenarios" :key="scenario.id" :value="scenario.id") {{ scenario.nameFR }}
div
span Ordre des candidats
select(v-model="sortOrder")
option(v-for="sort in sorts" :key="sort" :value="sort") {{ sort }}
header.legend
div.legend__item(v-for="party in parties" :key="party.id")
div.legend__item__color(:style="{ background: party.color }")
span {{ party.nameFR }}
section.tooltip.hovered-candidate(v-if="hoveredCandidate")
div {{ hoveredCandidate.lastName }} {{ hoveredCandidate.firstName }}
section.elected
div.rejected--scenario(v-show="electedRejected")
h3 Non-élu&middot;e&middot;s
div.pool
candidate-pixel(
v-for="candidate in electedRejected"
:candidate="candidate"
@hoveredCandidate="hoveredCandidate = $event"
:key="candidate.IDCandidate"
)
div.elected--scenario
h3 Élu&middot;e&middot;s
div.pool
candidate-pixel(
v-for="candidate in elected"
:candidate="candidate"
@hoveredCandidate="hoveredCandidate = $event"
:key="candidate.IDCandidate"
)
div.new--scenario(v-show="newElected")
h3 Nouveaux&middot;elles Élu&middot;e&middot;s
div.pool
candidate-pixel(
v-for="candidate in newElected"
:candidate="candidate"
@hoveredCandidate="hoveredCandidate = $event"
:key="candidate.IDCandidate"
)
section.rejected
h3 Non-élu&middot;e&middot;s
div.pool
candidate-pixel(
v-for="candidate in baseRejected"
:candidate="candidate"
@hoveredCandidate="hoveredCandidate = $event"
:key="candidate.IDCandidate"
)
</template>
<script>
import { differenceArrays, similarityArrays } from '@/assets/utils.js';
import CandidatePixel from '@/components/CandidatePixel.vue';
import parse from 'csv-parse/lib/sync';
import results from '@/assets/results.json';
import parties from '@/assets/parties.json';
import scenarios from '@/assets/scenarios.json';
const candidates = results.sort((a, b) => a.lastName > b.lastName ? 1 : -1);
candidates.forEach(c => {
const ids = parties.map(p => p.id);
const partyIndex = ids.indexOf(c.groupedparty);
c.party = parties[partyIndex] || { id: null, nameFR: 'Autre', color: '#d3d3d3' };
})
export default {
components: { CandidatePixel },
data () {
return {
selectedScenario: 'scenario_base',
hoveredCandidate: null,
sortOrder: 'parti',
sorts: [ 'parti', 'voix' ],
candidates,
parties,
scenarios
}
},
computed: {
filteredCandidates () {
return this.candidates.filter(r => r.districtCode === this.$route.params.canton);
},
sortedCandidates () {
if (this.sortOrder === 'voix')
return this.filteredCandidates.sort((a, b) => +a.votes < +b.votes ? 1 : -1);
return this.filteredCandidates.sort((a, b) => a.groupedparty < b.groupedparty ? 1 : -1);
},
baseElected () {
return this.sortedCandidates.filter(c => c.scenario_base === "1");
},
baseRejected () {
return this.sortedCandidates.filter(c => c.scenario_base === "0");
},
scenarioElected () {
if (this.selectedScenario === 'scenario_base') return false;
return this.sortedCandidates.filter(c => c[this.selectedScenario] === "1");
},
scenarioRejected () {
if (this.selectedScenario === 'scenario_base') return false;
return this.sortedCandidates.filter(c => c[this.selectedScenario] === "0");
},
electedRejected () {
if (!this.scenarioElected) return false;
return differenceArrays(this.baseElected, this.scenarioElected);
},
elected () {
return this.scenarioElected ? similarityArrays(this.scenarioElected, this.baseElected) : this.baseElected;
},
newElected () {
if (!this.scenarioElected) return false;
return differenceArrays(this.scenarioElected, this.baseElected)
},
rejected () {
return this.scenarioRejected || this.baseRejected;
}
}
}
</script>
<style scoped>
header {
background: #d7d7d7;
padding: 1rem;
}
.options {
display: flex;
justify-content: space-between;
}
.legend {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 2rem;
}
.legend__item {
margin: 15px;
}
.legend__item__color {
width: 25px;
height: 25px;
border-radius: 12.5px;
}
.elected {
display: flex;
}
.elected > * {
flex: 1;
}
.pool {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.rejected > * {
opacity: .5;
}
.elected--scenario > .pool, .new--scenario > .pool {
border-top: 5px solid green;
}
.rejected--scenario > .pool, .rejected > .pool {
border-top: 5px solid red;
}
</style>

25
src/pages/index.vue

@ -0,0 +1,25 @@
<template lang="pug">
main
p Veuillez sélectionner un canton
ul
li(v-for="canton in cantons" :key="canton.code")
nuxt-link(:to="`/canton/${canton.code}`") {{ canton.nameFR }}
</template>
<script>
import cantons from '~/assets/cantons.json';
export default {
data () {
return {
cantons
}
}
};
</script>
<style scoped>
main {
max-width: 50rem;
}
</style>

7
src/plugins/README.md

@ -0,0 +1,7 @@
# PLUGINS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).

11
src/static/README.md

@ -0,0 +1,11 @@
# STATIC
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).

BIN
src/static/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1278
src/static/results.csv

File diff suppressed because it is too large Load Diff

10
src/store/README.md

@ -0,0 +1,10 @@
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).

7719
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save