@@ -398,40 +398,80 @@ function injectPollChoices(model, poll) {
398398 const m = JSON . parse ( JSON . stringify ( model ) ) ; // deep clone
399399 const choices = poll . choices ;
400400
401- // Remove the generic "tallies" place
401+ // Remove the generic castVote transition and tallies place
402+ delete m . transitions [ 'castVote' ] ;
402403 delete m . places [ 'tallies' ] ;
403404
404- // Add a place for each choice, arranged in a row below castVote
405- const castVote = m . transitions [ 'castVote' ] || { x : 300 , y : 50 } ;
406- const startX = castVote . x - ( ( choices . length - 1 ) * 80 ) / 2 ;
405+ // Remove all arcs involving castVote or tallies
406+ m . arcs = m . arcs . filter ( a =>
407+ a . source !== 'castVote' && a . target !== 'castVote' &&
408+ a . source !== 'tallies' && a . target !== 'tallies'
409+ ) ;
410+
411+ // Layout: choices fan out from a central point
412+ const cx = 300 , topY = 40 ;
413+ const spacing = Math . max ( 120 , 400 / choices . length ) ;
414+ const startX = cx - ( ( choices . length - 1 ) * spacing ) / 2 ;
407415
408416 choices . forEach ( ( name , i ) => {
409- const placeId = 'tally:' + name ;
410- m . places [ placeId ] = {
411- x : Math . round ( startX + i * 80 ) ,
412- y : castVote . y + 180 ,
417+ const x = Math . round ( startX + i * spacing ) ;
418+
419+ // Each choice gets its own vote transition (square)
420+ m . transitions [ 'vote:' + name ] = {
421+ x : x ,
422+ y : topY ,
423+ '@type' : 'Transition' ,
424+ } ;
425+
426+ // Each choice gets its own tally place (circle)
427+ m . places [ 'tally:' + name ] = {
428+ x : x ,
429+ y : topY + 140 ,
413430 initial : [ 0 ] ,
414431 '@type' : 'Place' ,
415432 offset : 0 ,
416433 capacity : [ null ] ,
417434 } ;
418- } ) ;
419435
420- // Replace castVote→tallies arc with one arc per choice
421- m . arcs = m . arcs . filter ( a => ! ( a . source === 'castVote' && a . target === 'tallies' ) ) ;
422- choices . forEach ( ( name ) => {
436+ // vote:choice → tally:choice (records the vote)
423437 m . arcs . push ( {
424- source : 'castVote' ,
438+ source : 'vote:' + name ,
425439 target : 'tally:' + name ,
426440 weight : [ 1 ] ,
427441 inhibitTransition : false ,
428442 '@type' : 'Arrow' ,
429443 } ) ;
444+
445+ // vote:choice → nullifiers (prevents double voting)
446+ m . arcs . push ( {
447+ source : 'vote:' + name ,
448+ target : 'nullifiers' ,
449+ weight : [ 1 ] ,
450+ inhibitTransition : false ,
451+ '@type' : 'Arrow' ,
452+ } ) ;
430453 } ) ;
431454
432- // Update model name to poll title
433- m . name = poll . title || m . name ;
455+ // Reposition remaining places below the choices
456+ const bottomY = topY + 300 ;
457+ const remaining = Object . keys ( m . places ) . filter ( k => ! k . startsWith ( 'tally:' ) ) ;
458+ remaining . forEach ( ( id , i ) => {
459+ const rx = Math . round ( cx - ( ( remaining . length - 1 ) * 100 ) / 2 + i * 100 ) ;
460+ m . places [ id ] . x = rx ;
461+ m . places [ id ] . y = bottomY ;
462+ } ) ;
434463
464+ // Reposition createPoll and closePoll
465+ if ( m . transitions [ 'createPoll' ] ) {
466+ m . transitions [ 'createPoll' ] . x = cx - 150 ;
467+ m . transitions [ 'createPoll' ] . y = bottomY - 80 ;
468+ }
469+ if ( m . transitions [ 'closePoll' ] ) {
470+ m . transitions [ 'closePoll' ] . x = cx + 150 ;
471+ m . transitions [ 'closePoll' ] . y = bottomY - 80 ;
472+ }
473+
474+ m . name = poll . title || m . name ;
435475 return m ;
436476}
437477
0 commit comments