/* global d3, DATACHARTS, fetchDatacharts */ /* eslint-disable id-length, no-unused-vars, multiline-ternary, no-ternary, no-nested-ternary, no-invalid-this */ /* eslint prefer-reflect: ["error", { "exceptions": ["call"] }] */ /* eslint dot-location: ["error", "property"] */ // = require d3 const renderAreaCharts = () => { // lib const areachart = (opts = {}) => { // parse opts let data = opts.data let title = opts.title let container = d3.select(opts.container) let showAxis = opts.axis let ratio = opts.ratio let showTooltip = opts.tip !== "false" // set the dimensions and margins of the graph let margin = { top: 0, right: 0, bottom: 0, left: 0 } let width = Number(container.node().getBoundingClientRect().width) - margin.left - margin.right let height = (width / ratio) - margin.top - margin.bottom let titlePadding = d3.min([width / 10, 32]) // set the ranges let x = d3.scaleTime().range([0, width]); let y = d3.scaleLinear().range([height, 0]); // define the area let area = d3.area() .x((d) => x(d.key)) .y0(height) .y1((d) => y(d.value)); // define the line let valueline = d3.line() .x((d) => x(d.key)) .y((d) => y(d.value)); let svg = container.append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); // scale the range of the data x.domain(d3.extent(data, (d) => d.key)); y.domain([0, d3.max(data, (d) => d.value)]).nice(); // add the valueline path. let topLine = svg.append("path") .data([data]) .attr("class", "line") .attr("d", valueline) // add the area svg.append("path") .data([data]) .attr("class", "area") .attr("d", area) if (showTooltip) { // tooltip let circle = svg.append("circle") .attr("class", "circle") .attr("r", 6) .style("display", "none") let tooltip = d3.select("body").append("div") .attr("id", `${container.node().id}-tooltip`) .attr("class", "chart-tooltip") .style("opacity", 0) svg .on("mouseover", () => { circle.style("display", null) tooltip.style("opacity", 1) }) .on("mouseout", () => { circle.style("display", "none") tooltip.style("opacity", 0) }) .on("mousemove", function() { let x0 = x.invert(d3.mouse(this)[0]) let i = d3.bisector((d) => d.key).left(data, x0, 1) let d0 = data[i - 1] let d1 = data[i] let d = (x0 - d0.key > d1.key - x0) ? d1 : d0 // svg position relative to document let coords = { x: window.pageXOffset + container.node().getBoundingClientRect().left, y: window.pageYOffset + container.node().getBoundingClientRect().top } let tooltipContent = `
${d3.timeFormat("%e %B %Y")(d.key)}
${d.value.toLocaleString()} propuestas
` circle.attr("transform", `translate(${x(d.key)},${y(d.value)})`) tooltip.html(tooltipContent) .style("left", `${coords.x + x(d.key)}px`) .style("top", `${coords.y + y(d.value)}px`) }) } if (showAxis) { let xAxis = d3.axisBottom(x) .ticks(d3.timeMonth) .tickFormat(d3.timeFormat("%b %y")) .tickSize(-height) let yAxis = d3.axisLeft(y) .ticks(5) .tickSize(8) let _xAxis = (g) => { g.call(xAxis) g.select(".domain").remove() g.selectAll(".tick line").attr("class", "dashed") g.selectAll(".tick text").attr("y", 6) } let _yAxis = (g) => { g.call(yAxis) g.select(".domain").remove() g.select(".tick:first-of-type").remove() g.selectAll(".tick text").attr("text-anchor", "start").attr("x", 6) } // custom X-Axis svg.append("g") .attr("transform", `translate(0,${height})`) .call(_xAxis); // custom Y-Axis svg.append("g") .call(_yAxis) // last circle (current value) let g = svg.append("g") .data([data]) .attr("transform", (d) => `translate(${x(d[d.length - 1].key)},${y(d[d.length - 1].value)})`) g.append("circle") .attr("class", "circle") .attr("r", 8) g.append("text") .attr("class", "sum") .attr("text-anchor", "end") .attr("dx", -8 * 2) .text((d) => d[d.length - 1].value.toLocaleString()) } else { // add the title group let g = svg.append("g") .attr("text-anchor", "start") .attr("transform", `translate(${titlePadding},${titlePadding})`) g.append("text") .attr("x", 0) .attr("y", 0) .attr("class", "title") .text(title) g.append("text") .attr("x", 0) .attr("dy", titlePadding * 2) .attr("class", "sum") .text(Number(data.map((r) => r.value).reduce((a, b) => a + b, 0)).toLocaleString()) } } return $(".areachart:visible").each((i, container) => { // OPTIONAL: Helper function to preprocess the data const parseData = (data) => { // format the data data.forEach((d) => { d.key = d3.isoParse(d.key) d.value = Number(d.value) }); // order by date return data.sort((x, y) => d3.ascending(x.key, y.key)) } // OPTIONAL: Helper function to accumulates all data values const aggregate = (agg) => agg.map((item, index, array) => { if (index > 0) { item.value += array[index - 1].value } return item }) // If there's no data, fetch it if (!DATACHARTS || !DATACHARTS[container.dataset.metric]) { fetchDatacharts() } // MANDATORY: HTML must contain which metric should it display let data = DATACHARTS[container.dataset.metric].map((d) => { return { ...d } }) if (data) { let dataModified = aggregate(parseData(data)) areachart({ container: `#${container.id}`, title: container.dataset.title, data: dataModified, axis: (container.dataset.axis === "true") || false, ratio: container.dataset.ratio.split(":").reduce((a, b) => a / b) || (4 / 3), tip: container.dataset.tip }) } }) }