News

ElectionResults

One race's results: candidates in party colours with votes and share bars, the share of the vote counted, the margin, and the winner once it is called.

#news#election#chart#table#no-js

Demo

Preview

Ohio · U.S. Senate

✓ Projected winner
Ohio · U.S. Senate: votes and share for each candidate; Maria Lopez is the projected winner
CandidateVotesShare
Maria LopezDEM, winner2,145,33249.8%
John BeckerREPIncumbent2,101,87648.8%
Ann WuLIB61,4221.4%
87% of expected vote countedUpdated 11:42 PM ET

Maria Lopez won by 43,456 votes (1.0 points)

Riverside · Mayor

Too early to call
Riverside · Mayor: votes and share for each candidate
CandidateVotesShare
Sam Ortiz18,20440.9%
Priya NairIND17,95040.3%
Dan ColeGRN6,01113.5%
Others2,3815.3%
41% of expected vote counted

Sam Ortiz leads by 254 votes (0.6 points)

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/election_results.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/election_results.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/election_results.qwe
view ElectionResultsDemo() =    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; align-items: start">        <ElectionResults id="election-results-demo-ohio" race="Ohio · U.S. Senate" href="#ohio-senate" reporting={87.4} called="Maria Lopez"                         updated="Updated 11:42 PM ET" candidates={[                             { name: "John Becker", party: "REP", votes: 2101876, incumbent: true },                             { name: "Maria Lopez", party: "DEM", votes: 2145332 },                             { name: "Ann Wu", party: "LIB", votes: 61422 },                         ]}/>        <ElectionResults id="election-results-demo-mayor" race="Riverside · Mayor" reporting={41} show={3} candidates={[                             { name: "Sam Ortiz", votes: 18204, color: "#0f8b8d" },                             { name: "Priya Nair", party: "IND", votes: 17950 },                             { name: "Dan Cole", party: "GRN", votes: 6011 },                             { name: "Lee Park", votes: 1404 },                             { name: "Ruth Ames", votes: 977 },                         ]}/>    </div>

Source

ui/election_results.qwe
// ElectionResults: one race on election night: each candidate with their party's// colour, votes and share as a bar, the most votes first; how much of the expected// vote is counted; and who won once the race is called ("✓ Winner", "Projected// winner"), or "Too early to call" while it is not, with the leader's margin.////   <ElectionResults race="Ohio · U.S. Senate" reporting={87} called="Maria Lopez" updated="Updated 11:42 PM ET"//       candidates={[//           { name: "Maria Lopez", party: "DEM", votes: 2145332 },//           { name: "John Becker", party: "REP", votes: 2101876, incumbent: true },//           { name: "Ann Wu", party: "LIB", votes: 61422 },//       ]}/>//// race: the race's name, an <h3>, a link to href when given. candidates: maps of// { name, votes, party?, color?, incumbent? }; party is shown after the name, and// colours the bar: "DEM" (or "D") blue, "REP" (or "R") red, "GRN" green, "LIB"// gold, "IND" and others grey; color: any CSS colour instead. incumbent: marks them// "Incumbent". reporting: the share of the expected vote counted, 0 to 100 (a// number); left out, no meter. called: the name of the winner once the race is// called; called_label: what it says then ("Projected winner"); uncalled_label:// what it says before ("Too early to call"). show: how many candidates to show (all;// the rest are added up as "Others"). updated: when, as it reads. locale: how numbers// are written ("en-US"). id: the element's id. The candidates are a <table>, so// screen readers read name, votes and share in turn.import ui/theme.qwe use ui_clslet election_results_css = css`.ui-elx { display: grid; gap: 12px; min-width: 0; max-width: 520px; padding: 16px 18px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius);  background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.4 var(--ui-font); font-variant-numeric: tabular-nums; }.ui-elx-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 12px; }.ui-elx .ui-elx-race { margin: 0; font-size: 17px; font-weight: 750; }.ui-elx-race a { color: inherit; text-decoration: none; }.ui-elx-race a:hover { color: var(--ui-accent); }.ui-elx-race a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-elx-call { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 700;  background: var(--ui-subtle); color: var(--ui-muted); white-space: nowrap; }.ui-elx-called .ui-elx-call { background: var(--ui-success-soft); color: var(--ui-success); }.ui-elx-table { width: 100%; border-collapse: collapse; }.ui-elx-table th { padding: 0 0 6px; color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; text-align: right; }.ui-elx-table th:first-child { text-align: left; }.ui-elx-table td { padding: 8px 0 10px; border-top: 1px solid var(--ui-border); vertical-align: top; text-align: right; white-space: nowrap; }.ui-elx-table td:first-child { width: 100%; text-align: left; white-space: normal; }.ui-elx-table td + td { padding-left: 14px; }.ui-elx-name { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; font-weight: 600; }.ui-elx-party { padding: 0 5px; border-radius: 3px; background: color-mix(in srgb, var(--c) 16%, transparent); color: var(--c); font-size: 11px; font-weight: 700; }.ui-elx-inc { color: var(--ui-muted); font-size: 12px; font-weight: 400; }.ui-elx-win { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: var(--ui-success); color: var(--ui-bg);  font-size: 11px; font-weight: 800; }.ui-elx-bar { display: block; height: 8px; margin-top: 6px; border-radius: 4px; background: var(--ui-subtle); overflow: hidden; }.ui-elx-bar span { display: block; height: 100%; border-radius: 4px; background: var(--c); }.ui-elx-lost .ui-elx-bar span { opacity: .45; }.ui-elx-pct { font-size: 16px; font-weight: 700; }.ui-elx-votes { color: var(--ui-muted); }.ui-elx-winner .ui-elx-pct { color: var(--ui-fg); }.ui-elx-c-dem { --c: var(--ui-info); }.ui-elx-c-rep { --c: var(--ui-danger); }.ui-elx-c-grn { --c: var(--ui-success); }.ui-elx-c-lib { --c: var(--ui-warning); }.ui-elx-c-other { --c: var(--ui-muted); }.ui-elx-foot { display: grid; gap: 6px; color: var(--ui-muted); font-size: 12px; }.ui-elx-count { display: flex; justify-content: space-between; gap: 8px; }.ui-elx-count b { color: var(--ui-fg); font-weight: 650; }.ui-elx-meter { height: 4px; border-radius: 2px; background: var(--ui-border); overflow: hidden; }.ui-elx-meter span { display: block; height: 100%; background: var(--ui-fg); }.ui-elx-margin { margin: 0; }.ui-elx-margin b { color: var(--ui-fg); font-weight: 650; }`// The class of a party's colour: "DEM" is dem.fn election_results_party(party: str?) -> str = match upper(party ?? "") {    "DEM" | "D" => "dem"    "REP" | "R" | "GOP" => "rep"    "GRN" | "G" | "GREEN" => "grn"    "LIB" | "L" => "lib"    _ => "other"}// A share to one decimal, as it reads: "51.2%".fn election_results_share(p: num, locale: str) -> str = `${format_number(round(p, 1), locale, 1)}%`view ElectionResults(race: str, candidates: list, reporting = null, called: str? = null, called_label: str = "Projected winner",                     uncalled_label: str = "Too early to call", show: int? = null, updated: str? = null, href: str? = null,                     locale: str = "en-US", id: str? = null, class: str? = null) {    style global election_results_css    let ranked = sort_by(candidates, (c) => -c.votes)    let kept = show != null && len(ranked) > show        ? take(ranked, show) + [{ name: "Others", votes: sum(drop(ranked, show) | map((c) => c.votes)), others: true }]        : ranked    let total = sum(candidates | map((c) => c.votes))    let share = (v) => total > 0 ? v * 100 / total : 0    let lead = len(ranked) > 1 ? ranked[0].votes - ranked[1].votes : null    let is_called = called != null    let counted = reporting != null ? (reporting < 0 ? 0 : (reporting > 100 ? 100 : reporting)) : null    let num = (n) => format_number(n, locale, 0)    <section class={ui_cls(["ui-elx", is_called && "ui-elx-called", class])} id={id} aria-label={`Results: ${race}`}>        <div.ui-elx-head>            <h3.ui-elx-race>{href != null ? <a href={href}>{race}</a> : race}</h3>            <span.ui-elx-call>{is_called ? ["✓ ", called_label] : uncalled_label}</span>        </div>        <table.ui-elx-table>            <caption class="ui-sr">{race}: votes and share for each candidate{is_called ? `; ${called} is the ${lower(called_label)}` : ""}</caption>            <thead><tr><th scope="col">Candidate</th><th scope="col">Votes</th><th scope="col">Share</th></tr></thead>            <tbody>                {for c in kept => {                    let won = is_called && c.name == called                    let tone = c.color != null ? "" : `ui-elx-c-${c.others == true ? "other" : election_results_party(c.party)}`                    <tr class={ui_cls([tone, won && "ui-elx-winner", is_called && !won && "ui-elx-lost"])}                        style={c.color != null ? `--c: ${c.color}` : null}>                        <td>                            <span.ui-elx-name>                                {won && <span.ui-elx-win aria-hidden="true">✓</span>}                                <span>{c.name}</span>                                {c.party != null && <span.ui-elx-party>{upper(c.party)}</span>}                                {c.incumbent == true && <span.ui-elx-inc>Incumbent</span>}                                {won && <span.ui-sr>, winner</span>}                            </span>                            <span.ui-elx-bar aria-hidden="true"><span style={`width: ${round(share(c.votes), 2)}%`}></span></span>                        </td>                        <td.ui-elx-votes>{num(c.votes)}</td>                        <td.ui-elx-pct>{election_results_share(share(c.votes), locale)}</td>                    </tr>                }}            </tbody>        </table>        <div.ui-elx-foot>            {counted != null && [                <div.ui-elx-count><span><b>{format_number(round(counted), locale, 0)}%</b> of expected vote counted</span>{updated != null && <span>{updated}</span>}</div>,                <div.ui-elx-meter role="progressbar" aria-label="Expected vote counted" aria-valuemin="0" aria-valuemax="100" aria-valuenow={round(counted)}>                    <span style={`width: ${round(counted, 2)}%`}></span>                </div>,            ]}            {counted == null && updated != null && <div.ui-elx-count><span>{updated}</span></div>}            {lead != null && total > 0 && <p.ui-elx-margin>                <b>{ranked[0].name}</b> {is_called && ranked[0].name == called ? "won" : "leads"} by {num(lead)} votes ({format_number(round(share(lead), 1), locale, 1)} points)            </p>}        </div>    </section>}
Thread
88Useful? Like it, or add to its thread below.

Thread 0 posts

On ElectionResults: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.