add linters
This commit is contained in:
parent
3493916439
commit
644df93487
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
"extends": "stylelint-config-standard"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
]
|
||||
|
|
|
@ -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]
|
||||
|
|
199
src/js/index.js
199
src/js/index.js
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue