" Please comment me using the following template inspired by Class Responsibility Collaborator (CRC) design: For the Class part: State a one line summary. For example, ""I represent a paragraph of text"". I represent the published medicine information from several sources for a particular medicine. My main responsabilities are: - Visualize published medicine information to help in comparing several agencies. - I know how much information is published for a particular medicine. My main collaborator is: - MedDataItem: I store several of them. " Class { #name : #PublishedMedInfo, #superclass : #Object, #instVars : [ 'medName', 'medDataItems', 'medDataKeys', 'medDataMatrix', 'adminDataSize', 'arcWidth', 'columnLabels' ], #category : #'Dataviz-MedicineInfo' } { #category : #utility } PublishedMedInfo >> addLabelsFrom: anArray surrounding: aView withRadio: aDistance [ "I put a list of labels on aView which around a circle with a given radio" | angle | 1 to: anArray size do: [ :index | |label| angle := (index * 360/ anArray size) degreesToRadians. label := RTLabel new text: (anArray at: index) asString; height: aDistance / 30. aView add: (label element translateBy: (Point r: (aDistance)* 1.06 theta: angle - 0.075)) ]. ^ aView. ] { #category : #utility } PublishedMedInfo >> addLegendTo: aView titled: aString withData: anArray withColors: aColorPalette [ "Adds a legend with a given title to a given view" | legend | legend := RTLegendBuilder new. legend addText: aString. aView add: legend. ] { #category : #utility } PublishedMedInfo >> addLineSeparatorsTo: aView withData: data columnsDistance: aDistance centerSized: internalRadio ringSized: ringSize [ | e1 e2 ang | 1 to: data children size do: [ :i | e1 := (RTBox new size: 1) element. ang := (i * 360 / data children size) degreesToRadians. e1 translateTo: (internalRadio * ang cos)@(internalRadio * ang sin). e2 := (RTBox new size: 1) element. e2 translateTo: ((internalRadio + ringSize) * ang cos )@ ((internalRadio + ringSize)* ang sin). aView add: e1; add: e2; add: (RTLine new width: aDistance - 4 ; color: Color veryLightGray; edgeFrom: e1 to: e2) ] ] { #category : #utility } PublishedMedInfo >> addRotatedLabelsFrom: anArray surrounding: aView withRadio: aDistance withAngularCorrection: aValue [ "I put a list of rotated labels on aView which around a circle with a given radio" | angle | 1 to: anArray size do: [ :index | |label| angle := ((index * 360/ anArray size) - aValue) negated degreesToRadians. label := (RTRotatedLabel new text: (anArray at: index) asString; height: aDistance / 22) element. label translateBy: (Point r: label trachelShape notRotatedWidth/2+(aDistance)*1.06 theta: angle). aView add: label. angle := angle radiansToDegrees. angle := angle + ((angle between: -270 and: -90) ifTrue: [ 180 ] ifFalse: [ 0 ]). label trachelShape angleInDegree: angle. ]. ^ aView. ] { #category : #accessing } PublishedMedInfo >> adminDataSize [ "I calculate the amount of administrative data in the matrix. By convention all administrative data starts with '*' in its name" ^ adminDataSize isNil ifTrue: [ (self medDataKeys select: [:key | (key at: 1) = $*]) size ] ] { #category : #accessing } PublishedMedInfo >> adminDataSize: anObject [ adminDataSize := anObject ] { #category : #utility } PublishedMedInfo >> arcWidth [ ^ arcWidth ] { #category : #accessing } PublishedMedInfo >> arcWidth: anObject [ arcWidth := anObject ] { #category : #utility } PublishedMedInfo >> colorForWithoutBiosimilar [ "Returns a customized color for a medicament which has not biosimilar in its published info" ^ Color r: 218/255 g: 223/255 b: 225/255 ] { #category : #utility } PublishedMedInfo >> colorPalette16 [ "Returns a custom color palette for administrative data. In the data matrix, this correspond to the rows which name starts by '*' (first two admin data, contry and variant are already mapped in the name of each arc and don't start with '*'" | baseColors newColorPalette index | baseColors := RTColorPalette qualitative colors: 12 scheme:'Set3'. index := 1. newColorPalette := Array new: 16. baseColors do: [ :color | newColorPalette at: index put: color. index := index + 1. ]. "Customizing some individual colors to get a more constrasted palette, useful to distinguis in large information collections" newColorPalette at: 3 put: (Color r: 71/255 g:30/255 b: 253/255); at: 8 put: (Color r: 134/255 g:43/255 b: 80/255); at: 9 put: (Color r: 156/255 g:143/255 b: 50/255); at: 11 put: (Color r: 164/255 g:44/255 b: 107/255); at: 13 put: (Color r:0.75 g:0.22 b:0.17); at: 14 put: (Color r:0.42 g:0.48 b:0.54); at: 15 put: (Color r:0.83 g:0.33 b:0); at: 16 put: (Color r: 211/255 g:26/255 b: 239/255). ^ newColorPalette. ] { #category : #utility } PublishedMedInfo >> colorPalette26 [ "Returns a custom color palette for prescrition and use data. In the data matrix, this correspond to the rows which name doesn't start by '*' (first two admin data, contry and variant are already mapped in the name of each arc and doesn't start with '*'" | baseColors newColorPalette index | baseColors := RTColorPalette qualitative colors: 20 scheme:'FlatUI1'. index := 1. newColorPalette := Array new: 26. baseColors do: [ :color | newColorPalette at: index put: color. index := index + 1. ]. newColorPalette at: 2 put: (Color r:0.65 g:0.22 b:0.23); at: 4 put: (Color r:1 g:0.72 b:0.1); at: 6 put: (Color r:255/255 g:230/255 b:141/255); at: 8 put: (Color r:178/255 g:24/255 b:17/255); at: 14 put: (Color r:255/255 g:214/255 b:208/255); at: 18 put: (Color r:218/255 g:0/255 b:132/255); at: 21 put: (Color r:223/255 g:93/255 b:255/255); at: 22 put: (Color r:0.29 g:0.47 b:0.75); at: 23 put: (Color r:0.64 g:0.87 b:0.82); at: 24 put: (Color r:131/255 g:178/255 b:10/255); at: 25 put: (Color r:0.91 g:0.83 b:038); at: 26 put: (Color r:0.42 g:0.48 b:054). ^ newColorPalette. ] { #category : #accessing } PublishedMedInfo >> columnLabels [ ^ columnLabels ] { #category : #accessing } PublishedMedInfo >> columnLabels: anObject [ columnLabels := anObject ] { #category : #'data visualization' } PublishedMedInfo >> exploreDataForCountriesFromRow: firstRowIndex upTo: lastRowIndex coloredWith: aColorPalette centerTitled: aString arcWidth: aNumber [ "I show a subset of the matrix rows using a sunburst visualization variation" self arcWidth: aNumber. ^ self exploreMatrix: (self medDataMatrix copyFrom: firstRowIndex to: lastRowIndex) by: 'country' coloredWith: aColorPalette centerSized: 200 separationSized: 5 slicedFrom: firstRowIndex centerTitled: aString ] { #category : #'data visualization' } PublishedMedInfo >> exploreMatrix: aMatrix by: type coloredWith: aColorPalette centerSized: centerSize separationSized: separationSize slicedFrom: anInteger centerTitled: aString [ "I create a sunburst diagram for matrix alike information using aPalette for each of the rings on the sunburst. Information of the popup is taken according to the type of exploration that is being done in the matrix, which can done by the country which is publishing the info or by properties that are bein published. This code has a lot of parameters and maybe needs serious refactoring" | b data externalCircle internalCircle medLabel index | data := self matrixToTreeReversed: aMatrix. b := RTSunburstBuilder new. b strategy centerWidth: centerSize; hasCenter: false; arcWidth: self arcWidth; radialSpacing: 50. internalCircle := RTEllipse new width: centerSize*2; height: centerSize*2; color: Color white trans; borderColor: Color veryLightGray; borderWidth: 1. externalCircle := RTEllipse new width: internalCircle element width + (2 * aMatrix size * (self arcWidth + 1)); height: internalCircle element width + (2 * aMatrix size * (self arcWidth + 1)); color: Color white trans; borderColor: Color veryLightGray; borderWidth: 1. b view add: externalCircle element. b view add: internalCircle element. medLabel := RTLabel new text: aString, String cr, self medName; color: Color black; height: (internalCircle element width)/20 asInteger. b view add: (medLabel element translateBy: (internalCircle element center)). b radialSpacing: 1. index := 1. b shape color: [ :node | node header asString size > 1 ifTrue: [ Color black ] ifFalse: [ node header asInteger isZero ifTrue: [ Color white ] ifFalse: [ node header asInteger = 2 ifTrue: [ self colorForWithoutBiosimilar ] ifFalse: [ aColorPalette at: node level ] ] ] ]. b interaction noInteractions. type asLowercase = 'country' ifTrue: [ b interaction addInteraction: (RTPopup text: [:d | (self medDataKeys at: ((aMatrix size) - d level + anInteger + 1)), '-> ', d header]). ]. type asLowercase = 'property' ifTrue: [ b interactions add: (RTPopup text: [:d | self labelsForCountries at: (d level - 1)]). ]. b explore: data using: #children. b strategy hasCenter: false. b build. b view @ RTZoomableBoxView. self addLineSeparatorsTo: b view withData: data columnsDistance: separationSize centerSized: (internalCircle element width / 2) ringSized: ((externalCircle element width/2) - (internalCircle element width/2)). ^ {'theView' -> b view . 'externalRadio' -> (externalCircle element width/2)} asDictionary. ] { #category : #utility } PublishedMedInfo >> labelsForAdminProperties [ "I got the information about the administrative properties that are being published by all countries public health agencies. This information is a subset of medDataKeys" ^ self medDataKeys copyFrom: 3 to: self adminDataSize + 2. ] { #category : #utility } PublishedMedInfo >> labelsForCountries [ "I got the information about the country who is publishing the medicine info. This information is stored in the first row of the medDataMatrix. Countries use the ISO 3166-1 alpha 3 standard." | labels allCountries | allCountries := (self medDataMatrix at: 1). labels := OrderedCollection new. 1 to: allCountries size by: 2 do: [:index | labels add: (allCountries at: index). ]. ^ labels reversed. ] { #category : #utility } PublishedMedInfo >> labelsForCountriesAndVariants [ "I got the information about the country and variant of medicine info that is being published by a country public health agency. This information is stored in the first two rows of the medDataMatrix. Countries use the ISO 3166-1 alpha 3 standard and for the medicine variant we use complete words in the CVS but select only the first letter for visualization purposes, so the first letter is supposed to be different on each variant." | labels countries variants | countries := self medDataMatrix at: 1. variants := self medDataMatrix at: 2. labels := Array new: countries size. 1 to: countries size do: [:index | labels at: index put: (countries at: (countries size + 1 - index)), '-', (((variants at: (variants size + 1 - index)) copyFrom: 1 to: 1) asLowercase) ]. ^ labels ] { #category : #utility } PublishedMedInfo >> labelsForPUProperties [ "I got the information about the properties that are being published by all countries public health agencies. This information is a subset of medDataKeys" ^ self medDataKeys copyFrom: 3 + self adminDataSize to: self medDataKeys size. ] { #category : #'data import' } PublishedMedInfo >> loadDataFromCSV: aFile usingDelimiter: aCharacter [ "I import data from aFile storaged in Comma Separated Values (CSV) format. Is supposed that: - First row contains medicine name information. - First column contains the attributes that are being evaluated - Other columns contain the result of such evaluation as numerical values" | table | table := RTTabTable new input: aFile contents usingDelimiter: aCharacter. self medName: ((table valuesOfColumn: 2) at: 1). table removeFirstRow. self medDataKeys: table firstColumn. table removeColumn: 1. self medDataMatrix: table values. ] { #category : #utility } PublishedMedInfo >> matrixDataForAdmin [ "I extract all the administrative information from the medDataMatrix as a transposed matrix, so it can be used as an input for the matrixSunburst visualizations. By convention, this data is stored after the admin data for each drug has ended" | submatrixForAdmin numberOfColums transposedMatrix transposedRow | submatrixForAdmin := self medDataMatrix copyFrom: 3 to: (self adminDataSize + 2). numberOfColums := (submatrixForAdmin at: 1) size. transposedMatrix := OrderedCollection new. 1 to: (numberOfColums) by: 2 do: [ :columIndex | transposedRow := Array new: submatrixForAdmin size. 1 to: submatrixForAdmin size do: [:index | transposedRow at: index put: ((submatrixForAdmin at: index) at: columIndex) ]. transposedMatrix add: transposedRow ]. ^ transposedMatrix asArray ] { #category : #utility } PublishedMedInfo >> matrixDataForPU [ "I extract all the prescription and use data from the medDataMatrix as a transposed matrix, so it can be used as an input for the matrixSunburst visualizations. By convention, this data is stored after the admin data for each drug has ended" | submatrixForPU numberOfColums transposedMatrix transposedRow | submatrixForPU := self medDataMatrix copyFrom: 3 + self adminDataSize to: self medDataMatrix size. numberOfColums := (submatrixForPU at: 1) size. transposedMatrix := OrderedCollection new. 1 to: (numberOfColums) by: 2 do: [ :columIndex | transposedRow := Array new: submatrixForPU size. 1 to: submatrixForPU size do: [:index | transposedRow at: index put: ((submatrixForPU at: index) at: columIndex) ]. transposedMatrix add: transposedRow ]. ^ transposedMatrix asArray ] { #category : #'data visualization' } PublishedMedInfo >> matrixSunburstForAdminDataByCountry [ "Shows a matrix sunburst for the drug administrative data in several public drug agencies, using the properties of the medicine as tows and the countries that release the info (or not) as columns. By convention, this data starts at the 3rd row and ends in 2 + self adminDataSize." | temp view radio | temp := self exploreDataForCountriesFromRow: 3 upTo: 1 + self adminDataSize coloredWith: self colorPalette16 centerTitled: '' arcWidth: 15. view := temp at: 'theView'. radio := temp at: 'externalRadio'. ^ self addLabelsFrom: self labelsForCountriesAndVariants surrounding: view withRadio: radio*1.05. ] { #category : #'data visualization' } PublishedMedInfo >> matrixSunburstForAdminDataByProperty [ "Shows a matrix sunburst for the drug administrative data in several public drug agencies, using the properties of the medicine as columns and the countries as rows." | temp view radio | temp := self exploreMatrix: self matrixDataForAdmin by: 'property' coloredWith: self colorPalette16 centerSized: 200 separationSized: 6 slicedFrom: nil centerTitled: ''. view := temp at: 'theView'. radio := temp at: 'externalRadio'. ^ self addRotatedLabelsFrom: self labelsForAdminProperties surrounding: view withRadio: radio * 1.05 withAngularCorrection: 14.5 ] { #category : #'data visualization' } PublishedMedInfo >> matrixSunburstForAdminDataWithColorConvention [ "Shows a matrix sunburst for the drug administrative data in several public drug agencies" | composer sunburst | composer := RTComposer new. sunburst := self matrixSunburstForAdminDataByCountry. composer group: #sunburst. composer view. ] { #category : #'data visualization' } PublishedMedInfo >> matrixSunburstForPUDataByCountry [ "Shows a matrix sunburst for the drug prescripton and use data in several public drug agencies. For convention, this data stats at the row 3 + self adminDataSize and ends at the last one of the matrix" | temp view radio | temp:= self exploreDataForCountriesFromRow: 3 + self adminDataSize upTo: self medDataKeys size coloredWith: self colorPalette26 centerTitled: '' arcWidth: 15. view := temp at: 'theView'. radio := temp at: 'externalRadio'. ^ self addLabelsFrom: self labelsForCountriesAndVariants surrounding: view withRadio: radio*1.05. ] { #category : #'data visualization' } PublishedMedInfo >> matrixSunburstForPUDataByProperty [ "Shows a matrix sunburst for the drug administrative data in several public drug agencies, using the properties of the medicine as columns and the countries as rows." | temp view radio | temp := self exploreMatrix: self matrixDataForPU by: 'property' coloredWith: self colorPalette16 centerSized: 200 separationSized: 6 slicedFrom: nil centerTitled: ''. view := temp at: 'theView'. radio := temp at: 'externalRadio'. ^ self addRotatedLabelsFrom: self labelsForPUProperties surrounding: view withRadio: radio * 1.05 withAngularCorrection: 9 ] { #category : #utility } PublishedMedInfo >> matrixToTree: aMatrix [ "Converts aMatrix to a tree so it can be visualized using a sunburst" | currentNode tree column | tree := GrafoscopioNode new level: 1. (aMatrix at: 1) do: [ :label | tree addNode: (GrafoscopioNode new header: label) ]. column := 1. tree children do: [ :child | currentNode := child. 2 to: (aMatrix size) do: [:row | currentNode addNode: (GrafoscopioNode new header: ((aMatrix at: row) at: column)). currentNode := currentNode children at: 1. ]. column := column + 1. ]. ^ tree. ] { #category : #utility } PublishedMedInfo >> matrixToTreeReversed: aMatrix [ "Converts aMatrix to a tree so it can be visualized using a sunburst. Root of the tree is near to the last row, so the most exterior rings correspond to first rows, and inner most ones correspond to the last rows" | currentNode tree column | tree := GrafoscopioNode new level: 1. (aMatrix at: aMatrix size) do: [ :label | tree addNode: (GrafoscopioNode new header: label) ]. column := 1. tree children do: [ :child | currentNode := child. (aMatrix size -1 ) to: 1 by: -1 do: [:row | currentNode addNode: (GrafoscopioNode new header: ((aMatrix at: row) at: column)). currentNode := currentNode children at: 1. ]. column := column + 1. ]. ^ tree. ] { #category : #accessing } PublishedMedInfo >> medDataItems [ ^ medDataItems ] { #category : #accessing } PublishedMedInfo >> medDataItems: anObject [ medDataItems := anObject ] { #category : #accessing } PublishedMedInfo >> medDataKeys [ ^ medDataKeys ] { #category : #accessing } PublishedMedInfo >> medDataKeys: anObject [ medDataKeys := anObject ] { #category : #accessing } PublishedMedInfo >> medDataMatrix [ ^ medDataMatrix ] { #category : #accessing } PublishedMedInfo >> medDataMatrix: anObject [ medDataMatrix := anObject ] { #category : #accessing } PublishedMedInfo >> medName [ ^ medName ] { #category : #accessing } PublishedMedInfo >> medName: anObject [ medName := anObject ] { #category : #'data visualization' } PublishedMedInfo >> showAdminDataColorConventions [ "I create a legend showing the color palette and properties used in the matrix sunburst for administrative drug data" | legendBox adminDataKeys | legendBox := RTLegendBuilder new. legendBox addText: 'Convenciones' asUppercase. adminDataKeys := self medDataKeys copyFrom: 3 to: self adminDataSize + 1. 1 to: adminDataKeys size do: [ :i | legendBox addColor: (self colorPalette16 at: (self colorPalette16 size + 1 - i)) text: ((adminDataKeys at: i) copyReplaceAll: '* ' with: ''). ]. legendBox addColor: self colorForWithoutBiosimilar text: 'Sin biosimilar aprobado'. legendBox addColor: Color white text: 'Sin informacion publicada'. legendBox build. ^ legendBox view ] { #category : #'data visualization' } PublishedMedInfo >> showCountryColorConventions [ "I create a legend showing the color palette and countries used in the matrix sunburst for prescription drug data" | legendBox | legendBox := RTLegendBuilder new. legendBox addText: 'Convenciones' asUppercase. self labelsForCountries size to: 1 by: -1 do: [ :i | legendBox addColor: (self colorPalette16 at: i + 1) text: (self labelsForCountries at: i). ]. legendBox addColor: self colorForWithoutBiosimilar text: 'Sin biosimilar aprobado'. legendBox addColor: Color white text: 'Sin informacion publicada'. legendBox build. ^ legendBox view ] { #category : #'data visualization' } PublishedMedInfo >> showPUDataColorConventions [ "I create a legend showing the color palette and properties used in the matrix sunburst for prescription drug data. By convention this data starst where admin data ends and two first rows are 'special' admin data: name and variant of medicament which are already mapped on" | legendBox | legendBox := RTLegendBuilder new. legendBox addText: 'Convenciones' asUppercase. 1 to: (self medDataKeys size) - (self adminDataSize + 2) do: [ :i | legendBox addColor: (self colorPalette26 at: (self colorPalette26 size + 1 - i)) text: ((self medDataKeys at: self adminDataSize + 2 + i) copyReplaceAll: '* ' with: ''). ]. legendBox addColor: self colorForWithoutBiosimilar text: 'Sin biosimilar aprobado'. legendBox addColor: Color white text: 'Sin informacion publicada'. legendBox build. ^ legendBox view ]