Public
Edited
Oct 14, 2024
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
chart_levels = {
//await visibility();

// Chart
const svg = d3
.create("svg")
.attr("width", isMobile ? myWidth_mobile_levels : myWidth)
.attr("height", height_levels)
.style("overflow", isMobile ? "visible" : "hidden");

// DEFS elements:
const defs = svg.append("defs");
createGradient(defs, "left");

// Axis
const axisGroup = svg.append("g").attr("id", "axis");
axisGroup.append("g").call(xAxis_levels);
axisGroup.append("g").call(yAxis_levels);

// Aux rects for interactions
const auxGroup = svg.append("g").attr("id", "aux");
const auxRects = auxGroup
.selectAll("a")
.data(dataset)
.join("a")
// Disable anchors on mobile
.attr("href", (d) => (isMobile ? null : d.source || null))
.attr("target", "_blank");
if (!isMobile) {
auxRects
.append("title")
.text((d) => (d.source ? locale.titleHoverNote : null));
}
const rectsEl = auxRects
.append("rect")
.attr("data-date", (d) => formatTime(d.date))
.style("opacity", 0)
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut);

if (!isMobile) {
rectsEl
.attr("x", (d) => xScale_levels(d.date))
.attr("width", xScale_levels.step())
.attr("y", (d) => margin_levels.top)
.attr(
"height",
(d) => height_levels - margin_levels.top - margin_levels.bottom
);
} else {
rectsEl
.attr("x", (d) => margin_levels.left)
.attr("width", myWidth - margin_levels.left - margin_levels.right)
.attr("y", (d) => yScale_levels(d.date) + yScale_levels.bandwidth() / 2)
.attr("height", yScale_levels.step());
}

// CHART ELEMENTS
const chartGroup = svg.append("g").attr("id", "chart-elements");

// Lines
const linesGroup = chartGroup.append("g").attr("id", "volumen-lines");
const lines = linesGroup
.selectAll("line")
.data(dataset)
.join("line")
.attr("class", "volume interactive")
.attr("data-date", (d) => formatTime(d.date))
.style("stroke", colorNeutral(80))
.style("pointer-events", "none");

if (!isMobile) {
lines
.attr("x1", (d) => xScale_levels(d.date) + xScale_levels.bandwidth() / 2)
.attr("x2", (d) => xScale_levels(d.date) + xScale_levels.bandwidth() / 2)
.attr("y1", (d) => yScale_levels(0))
.attr("y2", (d) => yScale_levels(0))
.style("stroke-width", xScale_levels.bandwidth() * 0.6)
.call(highlightCurrentDate)
.transition()
.duration(transitionDuration)
.delay((d, i) => i * transtionDelay)
.attr("y2", (d) => yScale_levels(d.volume));
} else {
lines
.attr("x1", (d) => xScale_levels(0))
.attr("x2", (d) => xScale_levels(0))
.attr("y1", (d) => yScale_levels(d.date) + yScale_levels.bandwidth() / 2)
.attr("y2", (d) => yScale_levels(d.date) + yScale_levels.bandwidth() / 2)
.style("stroke-width", yScale_levels.bandwidth() * 0.6)
.call(highlightCurrentDate)
.transition()
.duration(transitionDuration)
.delay((d, i) => i * transtionDelay)
.attr("x2", (d) => xScale_levels(d.volume));
}

// Rects
const rectsGroup = chartGroup.append("g").attr("id", "trasvases-rects");
const rects = rectsGroup
.selectAll("rect")
.data(dataset)
.join("rect")
.attr("data-date", (d) => formatTime(d.date))
.attr("class", "trasvase interactive")
.style("fill", (d) => `url(#linear-gradient-${d.level})`)
.style("opacity", 0.7)
.style("pointer-events", "none");

if (!isMobile) {
rects
.attr("x", (d) => xScale_levels(d.date) + 1)
.attr("width", xScale_levels.bandwidth() - 2)
.attr("y", (d) => yScale_levels(d.volume))
.attr("height", 0)
.transition()
.duration(transitionDuration)
.delay((d, i) => 1500 + i * transtionDelay)
.attr("height", (d) => yScale_levels(0) - yScale_levels(d.transfer));
} else {
rects
.attr("x", (d) => xScale_levels(d.volume))
.attr("width", 0)
.attr("y", (d) => yScale_levels(d.date) + 1)
.attr("height", yScale_levels.bandwidth() - 2)
.transition()
.duration(transitionDuration)
.delay((d, i) => 1500 + i * transtionDelay)
.attr(
"x",
(d) =>
xScale_levels(d.volume) -
(xScale_levels(d.transfer) - xScale_levels(0))
)
.attr("width", (d) => xScale_levels(d.transfer) - xScale_levels(0)); // Growing with time
}

// Lines - level
const linesGroup2 = chartGroup.append("g").attr("id", "levels-lines2");
const lines2 = linesGroup2
.selectAll("line")
.data(dataset)
.join("line")
.attr("class", "level interactive")
.attr("data-date", (d) => formatTime(d.date))
//.style("stroke", colorNeutral(1000))
.style("stroke", (d) => d3.color(scaleColor(d.level)).darker(0.9))
.style("stroke-width", 2)
.style("pointer-events", "none");

if (!isMobile) {
lines2
.attr("x1", (d) => xScale_levels(d.date))
.attr("x2", (d) => xScale_levels(d.date) + xScale_levels.bandwidth())
.attr("y1", yScale_levels(0))
.attr("y2", yScale_levels(0))
.transition()
.duration(transitionDuration)
.delay((d, i) => i * transtionDelay)
.attr("y1", (d) => yScale_levels(d.volume))
.attr("y2", (d) => yScale_levels(d.volume));
} else {
lines2
.attr("x1", xScale_levels(0))
.attr("x2", xScale_levels(0))
.attr("y1", (d) => yScale_levels(d.date))
.attr("y2", (d) => yScale_levels(d.date) + yScale_levels.bandwidth())
.transition()
.duration(transitionDuration)
.delay((d, i) => i * transtionDelay)
.attr("x1", (d) => xScale_levels(d.volume))
.attr("x2", (d) => xScale_levels(d.volume));
}

// TOOLTIP
const tooltip = svg.append("g").attr("id", "tooltip");

const initialHeight = 140;
const lineHeight = 20;
const horizOffset = 10;

if (!isMobile) {
// Lines - level
const linesGroup3 = tooltip.append("g").attr("id", "tooltip-lines3");
const lines3 = linesGroup3
.selectAll("line")
.data(dataset)
.join("line")
.attr("class", "tooltip-line interactive")
.attr("data-date", (d) => formatTime(d.date))
.attr("x1", (d) => xScale_levels(d.date) + xScale_levels.bandwidth() / 2)
.attr("x2", (d) => xScale_levels(d.date) + xScale_levels.bandwidth() / 2)
.attr("y1", (d) => yScale_levels(d.volume) - 10)
.attr("y2", (d) => yScale_levels(d.volume) - initialHeight - 10)
.style("stroke", colorNeutral(900))
.style("stroke-width", 1.5)
.call(highlightCurrentDate)
.style("pointer-events", "none")
.style("opacity", 0);
}

// Texts
const textGroup = tooltip.append("g").attr("id", "texts");
const date = textGroup.append("g");
date
.selectAll("text")
.data(dataset)
.join("text")
.attr("class", "interactive")
.attr("x", (d) =>
isMobile
? xScale_levels(d["volume"])
: xScale_levels(d.date) + xScale_levels.bandwidth() / 2
)
.attr("y", (d) =>
isMobile
? yScale_levels(d.date) + yScale_levels.bandwidth() / 2 + 4
: yScale_levels(d["volume"])
)
.style(
"text-anchor",
isMobile
? "start"
: (d) => (xScale_levels(d.date) <= midPoint ? "start" : "end")
)
.attr("dy", isMobile ? -15 : -initialHeight)
.attr("dx", function (d) {
if (isMobile) return 20;
else
return xScale_levels(d.date) <= midPoint ? horizOffset : -horizOffset;
})
.text((d) =>
isMobile ? formatDateTooltip_mobile(d.date) : formatDateTooltip(d.date)
)
.style("font-size", isMobile ? "0.7rem" : "0.8rem")
.style("fill", colorNeutral(600))
.style("opacity", 0)
.call(highlightCurrentDate)
.style("pointer-events", "none")
// To improve readability
.call((text) => text.clone(true))
.attr("fill", "none")
.attr("stroke", colorNeutral(0))
.attr("stroke-width", 6);

const volume = textGroup.append("g");
volume
.selectAll("text")
.data(dataset)
.join("text")
.attr("class", "interactive")
.attr("x", (d) =>
isMobile
? xScale_levels(d["volume"])
: xScale_levels(d.date) + xScale_levels.bandwidth() / 2
)
.attr("y", (d) =>
isMobile
? yScale_levels(d.date) + yScale_levels.bandwidth() / 2
: yScale_levels(d["volume"])
)
.style("text-anchor", (d) =>
isMobile ? "start" : xScale_levels(d.date) <= midPoint ? "start" : "end"
)
.attr("dy", isMobile ? 5 : -initialHeight + lineHeight)
.attr("dx", function (d) {
if (isMobile) return 20;
else
return xScale_levels(d.date) <= midPoint ? horizOffset : -horizOffset;
})
.text((d) =>
isMobile
? formatVolume(d.volume)
: `Volumen embalse: ${formatVolume(d.volume)}`
)
.style("fill", colorNeutral(1000))
.style("font-weight", 600)
.style("font-size", isMobile ? "0.8rem" : "0.9rem")
.style("opacity", 0)
.call(highlightCurrentDate)
.style("pointer-events", "none")
// To improve readability
.call((text) => text.clone(true))
.attr("fill", "none")
.attr("stroke", colorNeutral(0))
.attr("stroke-width", 6);

if (!isMobile) {
const quantity = textGroup.append("g");
quantity
.selectAll("text")
.data(dataset)
.join("text")
.attr("class", "interactive")
.attr("x", (d) => xScale_levels(d.date) + xScale_levels.bandwidth() / 2)
.attr("y", (d) => yScale_levels(d["volume"]))
.style("text-anchor", (d) =>
xScale_levels(d.date) <= midPoint ? "start" : "end"
)
.attr("dy", -initialHeight + lineHeight * 2)
.attr("dx", (d) =>
xScale_levels(d.date) <= midPoint ? horizOffset : -horizOffset
)
.text((d) => `Trasvase mensual: ${formatVolume(d.transfer)}`)
.style("fill", (d) => scaleColor(d.level))
.style("font-weight", 800)
.style("opacity", 0)
.call(highlightCurrentDate)
.style("pointer-events", "none");
}

const level = textGroup.append("g");
level
.selectAll("text")
.data(dataset)
.join("text")
.attr("class", "interactive")
.attr("x", (d) =>
isMobile
? xScale_levels(d["volume"])
: xScale_levels(d.date) + xScale_levels.bandwidth() / 2
)
.attr("y", (d) =>
isMobile
? yScale_levels(d.date) + yScale_levels.bandwidth() / 2
: yScale_levels(d["volume"])
)
.style("text-anchor", (d) =>
isMobile ? "start" : xScale_levels(d.date) <= midPoint ? "start" : "end"
)
.attr("dy", isMobile ? 20 : -initialHeight + lineHeight * 3)
.attr("dx", function (d) {
if (isMobile) return 20;
else
return xScale_levels(d.date) <= midPoint ? horizOffset : -horizOffset;
})
.text((d) => `Nivel ${d.level}`)
.style("fill", (d) => scaleColor(d.level))
.style("font-size", isMobile ? "0.8rem" : "0.9rem")
.style("font-weight", isMobile ? 800 : 400)
.style("opacity", 0)
.call(highlightCurrentDate)
.style("pointer-events", "none")
// To improve readability
.call((text) => text.clone(true))
.attr("fill", "none")
.attr("stroke", colorNeutral(0))
.attr("stroke-width", 6);

if (!isMobile) {
/// LEVELS RULES
// Levels lines
const levelsGroup = svg.append("g").attr("id", "levels");
const levels_constant = levelsGroup
.selectAll("line")
.data([5, ...levelsArray_reduced])
.join("line")
.attr("class", "limit-level")
.attr("data-level-up", (d, i) => d)
.attr("data-level-down", (d, i) => d - 1)
.attr(
"x1",
xScale_levels(dataset[0].date) + xScale_levels.bandwidth() / 2
)
.attr(
"x2",
xScale_levels(dataset[dataset.length - 1].date) +
xScale_levels.bandwidth()
)
.attr("y1", (d, i) => yScale_levels([0, ...levelsLimitsArray_reduced][i]))
.attr("y2", (d, i) => yScale_levels([0, ...levelsLimitsArray_reduced][i]))
.style("stroke", baseColorLevels)
.style("opacity", baseOpacityLevels);

// Step curve
const levels_step = levelsGroup.append("g");
levels_step
.append("path")
.datum(dataset)
.attr("d", line)
.attr("class", "limit-level")
.attr("data-level-up", 3)
.attr("data-level-down", 2)
.style("stroke", baseColorLevels)
.style("fill", "none")
.style("opacity", baseOpacityLevels);

// LEGEND LEFT SIDE
const legendGroup = svg.append("g").attr("id", "legend");
const legend = legendGroup
.selectAll("line")
.data([5, ...levelsArray])
.join("line")
.attr("class", "limit-level")
.attr("data-level-up", (d, i) => d)
.attr("data-level-down", (d, i) => d - 1)
.attr("x1", 0)
.attr("x2", margin_levels.left - 35)
.attr("y1", (d, i) => yScale_levels([0, ...levelsLimitsArray][i]))
.attr("y2", (d, i) => yScale_levels([0, ...levelsLimitsArray][i]))
.style("stroke", baseColorLevels)
.style("opacity", baseOpacityLevels);
//.style("stroke-dasharray", baseDashArray);

const r = 10;
const circles = legendGroup
.selectAll("circle")
.data(levelsArray)
.join("circle")
.attr("cx", margin_levels.left - 50)
.attr("cy", (d, i) => yScale_levels(levelsMiddlePointArray[i]) - r / 2)
.attr("r", r)
.style("fill", (d) => scaleColor(d));

const texts = legendGroup
.append("g")
.selectAll("text")
.data(levelsArray)
.join("text")
.attr("x", margin_levels.left - 50)
.attr("y", (d, i) => yScale_levels(levelsMiddlePointArray[i]))
.text((d) => d)
.style("text-anchor", "middle")
.style("font-size", "0.8rem")
.style("fill", "white");

const texts2 = legendGroup
.append("g")
.selectAll("text")
.data(levelsArray)
.join("text")
.attr("x", margin_levels.left - 70)
.attr("y", (d, i) => yScale_levels(levelsMiddlePointArray[i]) + 1)
.text("Nivel")
.style("text-anchor", "end")
.style("fill", colorNeutral(1000));

const texts3 = legendGroup
.append("g")
.selectAll("text")
.data(levelsArray)
.join("text")
.attr("x", margin_levels.left - 40)
.attr("y", (d, i) => yScale_levels(levelsMiddlePointArray[i]) + 20)
.text((d, i) => levelsCategoryArray[i])
.style("text-anchor", "end")
.style("fill", (d) => scaleColor(d))
.style("font-size", "0.85rem")
.style("font-weight", 700);

const offsetArea = 2;
const area = legendGroup
.selectAll("rect")
.data(levelsArray)
.join("rect")
.attr("class", "interactive area-legend")
.attr("data-level", (d) => d)
.attr("x", 0)
.attr("width", margin_levels.left - 35)
.attr("y", (d, i) => yScale_levels(levelsLimitsArray[i]) + offsetArea)
.attr("height", (d, i) => levelsHeightArray[i] - offsetArea * 2)
.style("fill", colorNeutral(0))
.style("opacity", baseOpacityAreas)
.style("cursor", "pointer")
.call(highlightCurrentDate)
.on("mouseover", onMouseOverArea)
.on("mouseout", onMouseOutArea);

// Foreign object method
// https://observablehq.com/@bumbeishvili/foreignobject-typical-usage
const fo = svg
.append("foreignObject") // Append for d3.v4
.classed("fo-object", true)
.attr("width", 320)
.attr("height", height_transfers)
.style("pointer-events", "none");
fo.append("xhtml:div") // Append for d3.v4
.classed("trasvase-tooltip", true);
} else {
//
}

// TITLES
const titles = svg.append("g").attr("id", "titles");
const textElY = titles
.append("text")
.attr("y", isMobile ? 20 : 20)
//.attr("y", height_levels)
//.style("text-anchor", isMobile ? "start" : "start")
.style("text-anchor", isMobile ? "start" : "start")
.style("font-weight", 600)
.style("font-size", "0.9rem");

if (!isMobile) {
textElY
.append("tspan")
.text(locale.title_levels1)
.attr("dx", 0)
.attr("dy", 0)
.attr("x", 10);
//.attr("x", margin_levels.left);

textElY
.append("tspan")
.text(locale.title_levels2)
.attr("dx", 0)
.attr("dy", 20)
.attr("x", 10);
//.attr("x", margin_levels.left);
} else {
textElY
.append("tspan")
.text(locale.title_levels1_mobile)
.attr("dx", 0)
.attr("dy", 0)
.attr("x", margin_levels.left);
textElY
.append("tspan")
.text(locale.title_levels2_mobile)
.attr("dx", 0)
.attr("dy", 20)
.attr("x", margin_levels.left);
textElY
.append("tspan")
.text(locale.title_levels3_mobile)
.attr("dx", 0)
.attr("dy", 20)
.attr("x", margin_levels.left);
textElY
.append("tspan")
.text(locale.title_levels4_mobile)
.attr("dx", 0)
.attr("dy", 20)
.attr("x", margin_levels.left);
}

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
xScale_transfers = isMobile
? xScale_transfers_mobile_quantity
: xScale_transfers_desktop_dates
Insert cell
// https://observablehq.com/@d3/d3-scaleband
xScale_transfers_desktop_dates = d3
.scaleBand()
.domain(datesArray)
.range([margin_transfers.left, myWidth - margin_transfers.right]) // [left, right]
.paddingInner(0.3)
Insert cell
// https://observablehq.com/@d3/d3-scaleband
xScale_transfers_mobile_quantity = d3
.scaleLinear()
//.domain([0, d3.max(dataset, (d) => d.transfer)]) // Domain from real data
.domain([0, maxQuantity]) // Better having maxQuantity as reference
.range([
margin_transfers.left,
myWidth_mobile_transfers - margin_transfers.right
]) // [left, right]
.nice()
Insert cell
yScale_legend = d3
.scaleLinear()
//.domain([0, d3.max(dataset, (d) => d.transfer)]) // Domain from real data
.domain([0, maxVolume]) // Better having maxQuantity as reference
.range([0, height_levels - margin_levels.top - margin_levels.bottom]) // [bottom, top]
.nice()
Insert cell
xScale_levels = isMobile ? xScale_levels_mobile_quantity : xScale_levels_desktop_dates
Insert cell
// https://observablehq.com/@d3/d3-scaleband
xScale_levels_desktop_dates = d3
.scaleBand()
.domain(datesArray)
.range([margin_levels.left, myWidth - margin_levels.right]) // [left, right]
.paddingInner(0.2)
Insert cell
xScale_levels_mobile_quantity = d3
.scaleLinear()
//.domain([0, d3.max(dataset, (d) => d.transfer)]) // Domain from real data
.domain([0, maxVolume]) // Better having maxQuantity as reference
.range([margin_levels.left, myWidth_mobile_levels - margin_levels.right]) // [left, right]
.nice()
Insert cell
maxQuantity = 68
Insert cell
yScale_transfers = isMobile
? yScale_transfers_mobile_dates
: yScale_transfers_desktop_quantity
Insert cell
yScale_transfers_desktop_quantity = d3
.scaleLinear()
//.domain([0, d3.max(dataset, (d) => d.transfer)]) // Domain from real data
.domain([0, maxQuantity]) // Better having maxQuantity as reference
.range([margin_transfers.top, height_transfers - margin_transfers.bottom]) // [top, bottom]
.nice()
Insert cell
yScale_transfers_mobile_dates = d3
.scaleBand()
.domain(datesArray)
.range([margin_transfers.top, height_transfers - margin_transfers.bottom]) // [top, bottom]
.paddingInner(0.3)
Insert cell
yScale_levels = isMobile ? yScale_levels_mobile_dates : yScale_levels_desktop_quantity
Insert cell
yScale_levels_desktop_quantity = d3
.scaleLinear()
//.domain([0, d3.max(dataset, (d) => d.volume)]) // Domain from real data
.domain([0, maxVolume]) // Better having maxQuantity as reference
.range([height_levels - margin_levels.bottom, margin_levels.top]) // [bottom, top]
.nice()
Insert cell
yScale_levels_mobile_dates = d3
.scaleBand()
.domain(datesArray)
.range([margin_levels.top, height_levels - margin_levels.bottom]) // [top, bottom]
.paddingInner(0.3)
Insert cell
scaleColor = d3.scaleOrdinal().domain([1, 2, 3, 4]).range(colorNiveles)
Insert cell
midPoint = margin_transfers.left +
(myWidth - margin_transfers.left - margin_transfers.right) / 2
Insert cell
Insert cell
xAxis_levels = isMobile
? xAxis_levels_mobile_quantities
: xAxis_levels_desktop_date
Insert cell
xAxis_levels_desktop_date = (g) =>
g
.attr("class", "x-axis")
.attr("transform", `translate(0,${height_levels - margin_levels.bottom})`)
.style("color", colorNeutral(500))
.call(
d3
.axisBottom(xScale_levels)
.tickValues(
xScale_levels.domain().filter(function (d, i) {
return !(i % 12);
})
)

.ticks(d3.timeYear)
.tickFormat((d) => d.getFullYear())
)

.call((g) => g.select(".domain").remove())
Insert cell
xAxis_levels_mobile_quantities = (g) =>
g
.attr("class", "y-axis")
.style("color", colorNeutral(600))
.attr("transform", `translate(0,${height_transfers})`)
.call(
d3
.axisTop(xScale_levels)
.tickValues([0, 400, 1000])
.tickSize(
height_transfers - margin_transfers.top - margin_transfers.bottom + 40
)
)
.call((g) => g.select(".domain").remove())
.call((g) =>
g.selectAll(".tick:not(:first-of-type) line").style("opacity", 0.3)
)
Insert cell
yAxis_levels = isMobile
? yAxis_levels_mobile_date
: yAxis_levels_desktop_quantities
Insert cell
yAxis_levels_desktop_quantities = (g) =>
g
.attr("class", "y-axis")
.style("color", colorNeutral(600))
.attr("transform", `translate(${margin_levels.left},0)`)
.call(
d3.axisLeft(yScale_levels).ticks(8)
)
.call((g) => g.select(".domain").remove())
.call((g) =>
g.selectAll(".tick:not(:first-of-type) line").style("opacity", 0.3)
)
Insert cell
yAxis_levels_mobile_date = (g) =>
g
.attr("class", "x-axis")
.attr("transform", `translate(${margin_levels.left},0)`)
.style("color", colorNeutral(500))
.call(
d3
.axisLeft(yScale_levels)
.tickValues(
yScale_levels.domain().filter(function (d, i) {
return !(i % 12);
})
)
.ticks(d3.timeYear)
.tickFormat((d) => d.getFullYear())
)

.call((g) => g.select(".domain").remove())
Insert cell
axis_transfers = isMobile ? xAxis_transfers_mobile : yAxis_transfers_desktop
Insert cell
xAxis_transfers_mobile = (g) =>
g
.attr("class", "x-axis")
.style("color", colorNeutral(500))
.attr("transform", `translate(0,${height_transfers})`)
.call(
d3
.axisTop(xScale_transfers) // [left, right])
.tickValues([0, 30, 60])
.tickSize(
height_transfers - margin_transfers.top - margin_transfers.bottom + 40
)
)
.call((g) => g.select(".domain").remove())
.call((g) =>
g.selectAll(".tick:not(:first-of-type) line").style("opacity", 0.3)
)
Insert cell
yAxis_transfers_desktop = (g) =>
g
.attr("class", "y-axis")
.style("color", colorNeutral(500))
.attr("transform", `translate(${margin_transfers.left},0)`)
.call(
d3
.axisLeft(yScale_transfers) // [top, bottom])
.ticks(3)
.tickSize(-myWidth + margin_transfers.left + margin_transfers.right)
)
.call((g) => g.select(".domain").remove())
.call((g) =>
g.selectAll(".tick:not(:first-of-type) line").style("opacity", 0.3)
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
locale = ({
title_levels1: "Reservas en los embalses",
title_levels2: "Entrepeñas y Buendía (hm3)",

title_levels1_mobile: "Reservas en los",
title_levels2_mobile: "embalses de",
title_levels3_mobile: "Entrepeñas y",
title_levels4_mobile: "Buendía (hm3)",

title_transfers1: "Zoom: detalle",
title_transfers2: "de trasvases",
title_transfers3: "mensuales",
title_transfers4: "(hm3)",

title_transfers1_mobile: "Zoom:",
title_transfers2_mobile: "trasvases",
title_transfers3_mobile: "mensuales",
title_transfers4_mobile: "(hm3)",

titleHoverNote: "Haz click para consultar la fuente de los datos",

level1: "↕ Vetado",
level2: "↕ Excepcional",
level3: "↕ Automático",
level4: "↑ Automático",

title_levelsRules: "Las reglas de los embalses:"
})
Insert cell
Insert cell
Insert cell
parseDate = d3.timeParse("%Y-%m-%d")
Insert cell
formatPercent = d3.format(",.1%")
Insert cell
formatIntegers = d3.formatLocale(es_ES).format(",d")
Insert cell
formatTime = d3.timeFormat("%Y-%m-%d")
Insert cell
formatVolume = d => `${d} hm3`
Insert cell
formatDateTooltip = timeLocaleES.format("%B %Y")
Insert cell
formatDateTooltip_mobile = timeLocaleES.format("%b %Y")
Insert cell
Insert cell
Insert cell
Insert cell
height_mobile = 15 * dataset.length // Chart will grow on mobile with time, to be as readible as possible
Insert cell
Insert cell
Insert cell
Insert cell
myWidth = deviceSelection === "desktop" ? Math.min(width, maxWidth) : 320 // 👈🏻 Just for testing
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Civio main colors
import { colors, colorNeutral } from "@civio/utilities"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more