Public
Edited
Jul 4, 2023
Paused
Importers
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mapHeader = html`<div class="mapHeader">
<h2 class="map-title">${dataLocale[languageSelector].mapTitle}</h2>
<h4 class="map-subtitle">${dataLocale[languageSelector].mapSubtitle}</h4>

<div class='AP-form'>

<div class='row' id='filter'>
<input class="hidden radio-label" id='mf' type='radio' name='toggle' value='mf' checked='checked'>
<label class="button-label" for='mf'>${
dataLocale[languageSelector].mf_code
}</label>
<input class="hidden radio-label" id='ped' type='radio' name='toggle' value='ped'>
<label class="button-label" for='ped'>${
dataLocale[languageSelector].ped_code
}</label>
<input class="hidden radio-label" id='enf' type='radio' name='toggle' value='enf'>
<label class="button-label" for='enf'>${
dataLocale[languageSelector].enf_code
}</label>
</div>

<div id="legend-mf" class="legend">${legend({
color: color_mf,
title: "Pacientes vistos al día (MED)",
width: widthLegend
})}</div>
<div id="legend-enf" style="display:none" class="legend">${legend({
color: color_enf,
title: "Pacientes vistos al día (ENF)",
width: widthLegend
})}</div>
<div id="legend-ped" style="display:none" class="legend">${legend({
color: color_ped,
title: "Pacientes vistos al día (PED)",
width: widthLegend
})}</div>
</div>
</div>
`
Insert cell
Insert cell
mapDetailedInfo = html`<div class="mapDetailedInfo">
<header class="description-popup"></header>
<main class="map-main" >
<div id="pressure-chart" class="detailed-chart-group" style="display: none;">
<div class="chart_bckgnd" id="pressure-chart_bckgnd-mf">${createEvolutionChart_bckgnd(
"pressure_mf"
)}</div>
<div class="chart_bckgnd" style="display:none" id="pressure-chart_bckgnd-ped">${createEvolutionChart_bckgnd(
"pressure_ped"
)}</div>
<div class="chart_bckgnd" style="display:none" id="pressure-chart_bckgnd-enf">${createEvolutionChart_bckgnd(
"pressure_enf"
)}</div>

<div class="chart_onTop" id="pressure-chart_onTop"></div>
</div>
<div id="ratio-chart" class="detailed-chart-group" style="display: none;">
<div class="chart_bckgnd" id="ratio-chart_bckgnd-mf">${createEvolutionChart_bckgnd(
"ratio_mf"
)}</div>
<div class="chart_bckgnd" style="display:none" id="ratio-chart_bckgnd-ped">${createEvolutionChart_bckgnd(
"ratio_ped"
)}</div>
<div class="chart_bckgnd" style="display:none" id="ratio-chart_bckgnd-enf">${createEvolutionChart_bckgnd(
"ratio_enf"
)}</div>

<div class="chart_onTop" id="ratio-chart_onTop"></div>
</div>

</main>
<footer class="map-footer">${dataLocale[languageSelector].source}</footer>
</div>`
Insert cell
Insert cell
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@700;900&display=swap" rel="stylesheet">

<style>
/* Mapbox custom controls */
.mapboxgl-ctrl-scale {
background-color: hsla(0,0%,100%,0)!important;
border-bottom: 2px solid ${colorNeutral(300)}!important;
border-left: 2px solid ${colorNeutral(300)}!important;
border-right: 0!important;
}
.mapboxgl-ctrl-attrib-inner {
display: none;
}

/* HTML CUSTOM STYLES */
#AP-map * {
font-family: 'Lato', sans-serif;
}
.evolutionChart * {
font-family: 'Lato', sans-serif;
}
#AP-map {
font-size: 0.8rem;
max-width: ${maxWidth}px;
height: ${height}px;
margin-bottom: 2rem;
display: grid;
grid-template-columns: 30% auto;
grid-template-rows: ${headerHeight}px 1fr;
grid-gap: 0px;
grid-template-areas:
" A B"
" C B";
}
/* Naming the 3 grid areas */
.mapHeader { grid-area: A; }
.mapDetailedInfo { grid-area: C; }
.mapContent { grid-area: B; }

.mapHeader {
padding: 0 5px;
display: flex;
flex-direction: column;
justify-content: end;
}
.mapDetailedInfo {
padding: 5px;
display: flex;
flex-direction: column;
}
.mapDetailedInfo > header {
min-height: 60px;
}
.map-main {
flex: 1;
display: flex;
flex-direction: column;
}
.map-footer {
font-size: 0.7rem;
color: ${colorNeutral(500)};
line-height: 0.95;
}
.partial-data-note {
font-size: 0.7rem;
}
@media (max-width: ${mediaQueryLimit}px) {
#AP-map {
grid-template-areas: "A" "B" "C";
grid-template-columns: 1fr;
grid-template-rows: ${headerHeight}px ${mapHeightMobile}px 1fr;
}
.mapDetailedInfo {
text-align: center;
}
.map-main {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
margin-bottom: 1rem;
}
}
#AP-map .map-title {
font-size: 1.4rem;
margin-bottom: 10px;
text-align: ${isMobile ? "center" : "left"};
margin-top:0
}

#AP-map .map-subtitle {
font-size: 0.8rem;
font-weight: 400;
margin-bottom: 20px;
text-align: ${isMobile ? "center" : "left"};
margin-top:0
}
#AP-map .legend svg {
margin: ${isMobile ? "0 auto" : "inherit"};
}
#AP-map .area-siap {
font-size: 1rem;
font-weight: 800;
margin-bottom: 0.15rem;
}
.description-intro {
margin-bottom: 0rem;
height: 35px;
}
.description-body {
margin-bottom: 0.8rem;
}
.description-popup h3 {
margin-top: 0
}
.legend {
margin-top: 0.5rem;
}
/* Detailed info charts */
.detailed-chart-group {
position: relative;
width: 260px;
border-radius: 3px;
border: 1px solid ${colorNeutral(80)};
}
#pressure-chart {
height: ${heightChart}px;
margin-bottom: 5px;
}
#ratio-chart {
height: ${heightChart}px;
}
.detailed-chart-group .chart_bckgnd, .detailed-chart-group .chart_onTop {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
/* 📌 Pop-up info CS */
.mapboxgl-popup {
width: 160px;
text-align: center;
font-size: 0.6rem;
line-height: 1.3;

}
.mapboxgl-popup-tip, .mapboxgl-popup-content {
opacity: 0.7;
}
.mapboxgl-popup-content {
padding: 5px;
}

/* Radio button styling */
/* CSS Reference: https://codepen.io/mblode/pen/VwGxaO?editors=1100*/
.AP-form {
margin-bottom: 0.5rem;
}
.AP-form #filter {
margin-bottom: 0.8rem;
display:flex;
flex-direction: row;
align-items: center;
justify-content: ${isMobile ? "center" : "start"};
}
.button-label {
cursor: pointer;
background: ${colorNeutral(10)};
color: ${colorNeutral(500)};
padding: 10px 3px;
margin: 0 7px 0 0;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,0.2), inset 0 -3px 0 rgba(0,0,0,0.22);
transition: transform 0.3s, box-shadow 0.3s;
user-select: none;
font-size: 0.65rem;

min-width: 75px;
text-align: center;
}
.hidden {
display: none;
}
.button-label:hover {
box-shadow: 0 3px 10px rgba(0,0,0,0.2), inset 0 -3px 0 rgba(0,0,0,0.32);
}
.button-label:active {
transform: translateY(2px);
box-shadow: 0 3px 10px rgba(0,0,0,0.2), inset 0px -1px 0 rgba(0,0,0,0.22);
}
#mf:checked + .button-label, #ped:checked + .button-label, #enf:checked + .button-label {
color: ${colorNeutral(0)};
font-weight: 800;
}
#mf:checked + .button-label {
background: ${colorMf};
background: linear-gradient(60deg, ${colorPrimary}, ${colorMf});
background-image: linear-gradient(to right top, #08a6bf, #0f99df, #8680e1, #d255b3, #ef2e61);
background-image: linear-gradient(60deg, #08a6bf, #d44a6f);
}
#enf:checked + .button-label {
background: ${colorEnf};
background: linear-gradient(60deg, ${colorPrimary}, ${colorEnf});
}
#ped:checked + .button-label {
background: ${colorPed};
background: linear-gradient(60deg, ${colorPrimary}, ${colorPed});
}

</style>
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function createEvolutionChart_bckgnd(field) {
const speciality = field.split("_")[1];
const isPressureChart = field.split("_")[0] === "pressure";
const fieldTitle = speciality + "_small";

let colorScale;
let rangePressure;
let rangeRatio;
let maxRatio;
let maxSource;
const offset = 5;
if (speciality === "mf") {
colorScale = color_mf;
maxRatio = maxRatio_mf;
maxSource = dataLocale[languageSelector].maxSource_mf;
rangePressure = rangeFullSerie_Pressure_mf;
rangeRatio = rangeFullSerie_Ratio_mf;
} else if (speciality === "enf") {
colorScale = color_enf;
maxRatio = maxRatio_enf;
maxSource = dataLocale[languageSelector].maxSource_enf;
rangePressure = rangeFullSerie_Pressure_enf;
rangeRatio = rangeFullSerie_Ratio_enf;
} else if (speciality === "ped") {
colorScale = color_ped;
maxRatio = maxRatio_ped;
maxSource = dataLocale[languageSelector].maxSource_ped;
rangePressure = rangeFullSerie_Pressure_ped;
rangeRatio = rangeFullSerie_Ratio_ped;
}
const range = isPressureChart ? rangePressure : rangeRatio;
const margin = marginChart;
const xOffset = 5;

// Create SVG
const svg = d3
.create("svg")
.attr("class", "evolutionChart")
.attr("width", widthChart)
.attr("height", heightChart);

// Update domain Y scale
y.domain(range).range([heightChart - margin.bottom, margin.top]);
x.range([margin.left, widthChart - margin.right]);

// Axis
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);

// All data background
const allData = dataEvolution.map(
(d) =>
d.properties[isPressureChart ? "evolution_pressure" : "evolution_ratio"]
);
const dataCleaned = allData.map(function (d) {
return d.filter(function (e) {
//console.log(e[field], !isNaN(+e[field]));
//console.log(e[field], typeof e[field] === "number");
return typeof e[field] === "number";
});
});

const allPathsG = svg.append("g").attr("class", "allPaths-group");
allPathsG
.selectAll("path")
//.data(allData)
.data(dataCleaned)
.join("path")
.attr("fill", "none")
.attr("stroke", colorNeutral(250))
.attr("stroke-width", 0.1)
.attr(
"d",
d3
.line()
.x((d) => x(d.date))
.y((d) => y(d[field]))
);

// Just for ratio chart: recomendations
if (!isPressureChart) {
const maxLimits = svg.append("g").attr("class", "limits-group");
maxLimits
.append("line")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.attr("y1", y(maxRatio))
.attr("y2", y(maxRatio))
.attr("fill", "none")
.attr("stroke", colorHighlight)
.attr("stroke-width", 2);
//.attr("stroke-dasharray", "6 2");

// Small legend
maxLimits
.append("line")
.attr("x1", 0)
.attr("x2", 20)
.attr("y1", heightChart - 12)
.attr("y2", heightChart - 12)
.attr("fill", "none")
.attr("stroke", colorHighlight)
.attr("stroke-width", 2);

// Legend
const maxLimitText = maxLimits
.append("text")
.style("text-anchor", "start")
.style("font-weight", 400)
.style("font-size", "0.65rem")
.attr("x", 25)
.attr("y", (d) => heightChart - 10);

/*.text(
dataLocale[languageSelector][
isPressureChart ? "titleChartPressure" : "titleChartRatio"
] + ` (${dataLocale[languageSelector][fieldTitle]})`
)*/
maxLimitText
.append("tspan")
.text(`${formatIntegersReduced(maxRatio)}: cupo máximo`);
maxLimitText.append("tspan").text(` recomendado por ${maxSource}.`);
}

// Chart title
const textTitle = svg.append("g").attr("class", "text-title");
textTitle
.append("text")
.style("text-anchor", "start")
.style("font-weight", 700)
.style("font-size", "0.7rem")
.attr("dy", 0)
.attr("x", xOffset)
.attr("y", (d) => heightChart - 25)
.text(
dataLocale[languageSelector][
isPressureChart ? "titleChartPressure" : "titleChartRatio"
] + ` (${dataLocale[languageSelector][fieldTitle]})`
);

return svg.node();
}
Insert cell
function createEvolutionChart_onTop(data, field, regionCode) {
const speciality = field.split("_")[1];
const isPressureChart = field.split("_")[0] === "pressure";
const isArea = data[0].data_type === "area";

let colorScale;
let rangePressure;
let rangeRatio;
let maxRatio;
let partialData;
const offset = 5;
if (speciality === "mf") {
colorScale = color_mf;
maxRatio = maxRatio_mf;
rangePressure = rangeFullSerie_Pressure_mf;
rangeRatio = rangeFullSerie_Ratio_mf;
partialData = isPressureChart ? `pressure_mf_partial` : "";
} else if (speciality === "enf") {
colorScale = color_enf;
maxRatio = maxRatio_enf;
rangePressure = rangeFullSerie_Pressure_enf;
rangeRatio = rangeFullSerie_Ratio_enf;
partialData = isPressureChart ? `pressure_enf_partial` : "";
} else if (speciality === "ped") {
colorScale = color_ped;
maxRatio = maxRatio_ped;
rangePressure = rangeFullSerie_Pressure_ped;
rangeRatio = rangeFullSerie_Ratio_ped;
partialData = isPressureChart ? `pressure_ped_partial` : "";
}
const range = isPressureChart ? rangePressure : rangeRatio;
const margin = marginChart;

const mainColorRatio = colorNeutral(800);
const currentArray = data.map((d) => d[field]);
const areAllNulls = currentArray.every((d) => typeof d !== "number");
const dataCleaned = data.filter(function (d) {
return typeof d[field] === "number";
});
const lastIndexNotNull = dataCleaned.findLastIndex((d) => d);

// SPECIAL CASE: NO PEDIATRIA IN some CSS
const noPediatriaCS =
isPressureChart &&
speciality === "ped" &&
currentArray[lastIndexNotNull] === "No tiene pediatría";

// Create SVG
const svg = d3
.create("svg")
.attr("class", "evolutionChart")
.attr("width", widthChart)
.attr("height", heightChart);

// Update domain Y scale
y.domain(range).range([heightChart - margin.bottom, margin.top]);
x.range([margin.left, widthChart - margin.right]);

if (!areAllNulls) {
// Show background charts
document.getElementById(
`${isPressureChart ? "pressure" : "ratio"}-chart_bckgnd-${speciality}`
).style.opacity = 1;

// Actual chart
svg
.append("path")
.datum(dataCleaned)
.attr("fill", "none")
.attr("stroke", isPressureChart ? colorNeutral(600) : colorNeutral(500))
.attr("stroke-width", 2)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr(
"d",
d3
.line()
.x((d) => x(d.date))
.y((d) => y(d[field]))
);

// Data points
svg
.selectAll("circle")
.data(dataCleaned)
.join("circle")
.attr("cx", (d) => x(d.date))
.attr("cy", (d) => y(d[field]))
.attr("r", function (d, i) {
const myRadius = i === lastIndexNotNull ? 4.2 : 3;
return myRadius;
})
.attr("fill", (d) =>
isPressureChart ? colorScale(d[field]) : mainColorRatio
);

if (isPressureChart) {
const partialNote = svg.append("g");

partialNote
.selectAll("text")
.data(dataCleaned)
.join("text")
.attr("class", "partial-data-note")
.attr("x", 5)
.attr("y", heightChart - 10)
.text((d) => d[partialData])
.style("font-weight", 600)
.style("fill", (d) => colorScale(d[field]));
}

const textChart = svg.append("g").attr("class", "text-chart");
textChart
.selectAll("text")
.data(dataCleaned)
.join("text")
.style("text-anchor", "middle")
.style("font-weight", (d, i) => (i === lastIndexNotNull ? 800 : 600))
.style("font-size", (d, i) =>
i === lastIndexNotNull ? "0.75rem" : "0.65rem"
)
.style("opacity", (d, i) => (i === lastIndexNotNull ? 1 : 0.8))
.attr("dy", -10)
.attr("x", (d) => x(d.date))
.attr("y", (d) => y(d[field]))
.text((d) =>
isPressureChart
? formatDecimal(d[field]) + (d[partialData] ? " (*)" : "")
: formatIntegers(d[field])
)
// To improve readability
// White background
.attr("stroke", colorNeutral(0))
.attr("stroke-width", 2.5)
// Black visible text
.clone(true)
.style("fill", (d) =>
isPressureChart ? colorScale(d[field]) : mainColorRatio
)
.attr("stroke", "none");
} else {
// Hide background charts
document.getElementById(
`${isPressureChart ? "pressure" : "ratio"}-chart_bckgnd-${speciality}`
).style.opacity = 0;

showNoDataInfo(
svg,
isPressureChart,
isArea,
speciality,
regionCode,
noPediatriaCS
);
}

return svg.node();
}
Insert cell
function showNoDataInfo(
svg,
isPressureChart,
isArea,
speciality,
regionCode,
noPediatriaCS
) {
// Show message - info not available
const textNoteX = 5;
const textNoteY = 40;
const textNoteOffset = 15;
const colorNoteNoData = colorNeutral(400);
const colorNoteDataOtherZoom = colorNeutral(900);
const textMessage = svg
.append("text")
.style("font-size", "0.7rem")
.style("font-weight", 400)
.attr("fill", colorNoteNoData)
.attr("x", textNoteX)
.attr("y", textNoteY);

// Special case
if (noPediatriaCS) {
textMessage
.append("tspan")
.attr("x", textNoteX)
.attr("dy", textNoteOffset)
.text("Este Centro de Salud NO tiene pediatría");
} else {
// General case
textMessage
.append("tspan")
.attr("x", textNoteX)
.attr("dy", textNoteOffset)
.text("No hay información disponible sobre");

textMessage
.append("tspan")
.attr("x", textNoteX)
.attr("dy", textNoteOffset)
.text(
`${
isPressureChart
? "presión asistencial (pacientes/día)."
: "cupo (pacientes/profesional)."
}`
);
}

// Areas level
if (isArea) {
// Check if there's something in the next level
const thereIsDataPoints = resumeData
.filter((e) => e.region_code === regionCode)
// See if there's data
.some(
(e) => e[`pressure_points_${speciality}`] // points can just have pressure data, not ratio one
);

if (thereIsDataPoints) {
// Encourage to make zoom in
textMessage
.append("tspan")
// More prominent message visually
.style("font-size", "0.8rem")
.style("font-weight", 700)
.attr("fill", colorNoteDataOtherZoom)
.attr("x", textNoteX)
.attr("dy", textNoteOffset + 10)
.text("🔎 Amplía el zoom para ver estos datos");
textMessage
.append("tspan")
// More prominent message visually
.style("font-size", "0.8rem")
.style("font-weight", 700)
.attr("fill", colorNoteDataOtherZoom)
.attr("x", textNoteX)
.attr("dy", textNoteOffset)
.text("detallados por centros de salud.");
}
}
// Points levels
else if (!isArea) {
// Check if there's something in the previous level
const thereIsDataAreas = resumeData
.filter((e) => e.region_code === regionCode)
// See if there's data
.some(
(e) => e[`${isPressureChart ? "pressure" : "ratio"}_area_${speciality}`]
);

if (thereIsDataAreas) {
// Encourage to make zoom out
textMessage
.append("tspan")
.style("font-size", "0.8rem")
.style("font-weight", 700)
.attr("fill", colorNoteDataOtherZoom)
.attr("x", textNoteX)
.attr("dy", 25)
.text("🔎 Aleja el zoom para ver estos datos");
textMessage
.append("tspan")
.style("font-size", "0.8rem")
.style("font-weight", 700)
.attr("fill", colorNoteDataOtherZoom)
.attr("x", textNoteX)
.attr("dy", 15)
.text("detallados por áreas de salud.");
}
}
}
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
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {
dataMerge_areas,
dataMerge_points,
resumeData
} from "@civio/754-buscador-atencion-primaria-pre-dataset-2-0@latest"
Insert cell
Insert cell
dataMerge_points
Insert cell
Insert cell
Insert cell
Insert cell
Inputs.table(dataMerge_areas.features.map((d) => d.properties))
Insert cell
Insert cell
resumeData
Insert cell
Inputs.table(resumeData)
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