Grafoscopio/src/Dataviz/PublishedMedInfo.class.st

635 lines
21 KiB
Smalltalk

"
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
]