import { fmt, fmtFecha, parseFecha, armarReferencias, lineasCerca, nodoLeyenda, SMITH_TRAZO, SMITH_LEYENDA } from "../_js/portal.js"
catalogo = await FileAttachment("lago/puntuales/catalogo.csv").csv()
limites = await FileAttachment("antecedentes/normativa_limites.csv").csv({typed: false})
viewof est = Inputs.select(catalogo, { label: "Estación", format: (d) => d.nombre })
crudo = await d3.csv(`lago/puntuales/${est.id}.csv`)
filas = crudo.map((r) => ({
fecha: r.fecha,
t: parseFecha(r.fecha),
parametro: r.parametro,
unidad: r.unidad,
z: r.profundidad === "" ? null : +r.profundidad,
valor: r.valor === "" ? null : +r.valor,
censurado: r.censurado === "1",
limite: r.limite === "" ? null : +r.limite
}))
params = [...new Set(filas.map((d) => d.parametro))].sort()
viewof parametro = Inputs.select(params, { label: "Parámetro" })Muestreos puntuales del lago
Muestreos del lago desde 1986, en todas las profundidades de la planilla. Entran solo las mediciones detectadas. La primera figura junta todas las visitas. La línea vertical gris es la campaña elegida más abajo. La segunda figura es el perfil de esa misma visita: las mismas muestras, ordenadas de la superficie al fondo.
El círculo es una medición. Superficie: hasta 2 m. Fondo: la muestra más profunda de esa visita. Las líneas violeta punteadas, cuando aparecen, son límites de estado trófico de Smith et al. (1999).
function sinTilde(s) {
return String(s || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase()
}
serie = filas.filter((d) => d.parametro === parametro && !d.censurado && d.valor != null).map((d) => {
let v = d.valor
if (v != null && sinTilde(d.parametro).includes("fosforo") && sinTilde(d.unidad).includes("mg") && v >= 1) v = v / 1000
return { ...d, v }
}).filter((d) => d.v != null)
unidadP = (serie.find((d) => d.unidad) || {}).unidad || ""
porProfundidad = {
const maxZ = new Map()
for (const d of serie) {
if (d.z == null) continue
const prev = maxZ.get(d.fecha)
if (prev == null || d.z > prev) maxZ.set(d.fecha, d.z)
}
return serie.filter((d) => d.v != null).map((d) => ({
...d,
clase: d.z == null ? "sin profundidad" : d.z <= 2 ? "superficie" : d.z === maxZ.get(d.fecha) ? "fondo" : "intermedia"
}))
}
clasesPresentes = ["superficie", "intermedia", "fondo", "sin profundidad"].filter((c) => porProfundidad.some((d) => d.clase === c))
fechasCamp = [...new Set(serie.filter((d) => d.z != null).map((d) => d.fecha))].sort()
viewof campania = Inputs.select(fechasCamp, {
label: "Campaña del perfil",
format: (f) => fmtFecha(parseFecha(f)),
value: fechasCamp[fechasCamp.length - 1]
}){
const ys = porProfundidad.map((d) => d.v).filter((v) => Number.isFinite(v))
const refs = armarReferencias(limites, parametro, unidadP, est.nombre)
const cerca = ys.length ? lineasCerca(refs, Math.min(...ys), Math.max(...ys)) : { nsca: [], smith: [], coinciden: [] }
const det = porProfundidad.filter((d) => !d.censurado).map((d) => d.v)
const orden = det.slice().sort((a, b) => a - b)
const iMed = orden.length ? (orden.length - 1) / 2 : 0
const mediana = orden.length ? (orden[Math.floor(iMed)] + orden[Math.ceil(iMed)]) / 2 : null
const fig = Plot.plot({
width: 900,
height: 400,
marginLeft: 64,
marginBottom: 44,
color: {
legend: true,
domain: clasesPresentes,
range: clasesPresentes.map((c) => ({ superficie: "#1a5276", intermedia: "#5dade2", fondo: "#1B7F4E", "sin profundidad": "#7f8c8d" })[c])
},
x: { label: "Fecha" },
y: { grid: true, label: unidadP ? `${parametro} (${unidadP})` : parametro },
marks: [
Plot.ruleX([parseFecha(campania)], { stroke: "#566573", strokeDasharray: "4,3" }),
Plot.dot(porProfundidad, { x: "t", y: "v", fill: "clase", r: 3 }),
Plot.ruleY(cerca.nsca.filter((d) => /promedio/i.test(d.estadistico)), { y: "v", stroke: "#C62828", strokeDasharray: "5,4" }),
Plot.ruleY(cerca.nsca.filter((d) => !/promedio/i.test(d.estadistico)), { y: "v", stroke: "#C62828" }),
Plot.ruleY(cerca.smith, { stroke: SMITH_TRAZO, strokeDasharray: "6,4", strokeWidth: 1.6 })
]
})
const caja = document.createElement("div")
const h = document.createElement("h2")
h.textContent = "Serie de visitas"
const sim = document.createElement("p")
sim.className = "portal-origen"
sim.style.display = "flex"
sim.style.flexWrap = "wrap"
sim.style.gap = "1.2rem"
sim.style.alignItems = "center"
const item = (color, dashed, texto) => {
const sp = document.createElement("span")
const line = document.createElement("span")
line.style.cssText = "display:inline-block;width:2rem;margin-right:0.45rem;border-top:2px " + (dashed ? "dashed " : "solid ") + color + ";vertical-align:middle;"
sp.append(line, document.createTextNode(texto))
return sp
}
if (cerca.nsca.length) sim.append(item("#C62828", false, "Norma secundaria DS 19/2013"))
if (cerca.smith.length) sim.append(item(SMITH_TRAZO, true, SMITH_LEYENDA))
const p = document.createElement("p")
p.className = "portal-origen"
p.textContent = "La línea vertical gris marca la campaña del perfil de más abajo. Superficie: hasta 2 m. Fondo: la muestra más profunda de esa visita."
caja.append(h, fig)
if (sim.childNodes.length) caja.append(sim)
caja.append(p)
const ley = nodoLeyenda({ refs, cerca, mediana, unidad: unidadP })
if (ley) caja.append(ley)
return caja
}{
const perfilCamp = porProfundidad
.filter((d) => d.fecha === campania && d.z != null)
.slice()
.sort((a, b) => a.z - b.z)
const fig = Plot.plot({
width: 520,
height: 460,
marginLeft: 58,
marginBottom: 52,
marginRight: 16,
color: {
legend: true,
domain: clasesPresentes,
range: clasesPresentes.map((c) => ({ superficie: "#1a5276", intermedia: "#5dade2", fondo: "#1B7F4E", "sin profundidad": "#7f8c8d" })[c])
},
x: { grid: true, label: unidadP ? `${parametro} (${unidadP})` : parametro },
y: { grid: true, reverse: true, label: "Profundidad (m)" },
marks: [
Plot.line(perfilCamp, { x: "v", y: "z", stroke: "#1a5276" }),
Plot.dot(perfilCamp, { x: "v", y: "z", fill: "clase", r: 4 })
]
})
const caja = document.createElement("div")
const h = document.createElement("h2")
h.textContent = "Perfil de la campaña del " + fmtFecha(parseFecha(campania))
const p = document.createElement("p")
p.className = "portal-origen"
p.textContent = "Son los puntos de la fecha marcada en la serie, leídos de arriba (superficie) hacia abajo (fondo). El color es el mismo de la serie."
caja.append(h, fig, p)
return caja
}