add linters

This commit is contained in:
Hugoren Martinako 2020-12-19 18:56:55 +01:00
parent 3493916439
commit 644df93487
12 changed files with 1915 additions and 239 deletions

29
.eslintrc.js Normal file
View File

@ -0,0 +1,29 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"never"
]
}
}

3
.stylelintrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
"extends": "stylelint-config-standard"
}

1759
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,9 @@
"scripts": {
"start": "parcel serve './src/**/*.html' --open",
"prebuild": "rm -rf ./dist/*",
"build": "parcel build ./src/index.html --public-url ."
"build": "parcel build ./src/index.html --public-url .",
"lint": "eslint 'src/**/*.js' || stylelint 'src/**/*.css'",
"lint:fix": "eslint 'src/**/*.js' --fix || stylelint 'src/**/*.css' --fix"
},
"repository": {
"type": "git",
@ -18,6 +20,9 @@
"topojson-client": "^3.1.0"
},
"devDependencies": {
"parcel": "^2.0.0-beta.1"
"eslint": "^7.16.0",
"parcel": "^2.0.0-beta.1",
"stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0"
}
}

View File

@ -42,7 +42,7 @@
.control__button.pause .control__button-play {
border-style: double;
border-width: 0px 0 0px var(--space);
border-width: 0 0 0 var(--space);
}
.control__button-stop {

View File

@ -59,6 +59,6 @@ body {
background: var(--color);
padding: calc(0.5 * var(--space));
border-radius: 5px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.75);
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.75);
box-sizing: border-box;
}

View File

@ -7,8 +7,8 @@
}
.loader,
.loader:before,
.loader:after {
.loader::before,
.loader::after {
border-radius: 50%;
}
@ -24,13 +24,13 @@
transform: translateZ(0);
}
.loader:before,
.loader:after {
.loader::before,
.loader::after {
position: absolute;
content: "";
}
.loader:before {
.loader::before {
width: 5.2em;
height: 10.2em;
background: #0dc5c1;
@ -41,7 +41,7 @@
animation: load2 2s infinite ease 1.5s;
}
.loader:after {
.loader::after {
width: 5.2em;
height: 10.2em;
background: #0dc5c1;
@ -56,6 +56,7 @@
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}

View File

@ -1,129 +0,0 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@ -13,7 +13,6 @@
<link rel="shortcut icon" href="favicon.ico" />
<link rel="stylesheet" href="css/reset.css" />
<link rel="stylesheet" href="css/main.css" />
<meta name="theme-color" content="#fafafa" />

View File

@ -1,4 +1,4 @@
import CYL from "url:../static/cyl.topo.json";
import CYL from "url:../static/cyl.topo.json"
export const URL = CYL
export const PROP = "cyl"
@ -43,4 +43,4 @@ export const ELEMENTS = [
value: "Zamora",
code: "49",
},
];
]

View File

@ -1,7 +1,7 @@
export const percent = (num = 0) =>
num.toLocaleString(undefined, { style: "percent" });
num.toLocaleString(undefined, { style: "percent" })
export const formatDate = (date = new Date(), opts = {}) =>
date.toLocaleDateString(undefined, opts);
date.toLocaleDateString(undefined, opts)
export const get = (...props) => obj => props.reduce(
(objNode, prop) => objNode && objNode[prop]
? objNode[prop]

View File

@ -1,49 +1,47 @@
import { geoMercator, geoPath } from "d3-geo";
import { select } from "d3-selection";
import { dsv } from "d3-fetch";
import { extent } from "d3-array";
import { interval } from "d3-timer";
import { transition } from "d3-transition";
import { scaleQuantile } from "d3-scale";
import { schemeGreens } from "d3-scale-chromatic";
import { zoom, zoomIdentity } from "d3-zoom";
import { feature } from "topojson-client";
import { percent, formatDate, get } from "./helpers";
import { ELEMENTS, PROP, URL } from "./elements";
import { geoMercator, geoPath } from "d3-geo"
import { select } from "d3-selection"
import { interval } from "d3-timer"
import { transition } from "d3-transition"
import { scaleQuantile } from "d3-scale"
import { schemeGreens } from "d3-scale-chromatic"
import { zoom, zoomIdentity } from "d3-zoom"
import { feature } from "topojson-client"
import { percent, formatDate, get } from "./helpers"
import { ELEMENTS, PROP, URL } from "./elements"
// constants
const INTERVAL_TIME = 2000;
const projection = geoMercator();
const range = schemeGreens[9];
const color = scaleQuantile(range).domain([0, 1]);
const z = zoom().scaleExtent([1, 8]);
const getCodmun = get('properties', 'codmun')
const getValues = get('properties', 'values')
const getNombre = get('properties', 'nombre')
const INTERVAL_TIME = 1000
const projection = geoMercator()
const range = schemeGreens[9]
const color = scaleQuantile(range).domain([0, 1])
const z = zoom().scaleExtent([1, 8])
const getCodmun = get("properties", "codmun")
const getValues = get("properties", "values")
const getNombre = get("properties", "nombre")
// variables
let currentMonthIx = 0;
let currentFeatureIx = 0;
let geojson = null;
let months = null;
let w = 0;
let h = 0;
let tick = null;
let currentMonthIx = 0
let currentFeatureIx = 0
let geojson = null
let months = null
let w = 0
let h = 0
let tick = null
// static elements
const map = select("#map");
const svg = map.append("svg");
const gpath = svg.append("g");
const gmonth = svg.append("g");
const tooltip = map.append("div").attr("class", "tooltip card");
const map = select("#map")
const svg = map.append("svg")
const gpath = svg.append("g")
const gmonth = svg.append("g")
const tooltip = map.append("div").attr("class", "tooltip card")
const loader = map
.append("div")
.attr("class", "loader__container")
.append("div")
.attr("class", "loader");
.attr("class", "loader")
const sidebar = map.append("div").attr("class", "sidebar");
const sidebar = map.append("div").attr("class", "sidebar")
const legendRanges = sidebar
.append("div")
@ -51,17 +49,17 @@ const legendRanges = sidebar
.selectAll(".legend__range")
.data(range)
.join("div")
.attr("class", "legend__range");
.attr("class", "legend__range")
legendRanges
.append("i")
.attr("class", "legend__range-square")
.style("background-color", (d) => d);
.style("background-color", (d) => d)
legendRanges.append("span").text((d) => {
const [start, end] = color.invertExtent(d);
return `${percent(start)} - ${percent(end)}`;
});
const [start, end] = color.invertExtent(d)
return `${percent(start)} - ${percent(end)}`
})
sidebar
.append("div")
@ -72,42 +70,42 @@ sidebar
.attr("id", d => d)
.attr("class", "control__button")
.on("click", ({ target }) => {
select("#play").classed("pause", false);
select("#play").classed("pause", false)
if (target.id === "play") {
if (tick !== null) {
tick.stop();
tick = null;
return;
tick.stop()
tick = null
return
}
select(target).classed("pause", true);
if (currentMonthIx === months.length - 1) currentMonthIx = 0;
select(target).classed("pause", true)
if (currentMonthIx === months.length - 1) currentMonthIx = 0
tick = interval(() => {
currentMonthIx++;
render();
if (currentMonthIx === months.length - 1) tick.stop();
}, INTERVAL_TIME);
currentMonthIx++
render()
if (currentMonthIx === months.length - 1) tick.stop()
}, INTERVAL_TIME)
} else if (target.id === "stop") {
currentMonthIx = 0;
currentMonthIx = 0
if (tick !== null) {
tick.stop();
tick = null;
tick.stop()
tick = null
}
render();
render()
}
})
.append("span")
.attr("class", d => `control__button-${d}`);
.attr("class", d => `control__button-${d}`)
sidebar
.append("div")
.attr("class", "selector card")
.append("select")
.on("change", ({ target: { value } }) => {
const ix = ELEMENTS.findIndex(x => x.code === value);
currentFeatureIx = ix < 0 ? 0 : ix;
render();
const ix = ELEMENTS.findIndex(x => x.code === value)
currentFeatureIx = ix < 0 ? 0 : ix
render()
})
.selectAll("option")
.data(ELEMENTS)
@ -117,7 +115,7 @@ sidebar
"selected",
({ code }) => code === ELEMENTS[currentFeatureIx].code || null
)
.text(x => x.value);
.text(x => x.value)
// map functions
const render = () => {
@ -140,22 +138,21 @@ const render = () => {
.call(enter => enter.transition(t).attr("x", w * 0.9)),
update => update,
exit => exit.call(exit => exit.transition(t).style("opacity", 0).attr("x", w * 0.5).remove())
);
)
const features = geojson.features.filter(d => {
const { code } = ELEMENTS[currentFeatureIx];
if (!code) return true;
return code === getCodmun(d).substring(0, 2);
});
const featuresIds = features.map(getCodmun);
const isFeatureActive = d => {
const { code } = ELEMENTS[currentFeatureIx]
if (!code) return true
return code === getCodmun(d).substring(0, 2)
}
const [[x0, y0], [x1, y1]] = geoPath(projection).bounds({
...geojson,
features,
});
features: geojson.features.filter(isFeatureActive),
})
z.on("zoom", ({ transform }) => gpath.attr("transform", transform));
svg.call(z);
z.on("zoom", ({ transform }) => gpath.attr("transform", transform))
svg.call(z)
svg
.transition(t)
@ -165,18 +162,21 @@ const render = () => {
.translate(w / 2, h / 2)
.scale(Math.min(8, 0.9 / Math.max((x1 - x0) / w, (y1 - y0) / h)))
.translate(-(x0 + x1) / 2, -(y0 + y1) / 2)
);
)
gpath
.selectAll("path.path")
.data(geojson.features, getCodmun)
.selectAll("path")
.data(geojson.features.map(d => ({ ...d, activated: isFeatureActive(d) })), getCodmun)
.join(
enter => enter
.append("path")
.attr("class", "path")
.attr("d", d => geoPath(projection)(d))
.attr("stroke-alignment", "inner")
.attr("stroke-width", 0.5)
.attr("fill", d => color(getValues(d)[months[currentMonthIx]]))
.attr("stroke-opacity", 1)
.attr("stroke", d => color(getValues(d)[months[currentMonthIx]]))
.style("pointer-events", "auto")
.on("mouseenter", ({ pageX, pageY, target }, d) => {
const t = svg.transition().duration(INTERVAL_TIME / 4)
select(target)
@ -192,7 +192,7 @@ const render = () => {
.style("left", `${pageX}px`)
.html(
`${getNombre(d)}: <em>${percent(getValues(d)[months[currentMonthIx]])}</em>`
);
)
})
.on("mouseleave", ({ target }) => {
const t = svg.transition().duration(INTERVAL_TIME / 4)
@ -202,31 +202,40 @@ const render = () => {
.attr("fill", d => color(getValues(d)[months[currentMonthIx]]))
.attr("stroke", d => color(getValues(d)[months[currentMonthIx]]))
tooltip.style("opacity", 0);
tooltip.style("opacity", 0)
}),
update => update,
update => {
// reduce the number of transitions
update.filter(({ activated }) => !activated)
.attr("fill", "black")
.attr("stroke-opacity", 0.25)
.attr("stroke", "#fafafa")
.style("pointer-events", "none")
return update
.filter(({ activated }) => !!activated)
.call(update => update.transition(t)
.attr("fill", d => color(getValues(d)[months[currentMonthIx]]))
.attr("stroke-opacity", 1)
.attr("stroke", d => color(getValues(d)[months[currentMonthIx]]))
.style("pointer-events", "auto"))
},
exit => exit.remove()
)
.transition(t)
.attr("fill", d => featuresIds.includes(getCodmun(d)) ? color(getValues(d)[months[currentMonthIx]]) : "black")
.attr("stroke-opacity", d => featuresIds.includes(getCodmun(d)) ? 1 : 0.25)
.attr("stroke", d => featuresIds.includes(getCodmun(d)) ? color(getValues(d)[months[currentMonthIx]]) : "#fafafa")
.style("pointer-events", d => featuresIds.includes(getCodmun(d)) ? "auto" : "none")
};
}
const resize = () => {
({ width: w, height: h } = map.node().getBoundingClientRect());
svg.attr("viewBox", [0, 0, w, h]);
projection.fitSize([w, h], geojson);
render();
};
({ width: w, height: h } = map.node().getBoundingClientRect())
svg.attr("viewBox", [0, 0, w, h])
projection.fitSize([w, h], geojson)
render()
}
const reload = async (...promises) => {
const [topojson] = await Promise.all(promises);
const [topojson] = await Promise.all(promises)
geojson = feature(
topojson,
topojson.objects[PROP]
);
)
// set the differents month-year tuples
months = [
@ -235,16 +244,16 @@ const reload = async (...promises) => {
.map(({ properties: { values } }) => Object.keys(values))
.flat()
),
];
]
// fit & render map
resize();
resize()
// hide spinner
loader.style("opacity", 0);
};
loader.style("opacity", 0)
}
// init app
reload(fetch(URL).then((r) => r.json()));
reload(fetch(URL).then((r) => r.json()))
addEventListener("resize", resize);
addEventListener("resize", resize)