charts/lollipop.mjs (292 lines of code) (raw):

import dataTools from "./shared/dataTools" import ColorScale from "./shared/colorscale" import colorPresets from "./constants/colors" import { getURLParams, numberFormat, mustache, mobileCheck, setMinToMax, textPadding, textPaddingMobile, bufferize, tickTok, contains, wrap, getLabelFromColumn } from './shared/toolbelt'; import { addLabel, clickLogging } from './shared/arrows' import { drawShowMore } from "./shared/showmore" export default class Lollipop { constructor(settings) { this.settings = settings this.init() } init() { console.log('enableshowmore',this.settings.enableShowMore) drawShowMore(this.settings.enableShowMore) this.render() } render() { let { modules, type, colors, height, width, featuresWidth, featuresHeight, svgWidth, svgHeight, isMobile, title, subtitle, source, marginleft, margintop, marginbottom, marginright, tooltip, data, datum, labels, userkey, keys, rowHeight, enableShowMore, colorScheme, dropdown, groupBy, minX, maxX, xFormat, xScale, yScale, parseTime, xAxisLabel, minMax, xColumn, columns } = this.settings d3.select("#graphicContainer svg").remove() const chartKey = d3.select("#chartKey") chartKey.html("") datum = JSON.parse(JSON.stringify(data)) colors = new ColorScale() isMobile = mobileCheck() let lollies = keys.filter(d => d != 'Color' && d != groupBy) const keyColor = dataTools.getKeysColors({ keys: lollies, userKey: userkey, option: { colorScheme : colorScheme } }) colors.set(keyColor.keys, keyColor.colors) svgWidth = document .querySelector("#graphicContainer") .getBoundingClientRect().width svgHeight = datum.length * rowHeight + margintop + marginbottom featuresWidth = svgWidth - marginright - marginleft featuresHeight = svgHeight - margintop - marginbottom let range = [] datum.forEach(function(d) { if (xFormat.date) { lollies.forEach((lolly) => { if (contains(lollies, lolly)) { d[lolly] = parseTime( d[lolly] ) } }) } }) for (var i = 0; i < datum.length; i++) { lollies.forEach((d) => { range.push(datum[i][d]) }) } let extent = d3.extent(range) // Removed temporarily as getMinMax is only needed for charts where we want to use the same min and max on the positive and negative x axis, getMinMax // has been renamed to setMinToMa // const minMax = getMinMax(range.map(d => d)) // let max = minMax.max // let min = minMax.min // Bufferize now takes a percetange as the third argument let buffer = bufferize(extent[0], extent[1]) console.log("extent", extent, "buffer",buffer) minX = (!isNaN(minX)) ? buffer[0] : +minX maxX = (!isNaN(maxX)) ? buffer[1] : +maxX const svg = d3 .select("#graphicContainer") .append("svg") .attr("width", svgWidth) .attr("height", svgHeight) .attr("id", "svg") .attr("overflow", "hidden") const features = svg .append("g") .attr("transform","translate(" + marginleft + "," + margintop + ")") var x = d3[xScale]() x.range([ 0, featuresWidth]); //features.append("g") //.attr("transform", "translate(0," + svgHeight + ")") //.call(d3.axisBottom(x)) console.log("yScale", yScale) var y = d3[yScale]() .range([ 0, featuresHeight]) .domain(datum.map(function(d) { return d[groupBy]; })) .padding(0.9); (xFormat.date) ? x.domain(d3.extent(range)) : x.domain([minX, maxX]); //.nice() // if (minMax.status || x(0) > marginleft) { features.append('line') .style("stroke", "#767676") .style("stroke-width", 1) .attr("x1", x(0)) .attr("y1", margintop / 2) .attr("x2", x(0)) .attr("y2", svgHeight) .attr("opacity", 0.5); } if (lollies.length == 1) { if (minX < 0 && maxX > 0) { features.append("line") .attr("x1", function(d) { return x(0); }) .attr("x2", function(d) { return x(0); }) .attr("y1", function(d) { return 0; }) .attr("y2", function(d) { return featuresHeight; }) .style("stroke", '#767676') .style("stroke-width", "1px") } features.selectAll("lines") .data(datum) .enter() .append("line") .attr("x1", function(d) { return x(0); }) .attr("x2", function(d) { return x(+d[lollies[0]]); }) .attr("y1", function(d) { return y(d[groupBy]); }) .attr("y2", function(d) { return y(d[groupBy]); }) .style("stroke", (d, i) => { return (d.Color) ? d.Color : colors.get(d[lollies[0]]) }) .style("stroke-width", "4px") features .selectAll(".barText") .data(datum) .enter() .append("text") .attr("class", "barText") .attr("x", function(d) { let gap = (d[lollies[0]] < 0 ) ? -6 : 6 ; return x(0) + gap //(d[lollies[0]] > 0) ? x(d[lollies[0]]) + 20 : x(d[lollies[0]]) - 20; }) .attr("text-anchor",function(d) { return (d[lollies[0]] > 0) ? "start" : "end" ; }) .attr("y", function(d) { return y(d[groupBy]) - (rowHeight / 3.5); }) .text((d) => d[groupBy]) } else { features .selectAll(".barText") .data(datum) .enter() .append("text") .attr("class", "barText") .attr("x", function(d) { let range = [] for (var i = 0; i < lollies.length; i++) { range.push(d[lollies[i]]) } return x(d3.min(range)) - 3 }) .attr("text-anchor",function(d) { return "start" //(minMax.status) ? "start" : "end" ; }) .attr("y", function(d) { return y(d[groupBy]) - (rowHeight / 3); }) .text((d) => d[groupBy]) } const xTicks = tickTok(isMobile, x.domain(), featuresWidth) // Set the number of ticks const xAxis = g => g .attr("transform", `translate(0,${margintop / 2})`) .attr("class", "axisgroup") .call(d3.axisTop(x).tickSizeOuter(0)) .call(d3.axisTop(x) .tickSize(-svgHeight, 0, 0) .ticks(xTicks) .tickFormat((d) => { return xFormat.date ? d3.timeFormat("%b %Y")(d) : numberFormat(d) }) .tickPadding(10)) const yAxis = g => g .call(d3.axisLeft(y)) features .append("g") .attr("class", "x") .attr("transform", "translate(0," + svgHeight + ")") .call(xAxis) if (lollies.length == 2) { let col1 = colors.get(lollies[0]) let col2 = colors.get(lollies[1]) features.selectAll("lines") .data(datum) .enter() .append("line") .attr("x1", function(d) { return x(+d[lollies[0]]); }) .attr("x2", function(d) { return x(+d[lollies[1]]); }) .attr("y1", function(d) { return y(d[groupBy]); }) .attr("y2", function(d) { return y(d[groupBy]); + 10}) .attr("stroke", "#bdbdbd") .style("stroke-width", "6px") } // features // .append("circle") // .attr("cx", `${ (featuresWidth) /2}`) // .attr("cy", `${ (featuresHeight) /2}`) // .attr("fill", "red") // .attr("r", 5) if (lollies.length > 1) { lollies.forEach((k, i) => { const keyDiv = chartKey .append("div") .attr("class", "keyDiv") keyDiv .append("span") .attr("class", "keyCircle") .style("background-color", () => colors.get(k)) keyDiv .append("span") .attr("class", "keyText") .text(getLabelFromColumn(columns, k)) }) } if (xAxisLabel) { svg .append("text") .attr("x", marginleft) .attr("y", margintop / 2) .attr("fill", "#767676") .attr("text-anchor", "start") .text(xAxisLabel) //.call(wrap, marginleft > 15 ? marginleft - 15 : marginleft); // Assuming `maxWidth` is defined } for (const lolly of lollies) { features.selectAll(".lolly") .data(datum) .enter() .append("circle") .attr("cx", function(d) { return x(+d[lolly]); }) .attr("cy", function(d) { return y(d[groupBy]); }) .attr("r", "7") .style("fill", (d, i) => { return (d.Color) ? d.Color : colors.get(lolly) }) .style("stroke", (d, i) => { return (d.Color) ? d.Color : colors.get(lolly) }) } if (labels.length > 0) { const clickLoggingOn = getURLParams("labelling") ? true : false ; console.log("clickLoggingOn", clickLoggingOn); // Move this to wrangle later once we re-factor the labelling stuff if (typeof labels[0].coords === 'string') { labels.forEach(function(d) { d.coords = JSON.parse(d.coords) d.sweepFlag = +d.sweepFlag d.largeArcFlag = +d.largeArcFlag d.radius = +d.radius }) } console.log("annotations", labels) labels.forEach((config) => { addLabel(svg, config, svgWidth, svgHeight, { "left": marginleft, "right": marginright, "top": margintop, "bottom": marginbottom }, clickLoggingOn) }) } } }