function LineChart(
data,
{
x = ([x]) => x,
y = ([y]) => y,
z = () => 1,
tipo = () => 1,
ajuste_ipc = () => 1,
año_ipc = ([año_ipc]) => año_ipc,
title,
defined,
curve = d3.curveLinear,
marginTop = 20,
marginRight = 30,
marginBottom = 100,
marginLeft = 50,
width = 640,
height = 800,
xType = d3.scaleLinear,
xDomain,
xRange = [marginLeft, width - marginRight],
yType = d3.scaleLinear,
yDomain,
yRange = [height - marginBottom, marginTop],
yFormat,
yLabel,
zDomain,
tDomain,
colors = ["#31739B", "#F58191", "#bbb"],
strokeLinecap = "round",
strokeLinejoin,
strokeWidth = 2,
strokeOpacity, // stroke opacity of line
mixBlendMode = "multiply", // blend mode of lines
voronoi // show a Voronoi overlay? (for debugging)
} = {}
) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);
const Z = d3.map(data, z);
const _tipo = d3.map(data, tipo);
const _ajuste_ipc = d3.map(data, ajuste_ipc);
const _año_ipc = d3.map(data, año_ipc);
const O = d3.map(data, (d) => d);
if (defined === undefined) defined = (d, i) => !isNaN(X[i]) && !isNaN(Y[i]);
const D = d3.map(data, defined);
// Compute default domains, and unique the z-domain.
if (xDomain === undefined) xDomain = d3.extent(X);
if (yDomain === undefined)
yDomain = [0, d3.max(Y, (d) => (typeof d === "string" ? +d : d))];
if (zDomain === undefined) zDomain = Z;
zDomain = new d3.InternSet(zDomain);
// Omit any data not present in the z-domain.
const I = d3.range(X.length).filter((i) => zDomain.has(Z[i]));
if (tDomain === undefined) tDomain = _tipo;
tDomain = new d3.InternSet(tDomain);
// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3
.axisBottom(xScale)
.ticks(width / 150)
.tickSizeOuter(0)
.tickFormat(d3.format(".0f"));
const yAxis = d3
.axisLeft(yScale)
.ticks(height / 60, yFormat)
.tickFormat(function (d) {
return formatNumberES(d, 0);
});
const color = d3.scaleOrdinal(tDomain, colors);
// Compute titles.
const T =
title === undefined ? Z : title === null ? null : d3.map(data, title);
// Construct a line generator sueldo
const line = d3
.line()
.defined((i) => D[i])
.curve(curve)
.x((i) => xScale(X[i]))
.y((i) => yScale(Y[i]));
// Construct a line generator IPC
const line2 = d3
.line()
.defined((i) => D[i])
.curve(curve)
.x((i) => xScale(X[i]))
.y((i) => yScale(_ajuste_ipc[i]));
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.style("-webkit-tap-highlight-color", "transparent")
.style("overflow", "visible")
.on("pointerenter", pointerentered)
.on("pointermove", pointermoved)
.on("pointerleave", pointerleft)
.on("touchstart", (event) => event.preventDefault());
svg
.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);
svg
.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call((g) => g.select(".domain").remove())
.call(
voronoi
? () => {}
: (g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1)
)
.call((g) =>
g
.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel)
);
//**** line y circulos _ajuste_ipc ****//
const path2 = svg
.append("g")
.attr("fill", "none")
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth)
.attr("stroke-opacity", 0.4)
.selectAll("path")
.data(d3.group(I, (i) => Z[i]))
.join("path")
.style("mix-blend-mode", mixBlendMode)
.attr("stroke", "#F58191")
.attr("d", ([, I]) => line2(I));
svg
.append("g")
.selectAll("circle")
.data(I)
.join("circle")
.attr("cx", (i) => xScale(X[i]))
.attr("cy", (i) => yScale(_ajuste_ipc[i]))
.attr("stroke", "white")
.attr("fill", "#F58191")
.attr("r", (i) => (_ajuste_ipc[i] == null ? 0 : 3));
///**** line y circulos sueldos ****//
const path = svg
.append("g")
.attr("fill", "none")
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth)
.attr("stroke-opacity", strokeOpacity)
.selectAll("path")
.data(d3.group(I, (i) => Z[i]))
.join("path")
.style("mix-blend-mode", mixBlendMode)
.attr("stroke", "#B7B5A8")
.attr("d", ([, I]) => line(I));
svg
.append("g")
.selectAll("circle")
.data(I)
.join("circle")
.attr("cx", (i) => xScale(X[i]))
.attr("cy", (i) => yScale(Y[i]))
.attr("stroke", "white")
.attr("fill", "grey")
.attr("r", (i) => (Y[i] == null ? 0 : 3));
/*****************************************/
/***** ROLLOVER SUELDO e IPC (dot2) ****/
/*****************************************/
const dot = svg.append("g");
dot.append("circle").attr("r", 2.5).attr("display", "none");
const dot2 = svg.append("g");
dot2.append("circle").attr("r", 2.5).attr("display", "none");
function pointermoved(event) {
const [xm, ym] = d3.pointer(event);
const i = d3.least(I, (i) =>
Math.hypot(xScale(X[i]) - xm, yScale(Y[i]) - ym)
); // closest point
path
.style("stroke", ([z]) => (Z[i] === z ? null : "#efefef"))
.filter(([z]) => Z[i] === z)
.raise();
dot.attr("transform", `translate(${xScale(X[i])},${yScale(Y[i])})`);
dot2.attr(
"transform",
`translate(${xScale(X[i])},${yScale(_ajuste_ipc[i])})`
);
const text = dot.selectAll("text");
if (T)
dot
.select("text")
.attr("font-weight", 400)
.text("En " + X[i] + " el sueldo")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.attr("font-weight", 400)
.text("del alcalde de")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.attr("font-weight", 700)
.text(T[i])
.append("tspan")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.attr("font-weight", 400)
.text("fue de ")
.append("tspan")
.attr("font-weight", 700)
.text(formatNumberES(Y[i]) + "€")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.attr("font-weight", 400)
.text(
"(Dedicación: " + (_tipo[i] == null ? "sin info" : _tipo[i]) + ")"
)
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.text("–––––––––")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.text("La proyección del sueldo")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.text("para este año")
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.text(
"respecto al de " + (_año_ipc[i] == null ? "sin info" : _año_ipc[i])
)
.append("tspan")
.attr("dy", 17)
.attr("x", 0)
.attr("font-weight", 700)
.attr("fill", "#F58191")
.text(
"sería de: " +
(_ajuste_ipc[i] == null
? "sin info"
: formatNumberES(_ajuste_ipc[i]) + "€")
);
const path2 = dot
.selectAll("path")
.data([,])
.join("path")
.attr("fill", "white")
.attr("stroke", "black");
dot
.append("text")
.attr("class", "tooltip")
.attr("text-anchor", "end")
.attr("y", -8);
// tooltip positioning
const { x, y, width: w, height: h } = text.node().getBBox();
if (X[i] < 2014) {
text.attr("transform", `translate(${-w / 2 + w / 1.3},${y})`);
} else if (X[i] > 2019) {
text.attr("transform", `translate(${-w / 2 - w / 1.3},${y - 30})`);
} else {
text.attr("transform", `translate(${-w / 2},${15 - y})`);
}
// tooltip container path
if (X[i] < 2014) {
path2.attr(
"d",
`M ${x + 20},${y - h / 2 + 50} L ${x + w + 60},${y - h / 2 + 50} L ${
x + w + 60
},${y + h} L ${x + 20},${y + h} L ${x + 20},${y - h / 2 + 50}`
);
} else if (X[i] > 2019) {
path2.attr(
"d",
`M ${x - 30},${y - h / 2 + 20} L ${x - w - 60},${y - h / 2 + 20} L ${
x - w - 60
},${y + h - 30} L ${x - 30},${y + h - 30} L ${x - 30},${y - h / 2 + 20}`
);
} else {
path2.attr(
"d",
`M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`
);
}
}
function pointerentered() {
path.style("mix-blend-mode", null).style("stroke", "#ddd");
dot.attr("display", true);
}
function pointerleft() {
path.style("mix-blend-mode", mixBlendMode).style("stroke", null);
dot.attr("display", "none");
svg.node().value = null;
svg.dispatch("input", { bubbles: true });
}
return Object.assign(svg.node(), { value: null });
}