@@ -721,194 +721,3 @@ class: thanks
721721[ ryanw@hudcostreets.org ] · [ hudcostreets.org] ( https://hudcostreets.org ) · [ @hudcostreets ] ( https://instagram.com/hudcostreets )
722722
723723Slides + source: [ safe26.hccs.dev] ( https://safe26.hccs.dev )
724-
725- ---
726- class: unitgrid-exp
727- clicks: 0
728- ---
729-
730- <script setup >
731- import { computed , ref } from ' vue'
732-
733- // Full-year (annualized) figures; the YTD toggle prorates these to today.
734- const KSI = {
735- killed: { driver: 301 , passenger: 92 , pedestrian: 173 , cyclist: 16 },
736- serious: { driver: 1977 , passenger: 1001 , pedestrian: 703 , cyclist: 257 },
737- moderate: { driver: 21249 , passenger: 9545 , pedestrian: 1840 , cyclist: 1256 },
738- minor: { driver: 26441 , passenger: 12624 , pedestrian: 1539 , cyclist: 800 },
739- }
740- const TYPES = [' driver' , ' passenger' , ' pedestrian' , ' cyclist' ]
741- const TYPE_LABELS = { driver: ' Driver' , passenger: ' Passenger' , pedestrian: ' Ped' , cyclist: ' Cyclist' }
742- const COLORS = { driver: ' #a94c9a' , passenger: ' #f08030' , pedestrian: ' #d85a6a' , cyclist: ' #7c5295' }
743- const ROWS = [
744- { key: ' killed' , label: ' Killed' },
745- { key: ' serious' , label: ' Serious injury' },
746- { key: ' moderate' , label: ' Moderate' },
747- { key: ' minor' , label: ' Minor / other' },
748- ]
749-
750- // Fraction of the calendar year elapsed (uniform proration).
751- const now = new Date ()
752- const yStart = new Date (now .getFullYear (), 0 , 1 )
753- const yEnd = new Date (now .getFullYear () + 1 , 0 , 1 )
754- const yearFrac = (now - yStart) / (yEnd - yStart)
755- const asOf = now .toLocaleDateString (' en-US' , { month: ' short' , day: ' numeric' })
756-
757- const ytd = ref (false )
758- const factor = computed (() => ytd .value ? yearFrac : 1 )
759- const scaledCounts = (key ) => {
760- const f = factor .value
761- return Object .fromEntries (TYPES .map (t => [t, KSI [key][t] * f]))
762- }
763-
764- const selected = ref ([... TYPES])
765- const allSelected = () => TYPES .every (t => selected .value .includes (t))
766- const isSel = (t ) => selected .value .includes (t)
767- function selectAll () { selected .value = [... TYPES] }
768- function clickType (t ) {
769- if (allSelected ()) { selected .value = [t]; return }
770- const s = new Set (selected .value )
771- s .has (t) ? s .delete (t) : s .add (t)
772- if (s .size === 0 || s .size === TYPES .length ) selectAll ()
773- else selected .value = TYPES .filter (x => s .has (x))
774- }
775- const rowTotal = (key ) => Math .round (selected .value .reduce ((sum , t ) => sum + KSI [key][t], 0 ) * factor .value )
776- </script >
777-
778- <div class =" exp-head " >
779- <h1 >Who gets hurt — by severity <span class =" exp-tag " >experiment</span ></h1 >
780- <div class =" scope-toggle " >
781- <button :class="{ active: !ytd }" @mousedown.prevent @click="ytd = false">Annual</button>
782- <button :class="{ active: ytd }" @mousedown.prevent @click="ytd = true">YTD · {{ asOf }}</button>
783- </div >
784- </div >
785-
786- <div class =" rows " >
787- <div class =" urow " v-for =" row in ROWS " :key =" row.key " >
788- <div class="meta">
789- <span class="n">{{ rowTotal(row.key).toLocaleString() }}</span>
790- <span class="lab">{{ row.label }}</span>
791- </div>
792- <div class="box">
793- <UnitGrid :counts="scaledCounts(row.key)" :colors="COLORS" :types="TYPES" :selected="selected" />
794- </div>
795- </div >
796- </div >
797-
798- <div class =" vt-select " >
799- <button : class ="{ active: allSelected() }" @mousedown .prevent @click ="selectAll">All</button >
800- <button
801- v-for="t in TYPES"
802- : key ="t"
803- : class ="[ t, { active: isSel(t) && !allSelected() }] "
804- : title ="TYPE_LABELS[ t] "
805- @mousedown .prevent
806- @click ="clickType(t)"
807- > <PersonIcon :type =" t " /></button >
808- </div>
809-
810- <style >
811- .slidev-layout.unitgrid-exp {
812- padding : 1rem 2rem 0.8rem ;
813- display : flex ;
814- flex-direction : column ;
815- .exp-head {
816- display : flex ;
817- align-items : center ;
818- justify-content : space-between ;
819- gap : 1rem ;
820- margin-bottom : 0.6rem ;
821- }
822- h1 { font-size : 1.8rem ; font-weight : 600 ; margin : 0 ; }
823- .exp-tag {
824- font-size : 0.8rem ;
825- font-weight : 400 ;
826- opacity : 0.6 ;
827- border : 1px solid rgba (255 ,255 ,255 ,0.4 );
828- border-radius : 999px ;
829- padding : 0.1rem 0.5rem ;
830- vertical-align : middle ;
831- }
832- .scope-toggle {
833- display : inline-flex ;
834- flex : 0 0 auto ;
835- border : 1px solid rgba (255 ,255 ,255 ,0.4 );
836- border-radius : 999px ;
837- overflow : hidden ;
838- button {
839- font-family : inherit ;
840- font-size : 0.85rem ;
841- padding : 0.25rem 0.9rem ;
842- background : transparent ;
843- color : rgba (255 ,255 ,255 ,0.8 );
844- border : none ;
845- cursor : pointer ;
846- white-space : nowrap ;
847- }
848- button .active { background : var (--hccs-accent ); color : #14401f ; font-weight : 600 ; }
849- }
850- .rows {
851- flex : 1 1 auto ;
852- display : grid ;
853- grid-template-rows : repeat (4 , 1fr );
854- gap : 0.7rem ;
855- min-height : 0 ;
856- }
857- .urow {
858- display : grid ;
859- grid-template-columns : 9rem 1fr ;
860- align-items : center ;
861- gap : 0.8rem ;
862- min-height : 0 ;
863- }
864- .meta {
865- display : flex ;
866- flex-direction : column ;
867- align-items : flex-end ;
868- line-height : 1.05 ;
869- }
870- .meta .n { font-size : 1.5rem ; font-weight : 700 ; color : var (--hccs-accent ); font-variant-numeric : tabular-nums ; }
871- .meta .lab { font-size : 0.95rem ; opacity : 0.9 ; }
872- .box {
873- height : 100% ;
874- min-height : 0 ;
875- background : rgba (0 ,0 ,0 ,0.13 );
876- border-radius : 4px ;
877- }
878- .vt-select {
879- display : flex ;
880- flex-wrap : wrap ;
881- justify-content : center ;
882- align-items : center ;
883- gap : 0.4rem ;
884- margin-top : 0.7rem ;
885- button {
886- display : inline-flex ;
887- align-items : center ;
888- justify-content : center ;
889- font-size : 0.85rem ;
890- padding : 0.2rem 0.55rem ;
891- border-radius : 999px ;
892- border : 1px solid rgba (255 ,255 ,255 ,0.55 );
893- background : rgba (255 ,255 ,255 ,0.92 );
894- color : #14401f ;
895- cursor : pointer ;
896- font-family : inherit ;
897- white-space : nowrap ;
898- }
899- button .person-icon { width : 1.35em ; height : 1.35em ; }
900- button .active { background : var (--hccs-accent ); color : #14401f ; border-color : var (--hccs-accent ); font-weight : 600 ; }
901- button .driver .person-icon { color : #a94c9a ; }
902- button .passenger .person-icon { color : #f08030 ; }
903- button .pedestrian .person-icon { color : #d85a6a ; }
904- button .cyclist .person-icon { color : #7c5295 ; }
905- button .driver.active { background : #a94c9a ; border-color : #a94c9a ; color : #fff ; }
906- button .passenger.active { background : #f08030 ; border-color : #f08030 ; color : #14401f ; }
907- button .pedestrian.active { background : #d85a6a ; border-color : #d85a6a ; color : #fff ; }
908- button .cyclist.active { background : #7c5295 ; border-color : #7c5295 ; color : #fff ; }
909- button .active .person-icon { color : inherit ; }
910- }
911- }
912- </style >
913-
914- [ ryanw@hudcostreets.org ] : mailto:ryanw@hudcostreets.org
0 commit comments