From 22ca1fff0aad68054fc0bcfeb1cbd0c84df567d5 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Fri, 27 Jun 2025 19:27:40 -0400 Subject: [PATCH] make all sidecar fields optional --- static/bargraph/barchart.js | 87 ++++++++++++++++++--------------- testData/bp-sidecar-medium.json | 6 +++ visualizer/sidecar/reader.py | 9 ++-- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/static/bargraph/barchart.js b/static/bargraph/barchart.js index 89fd4a3d..041b8bbf 100644 --- a/static/bargraph/barchart.js +++ b/static/bargraph/barchart.js @@ -496,34 +496,49 @@ function makeBarGraph(args) { } } } - function replaceTextWithSidecarData(d3TextElem) { - /* - * Wraps the text element in a href from the sidecar data. - * Precondition: this candidate must have sidecar data - */ + function replaceTextWithSidecarData(d3TextElem, textShift = 0) { d3TextElem.each(function() { - const textElem = d3.select(this), - name = textElem.text(), - data = candidateSidecarData['info'][name], - href = data['moreinfo_url'], - party = data['party'], + let textElem = d3.select(this), + name = textElem.text(), + data = candidateSidecarData['info'][name], + href = null, + party = null, + isIncumbent = null; + if (data) { + href = data['moreinfo_url']; + party = data['party']; isIncumbent = data['incumbent']; - - const link = textElem.text(null) - .append("a") + } + + // Clear existing content and set common attributes on + textElem + .text(null) + .attr("x", textShift) + .attr("text-anchor", "start") + .attr("dy", ".32em"); + + if (href != null) { + const link = textElem.append("a") .attr("href", href) .attr("target", "_blank") - .attr("dy", ".32em") .style("fill", "#1c5f99") .text(name); + + if (isIncumbent) { + link.classed("dataLabelIncumbent", true); + } + } else { + textElem.append("tspan") + .style("fill", "#1c5f99") + .text(name); + } + + if (party != null) { textElem.append("tspan") .attr("dx", "5px") .style("fill", "#999") .text(party); - - if (isIncumbent) { - link.classed("dataLabelIncumbent", true); - } + } }); } @@ -606,33 +621,25 @@ function makeBarGraph(args) { .attr("cy", 0) .attr("r", imageSize/2); - const candidatesWithoutSidecarData = candidateWrapper.selectAll(".tick") - .filter(d => candidateSidecarData['info'][d] === undefined); - - const candidatesWithSidecarData = candidateWrapper.selectAll(".tick") - .filter(d => candidateSidecarData['info'][d] !== undefined); - - candidatesWithSidecarData.selectAll(".tick text") - .call(c => replaceTextWithSidecarData(c)); + const candidates = candidateWrapper.selectAll(".tick"); + const textShift = imageSize + 10; + candidates.selectAll(".tick text") + .call(c => replaceTextWithSidecarData(c, textShift)); - candidatesWithSidecarData + candidates .append("image") - .attr("href", d => candidateSidecarData['info'][d]['photo_url']) + .attr("href", d => { + const info = candidateSidecarData['info'][d]; + let photoUrl = null; + if (info) { + photoUrl = info['photo_url']; + } + return photoUrl ? photoUrl : null; + }) .attr("clip-path", `url(#${circleDefId})`) .attr("width", imageSize) .attr("height", imageSize) .attr("y", -imageSize/2); - - // Shift the text over and word wrap - const textShift = imageSize + 10; - candidatesWithoutSidecarData.selectAll(".tick text") - .attr("text-anchor", "start") - .attr("x", textShift) - .call(magicWordWrap); - candidatesWithSidecarData.selectAll("a") - .attr("text-anchor", "start") - .attr("x", textShift) - .call(magicWordWrap); } else { @@ -853,4 +860,4 @@ function makeBarGraph(args) { $('[data-toggle="tooltip"]').tooltip(); return transitions; -} \ No newline at end of file +} diff --git a/testData/bp-sidecar-medium.json b/testData/bp-sidecar-medium.json index 4ff0277b..b45e3f90 100644 --- a/testData/bp-sidecar-medium.json +++ b/testData/bp-sidecar-medium.json @@ -12,6 +12,12 @@ "photo_url": "/static/visualizer/logo-white.png", "moreinfo_url": "https://ballotpedia.org/Link", "party": "(Green)" + }, + "Vanilla" : { + "incumbent": null, + "photo_url": null, + "moreinfo_url": null, + "party": null } }, "order": [ diff --git a/visualizer/sidecar/reader.py b/visualizer/sidecar/reader.py index 6d979b00..23aff713 100644 --- a/visualizer/sidecar/reader.py +++ b/visualizer/sidecar/reader.py @@ -5,6 +5,7 @@ """ import json + from visualizer import common @@ -62,10 +63,10 @@ def _assert_valid(self, graph): self._expect_in(expectedNames, "the actual candidates", candidate) self._expect_in(info, "the candidate info", 'incumbent') - assert isinstance(info['incumbent'], bool) - assert isinstance(info['photo_url'], str) - assert isinstance(info['moreinfo_url'], str) - assert isinstance(info['party'], str) + assert info['incumbent'] is None or isinstance(info['incumbent'], bool) + assert info['photo_url'] is None or isinstance(info['photo_url'], str) + assert info['moreinfo_url'] is None or isinstance(info['moreinfo_url'], str) + assert info['party'] is None or isinstance(info['party'], str) @classmethod def _expect_in(cls, data, dataDescriptor, field):