async function createChart(myDataset, chartNumber) {
const svg = d3
.create("svg")
.attr("id", chartNumber)
.attr("viewBox", [0, 0, myWidth, height]);
const x = d3
.scaleTime()
.domain(myDateDomainNice)
.range([margin.left, myWidth - margin.right])
.nice();
const unitSize = (+x.range()[1] - +x.range()[0]) / 12;
const y = d3
.scaleLinear()
.domain(d3.extent(myDataset, d => d.y))
.nice()
.range([height - margin.bottom, margin.top]);
const xAxis = g =>
g
.attr('class', 'xAxis')
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(
d3
.axisBottom(x)
.ticks(myWidth / 50)
.tickFormat(isMobile ? formatDateShort : formatDateComplete)
)
.call(g => g.select(".domain").remove())
.call(g =>
g
.selectAll(".tick text")
.attr('dx', unitSize / 2)
.style("text-anchor", "middle")
);
const yAxis = g =>
g
.attr('class', 'yAxis')
.attr("transform", `translate(${margin.left},0)`)
.call(
d3
.axisLeft(y)
.tickSize(isMobile ? 2 : 5)
.tickFormat(d => (isMobile ? formatMoneyReduced(d) : formatMoney(d)))
)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick text").style('font-size', "0.6rem"))
.call(g =>
g
.append("text")
.attr("class", "axisYTitle")
.style("font-size", isMobile ? "0.6rem" : "0.75rem")
.attr("x", isMobile ? -margin.left + 10 : -margin.left + 15)
.attr("y", isMobile ? margin.top - 10 : margin.top - 20)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(dataLocale[languageSelector].titleY)
);
const grid = g =>
g
.attr("stroke", colorQCLOOrange)
.attr("stroke-opacity", 0.2)
.call(g =>
g
.append("g")
.selectAll("line")
.data(x.ticks())
.join("line")
.attr("x1", d => 0.5 + x(d))
.attr("x2", d => 0.5 + x(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom)
)
.call(g =>
g
.append("g")
.selectAll("line")
.data(y.ticks())
.join("line")
.attr("y1", d => 0.5 + y(d))
.attr("y2", d => 0.5 + y(d))
.attr("x1", margin.left)
.attr("x2", width - margin.right)
);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg.append("g").call(grid);
// Highlight estado de alarma
const bandTime = svg.append("g");
bandTime
.append("rect")
.attr("x", x(estadoAlarmaRange[0]))
.attr("y", margin.top)
.attr("width", x(estadoAlarmaRange[1]) - x(estadoAlarmaRange[0]))
.attr("height", height - margin.top - margin.bottom)
.style("fill", colorQCLOOrange)
.attr("fill-opacity", 0.1);
bandTime
.append("text")
.attr("text-anchor", "end")
.attr("font-size", isMobile ? "0.6rem" : "0.7rem")
.attr("x", x(estadoAlarmaRange[0]))
.attr("y", margin.top)
.attr("dy", -5)
.attr("dx", -5)
.style("fill", colorQCLOOrange)
.attr("fill-opacity", 0.6)
.text(dataLocale[languageSelector].covidRange)
.attr("transform", `rotate(-90 ${x(estadoAlarmaRange[0])} ${margin.top})`);
const myMaxDataByChart = d3.max(myDataset, d => +d.amount);
let myMinDataByChart = d3.min(myDataset, d => +d.amount);
if (chartNumber === "chart01") {
myMinDataByChart = 120;
} else if (chartNumber === "chart02") {
myMinDataByChart = 10;
} else if (chartNumber === "chart03") {
myMinDataByChart = 200;
} else if (chartNumber === "chart04") {
myMinDataByChart = 100;
}
// Circles size based on area
const scaleCircles = d3
.scaleSqrt()
.domain([myMinDataByChart, myMaxDataByChart])
.range([3, 15]);
svg
.append("g")
.selectAll("circle")
.data(myDataset)
.join("circle")
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))
.style("fill-opacity", 0.6)
.attr("fill", colorQCLOOrange)
.attr("fill", d =>
d.winning_party_name.toUpperCase().includes("ABBOTT") ||
d.winning_party_name.toUpperCase().includes("ABBOT")
? colorAbbott
: colorQCLOOrange
)
.style(
"mix-blend-mode",
colorBlendEffect === "none" ? null : colorBlendEffect
)
.attr("r", circlesScaled ? d => scaleCircles(d.amount) : 3)
.style("cursor", "pointer")
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut);
let myTable;
if (chartNumber === "chart01") {
myTable = table01;
} else if (chartNumber === "chart02") {
myTable = table02;
} else if (chartNumber === "chart03") {
myTable = table03;
} else if (chartNumber === "chart04") {
myTable = table04;
}
// INTERACTIVE FILTERING
// Higlight search results on chart
if (myTable.length !== myDataset.length) {
const highlightedEl = svg
.selectAll('circle')
.filter(d => myTable.includes(d))
.attr("fill", "black");
}
//////// LEGEND
const valuesLegend = [myMinDataByChart, myMaxDataByChart];
const xPositLegend = isMobile ? myWidth - 110 : myWidth - 180;
const yPositLegend = isMobile ? 300 : 285;
const xLabel = xPositLegend + 30;
// Bubbles
const legend = svg.append("g").attr("class", "circlesLegend");
legend
.selectAll("circle")
.data(valuesLegend)
.join("circle")
.attr("cx", xPositLegend)
.attr("cy", d => yPositLegend - scaleCircles(d))
.attr("r", d => scaleCircles(d))
.style("fill", "none")
.attr("stroke", colorQCLOOrange);
// Lines
legend
.selectAll("line")
.data(valuesLegend)
.join("line")
.attr("x1", d => xPositLegend + scaleCircles(d))
.attr("x2", xLabel)
.attr("y1", d => yPositLegend - scaleCircles(d))
.attr("y2", d => yPositLegend - scaleCircles(d))
.style("stroke-dasharray", "2")
.attr("stroke", colorQCLOOrange)
.style("opacity", 0.6);
// Texts
legend
.selectAll("text")
.data(valuesLegend)
.join("text")
.attr("x", xLabel)
.attr("y", d => yPositLegend - scaleCircles(d) + 3)
.attr("dx", 2)
.text(d => `${format(d)} u`)
.style("fill", colorQCLOOrange)
.style("font-size", "0.6rem");
// Amount length
legend
.append("text")
.attr("x", xPositLegend)
.attr("y", yPositLegend - 35)
.text(d => `${dataLocale[languageSelector].legendSize}`)
.style("fill", colorQCLOOrange)
.style("font-size", "0.7rem")
.style("font-weight", 800)
.style("text-anchor", "middle");
// Abbot special case: annotation
if (chartNumber === "chart04" && subcategories04All == "Pruebas antígenos") {
const abbotAnnotation = svg
.append('text')
.attr('fill', colorAbbott)
.style('font-size', '0.7rem')
.attr('y', isMobile ? y(9) - 30 : y(7) + 10);
abbotAnnotation
.append('tspan')
.attr(
'x',
isMobile ? x(abbotAnnotationDate) - 5 : x(abbotAnnotationDate) + 5
)
.text(dataLocale[languageSelector].abbottNote1);
abbotAnnotation
.append('tspan')
.attr(
'x',
isMobile ? x(abbotAnnotationDate) - 5 : x(abbotAnnotationDate) + 5
)
.attr('dy', 15)
.text(dataLocale[languageSelector].abbottNote2);
svg
.append('line')
.attr("stroke", colorAbbott)
.attr('x1', x(abbotAnnotationDate))
.attr('y1', y(4.8))
.attr('x2', x(abbotAnnotationDate))
.attr('y2', isMobile ? y(9.5) : y(7))
.style("stroke-dasharray", "2")
.style("opacity", 0.6);
}
//////// TOOLTIP
const tooltipWidth = isMobile ? myWidth - 5 : 310;
const tooltipHeight = 160;
const yPositTooltip = isMobile ? 50 : margin.top + 30;
// Fixed tooltop
const fixedTooltip = svg
.append("rect")
.attr('class', 'tooltipBackground')
.attr('x', isMobile ? 5 : myWidth - margin.right - tooltipWidth - 20)
.attr('y', yPositTooltip)
.attr("height", tooltipHeight)
.attr("width", tooltipWidth)
.attr("fill", colorQCLOYellowLight)
.attr("fill-opacity", 0.5)
.attr("pointer-events", "none");
const iconContractWidth = 40;
const iconContractHeight = 40;
const scaleFactorIconContract = 1.3;
// Different icon on each chart
const iconTooltip = svg
.append("image")
.attr("class", "tooltipIcon")
.attr('width', iconContractWidth * scaleFactorIconContract)
.attr('height', iconContractWidth * scaleFactorIconContract)
.attr(
'x',
isMobile ? myWidth - margin.right - 50 : myWidth - margin.right - 80
)
.attr('y', yPositTooltip - 20)
.attr("opacity", 0.9);
// SVG title
const titleSvg = svg
.append("text")
.classed("svgTitle", true)
.attr('font-size', "1rem")
.attr("font-weight", 600)
.attr("text-anchor", "end")
.attr("fill", colorCivioMainBlue)
.attr('y', yPositTooltip - 5);
titleSvg
.append("tspan")
.attr(
'x',
isMobile ? myWidth - margin.right - 60 : myWidth - margin.right - 90
)
.attr('dy', 0)
.attr("dx", 10)
.text(dataLocale[languageSelector].titlePrices);
const chartElement = titleSvg
.append("tspan")
.attr("fill", colorQCLOOrange)
.attr('font-size', isMobile ? "1.12rem" : "1.20rem")
.attr(
'x',
isMobile ? myWidth - margin.right - 50 : myWidth - margin.right - 80
)
.attr('dy', 25)
.attr("dx", -10);
// Line details
const lineDetail = svg
.append("line")
.attr('stroke', colorQCLOYellow)
.attr('stroke-width', '4px')
.attr('y1', yPositTooltip + 30)
.attr('y2', yPositTooltip + 30);
if (chartNumber === "chart01") {
// Image
iconTooltip.attr("href", await FileAttachment("icon-masks.svg").url());
// Title
chartElement.text(dataLocale[languageSelector].masks);
lineDetail
.attr(
'x1',
isMobile ? myWidth - margin.right - 140 : myWidth - margin.right - 200
)
.attr(
'x2',
isMobile ? myWidth - margin.right - 80 : myWidth - margin.right - 120
);
} else if (chartNumber === "chart02") {
// Image
iconTooltip.attr("href", await FileAttachment("icon-gel.svg").url());
// Title
chartElement.text(dataLocale[languageSelector].gel);
lineDetail
.attr('x1', myWidth - margin.right - 200)
.attr('x2', myWidth - margin.right - 120);
} else if (chartNumber === "chart03") {
// Image
iconTooltip.attr("href", await FileAttachment("icon-gloves.svg").url());
// Title
chartElement.text(dataLocale[languageSelector].gloves);
lineDetail
.attr('x1', myWidth - margin.right - 170)
.attr('x2', myWidth - margin.right - 100);
} else if (chartNumber === "chart04") {
// Image
iconTooltip.attr("href", await FileAttachment("icon-tests.svg").url());
// Title
chartElement.text(dataLocale[languageSelector].tests);
// Line
lineDetail
.attr(
'x1',
isMobile ? myWidth - margin.right - 90 : myWidth - margin.right - 140
)
.attr(
'x2',
isMobile ? myWidth - margin.right - 60 : myWidth - margin.right - 90
);
}
// Tooltip titles
const toolTipText = svg
.append("g")
.attr("class", "tooltipData")
.attr('font-size', "0.72rem")
.attr("fill", colorCivioMainBlue)
.attr("text-anchor", "start");
const offsetTextsTooltip = 15;
// Tooltip answers
const tooltipAdminData = toolTipText
.append("text")
.classed("tooltipAdminData", true)
.attr("font-weight", 600)
.attr(
'x',
isMobile
? myWidth - margin.right - tooltipWidth + 20
: myWidth - margin.right - tooltipWidth - 10
)
.attr('y', yPositTooltip + 45);
const tooltipProductData = toolTipText
.append("text")
.classed("tooltipProductData", true)
.attr(
'x',
isMobile
? myWidth - margin.right - tooltipWidth + 20
: myWidth - margin.right - tooltipWidth - 10
)
.attr('y', yPositTooltip + 102);
const tooltipAgencyData = toolTipText
.append("text")
.classed("tooltipAgencyData", true)
.attr(
'x',
isMobile
? myWidth - margin.right - tooltipWidth + 20
: myWidth - margin.right - tooltipWidth - 10
)
.attr('y', yPositTooltip + 102);
// Interaction text
const interactionText = svg
.append("text")
.attr("class", "interactionNote")
.attr('font-size', "0.85rem")
.attr("fill", colorCivioMainBlue)
.attr("text-anchor", "middle")
.attr('y', yPositTooltip + 80);
const note1 = interactionText
.append("tspan")
.attr('x', myWidth - margin.right - tooltipWidth / 2)
.attr('dx', isMobile ? 0 : -20)
.text(
isMobile
? dataLocale[languageSelector].interactionNote1Mobile
: dataLocale[languageSelector].interactionNote1Desktop
);
const note2 = interactionText
.append("tspan")
.attr('x', myWidth - margin.right - tooltipWidth / 2)
.attr('dx', isMobile ? 0 : -20)
.attr('dy', 20)
.text(
isMobile
? dataLocale[languageSelector].interactionNote2Mobile
: dataLocale[languageSelector].interactionNote2Desktop
);
return svg.node();
}