@@ -351,7 +351,7 @@ export class SubmissionsService {
351351 this . setHeader ( docContent , 'Charts' ) ;
352352 for ( let i = 0 ; i < exam . questions . length ; i ++ ) {
353353 const question = exam . questions [ i ] ;
354- if ( question . type !== 'select' && question . type !== 'selectMultiple' ) { continue ; }
354+ if ( question . type !== 'select' && question . type !== 'selectMultiple' && question . type !== 'ratingScale' ) { continue ; }
355355 question . index = i ;
356356 docContent . push ( { text : `Q${ i + 1 } : ${ question . body } ` } ) ;
357357 if ( question . type === 'selectMultiple' ) {
@@ -389,6 +389,16 @@ export class SubmissionsService {
389389 ] ,
390390 alignment : 'center'
391391 } ) ;
392+ } else if ( question . type === 'ratingScale' ) {
393+ const ratingScaleAgg = this . aggregateQuestionResponses ( question , updatedSubmissions , 'count' ) ;
394+ const ratingScaleImg = await this . generateChartImage ( ratingScaleAgg ) ;
395+ docContent . push ( {
396+ stack : [
397+ { image : ratingScaleImg , width : 300 , alignment : 'center' , margin : [ 0 , 10 , 0 , 10 ] } ,
398+ { text : `Total respondents: ${ updatedSubmissions . length } ` , alignment : 'center' }
399+ ] ,
400+ alignment : 'center'
401+ } ) ;
392402 } else {
393403 const pieAgg = this . aggregateQuestionResponses ( question , updatedSubmissions , 'count' ) ;
394404 const pieImg = await this . generateChartImage ( pieAgg ) ;
@@ -552,16 +562,18 @@ export class SubmissionsService {
552562 canvas . width = 300 ;
553563 canvas . height = 400 ;
554564 const isBar = data . chartType === 'bar' ;
565+ const isRatingScale = data . isRatingScale || false ;
555566 const ctx = canvas . getContext ( '2d' ) ;
556567
557568 return new Promise < string > ( ( resolve ) => {
569+ const maxCount = Math . max ( ...data . data ) ;
558570 const chartConfig : ChartConfiguration < 'bar' | 'doughnut' > = {
559571 type : isBar ? 'bar' : 'doughnut' ,
560572 data : {
561573 labels : data . labels ,
562574 datasets : [ {
563575 data : data . data ,
564- label : isBar ? '% of responders/selection' : undefined ,
576+ label : isRatingScale ? 'selection/choices(1-9)' : ( isBar ? '% of responders/selection' : undefined ) ,
565577 backgroundColor : [
566578 '#FF6384' , '#36A2EB' , '#FFCE56' , '#4BC0C0' , '#9966FF' , '#FF9F40' , '#C9CBCF' , '#8DD4F2' , '#A8E6CF' , '#DCE775'
567579 ] ,
@@ -571,25 +583,33 @@ export class SubmissionsService {
571583 responsive : false ,
572584 maintainAspectRatio : false ,
573585 indexAxis : 'x' ,
586+ plugins : {
587+ legend : {
588+ display : true ,
589+ labels : {
590+ boxWidth : isBar ? 0 : 50 ,
591+ boxHeight : isBar ? 0 : 20
592+ }
593+ }
594+ } ,
574595 scales : isBar ? {
575596 y : {
576597 type : 'linear' ,
577598 beginAtZero : true ,
578- max : 100 ,
579- ticks : { precision : 0 }
599+ max : isRatingScale ? maxCount > 0 ? Math . ceil ( maxCount / 10 ) * 10 : 10 : 100 ,
600+ ticks : { precision : 0 , stepSize : 2 }
580601 }
581602 } : { } ,
582603 animation : {
583604 onComplete : function ( ) {
584605 if ( isBar && data . userCounts ) {
585606 this . getDatasetMeta ( 0 ) . data . forEach ( ( bar , index ) => {
586- const percentage = data . data [ index ] ;
587- const userCount = data . userCounts [ index ] ;
588- if ( percentage > 0 ) {
589- ctx . fillText ( `${ userCount } ` , bar . x - 2.5 , bar . y ) ;
607+ const count = data . userCounts [ index ] ;
608+ if ( count > 0 ) {
609+ ctx . fillText ( `${ count } ` , bar . x - 2.5 , bar . y ) ;
590610 }
591611 } ) ;
592- } else if ( ! isBar ) {
612+ } else {
593613 const total = data . data . reduce ( ( sum , val ) => sum + val , 0 ) ;
594614 this . getDatasetMeta ( 0 ) . data . forEach ( ( element , index ) => {
595615 const count = data . data [ index ] ;
@@ -609,37 +629,47 @@ export class SubmissionsService {
609629 } ) ;
610630 }
611631
612- aggregateQuestionResponses (
613- question ,
614- submissions ,
615- mode : 'percent' | 'count' = 'percent' ,
616- calculationMode : 'users' | 'selections' = 'users'
617- ) {
632+ aggregateQuestionResponses ( question , submissions , mode : 'percent' | 'count' = 'percent' , calculationMode : 'users' | 'selections' = 'users' ) {
618633 const totalUsers = submissions . length ;
619634 const counts : Record < string , Set < string > > = { } ;
620635
621- question . choices . forEach ( c => { counts [ c . text ] = new Set ( ) ; } ) ;
622- if ( question . hasOtherOption ) {
623- counts [ 'Other' ] = new Set ( ) ;
636+ if ( question . type === 'ratingScale' ) {
637+ for ( let i = 1 ; i <= 9 ; i ++ ) {
638+ counts [ i . toString ( ) ] = new Set ( ) ;
639+ }
640+ } else {
641+ question . choices . forEach ( c => { counts [ c . text ] = new Set ( ) ; } ) ;
642+ if ( question . hasOtherOption ) {
643+ counts [ 'Other' ] = new Set ( ) ;
644+ }
624645 }
625646
626647 submissions . forEach ( ( sub , submissionIndex ) => {
627648 const ans = sub . answers [ question . index ] ;
628649 if ( ! ans ) { return ; }
629650
630651 const userId = sub . user ?. _id || sub . user ?. name || sub . _id || `submission_${ submissionIndex } ` ;
631- const selections = question . type === 'selectMultiple' ? ans . value ?? [ ] : ans . value ? [ ans . value ] : [ ] ;
632- selections . forEach ( selection => {
633- if ( selection . isOther || selection . id === 'other' ) {
634- counts [ 'Other' ] ?. add ( userId ) ;
635- } else {
636- const txt = selection . text ?? selection ;
637- counts [ txt ] ?. add ( userId ) ;
652+ if ( question . type === 'ratingScale' ) {
653+ if ( ans . value ) {
654+ const value = ans . value . toString ( ) ;
655+ if ( counts [ value ] ) {
656+ counts [ value ] . add ( userId ) ;
657+ }
638658 }
639- } ) ;
659+ } else {
660+ const selections = question . type === 'selectMultiple' ? ans . value ?? [ ] : ans . value ? [ ans . value ] : [ ] ;
661+ selections . forEach ( selection => {
662+ if ( selection . isOther || selection . id === 'other' ) {
663+ counts [ 'Other' ] ?. add ( userId ) ;
664+ } else {
665+ const txt = selection . text ?? selection ;
666+ counts [ txt ] ?. add ( userId ) ;
667+ }
668+ } ) ;
669+ }
640670 } ) ;
641671
642- const labels = Object . keys ( counts ) ;
672+ const labels = question . type === 'ratingScale' ? Array . from ( { length : 9 } , ( _ , i ) => ( i + 1 ) . toString ( ) ) : Object . keys ( counts ) ;
643673 const userCounts = labels . map ( l => counts [ l ] . size ) ;
644674 const totalSelections = userCounts . reduce ( ( sum , count ) => sum + count , 0 ) ;
645675 let data : number [ ] ;
@@ -664,7 +694,8 @@ export class SubmissionsService {
664694 userCounts,
665695 totalUsers,
666696 totalSelections,
667- chartType : question . type === 'selectMultiple' ? ( mode === 'percent' ? 'bar' : 'pie' ) : 'pie'
697+ chartType : question . type === 'ratingScale' ? 'bar' : ( question . type === 'selectMultiple' ? ( mode === 'percent' ? 'bar' : 'pie' ) : 'pie' ) ,
698+ isRatingScale : question . type === 'ratingScale'
668699 } ;
669700 }
670701
@@ -686,6 +717,9 @@ export class SubmissionsService {
686717 case 'selectMultiple' :
687718 result = answer . value . map ( item => item . text ) . join ( ', ' ) ;
688719 break ;
720+ case 'ratingScale' :
721+ result = `Rating: ${ answer . value } (on 1-9 scale)` ;
722+ break ;
689723 default :
690724 result = answer . value ;
691725 }
0 commit comments