From c87cb355800d6f405b702fe52e71616c35893484 Mon Sep 17 00:00:00 2001 From: Yann Le Goff Date: Thu, 17 Oct 2024 14:32:08 +0200 Subject: [PATCH 1/2] Rotate the kiviat polygon and fix the bottom label position --- src/Roassal-Chart/Collection.extension.st | 62 +- src/Roassal-Chart/RSAbstractBandPlot.class.st | 376 ++-- .../RSAbstractBandPlotShape.class.st | 266 +-- src/Roassal-Chart/RSAbstractBarPlot.class.st | 270 +-- src/Roassal-Chart/RSAbstractBinning.class.st | 54 +- src/Roassal-Chart/RSAbstractChart.class.st | 1270 ++++++------- .../RSAbstractChartDecoration.class.st | 446 ++--- .../RSAbstractChartPopupBuilder.class.st | 56 +- .../RSAbstractLabelDecoration.class.st | 248 +-- .../RSAbstractMarkerDecoration.class.st | 246 +-- src/Roassal-Chart/RSAbstractPlot.class.st | 830 ++++----- src/Roassal-Chart/RSAbstractTick.class.st | 698 +++---- src/Roassal-Chart/RSAutoLocator.class.st | 42 +- src/Roassal-Chart/RSBarPlot.class.st | 350 ++-- src/Roassal-Chart/RSBarPlotNew.class.st | 220 +-- src/Roassal-Chart/RSBarPlotShape.class.st | 156 +- src/Roassal-Chart/RSBinSizeBinning.class.st | 78 +- .../RSBlockChartPopupBuilder.class.st | 64 +- src/Roassal-Chart/RSBoxPlot.class.st | 1616 ++++++++--------- src/Roassal-Chart/RSBoxPlotShape.class.st | 882 ++++----- src/Roassal-Chart/RSChart.class.st | 30 +- src/Roassal-Chart/RSChartExtents.class.st | 164 +- .../RSChartPopupDecoration.class.st | 186 +- .../RSChartSpineDecoration.class.st | 122 +- src/Roassal-Chart/RSChartStyler.class.st | 172 +- .../RSChartTitleDecoration.class.st | 92 +- src/Roassal-Chart/RSClusterChart.class.st | 330 ++-- src/Roassal-Chart/RSCompositeChart.class.st | 316 ++-- src/Roassal-Chart/RSDefaultBinning.class.st | 78 +- src/Roassal-Chart/RSDensityPlot.class.st | 510 +++--- src/Roassal-Chart/RSDoubleBarPlot.class.st | 272 +-- src/Roassal-Chart/RSFixedBinning.class.st | 70 +- src/Roassal-Chart/RSFixedLocator.class.st | 74 +- src/Roassal-Chart/RSHistogramPlot.class.st | 486 ++--- .../RSHorizontalBarPlot.class.st | 154 +- src/Roassal-Chart/RSHorizontalTick.class.st | 150 +- .../RSHorizontalTopTick.class.st | 166 +- src/Roassal-Chart/RSInvertedCDF.class.st | 60 +- src/Roassal-Chart/RSKernelDensity.class.st | 500 ++--- src/Roassal-Chart/RSKernelFunction.class.st | 34 +- .../RSKernelGaussianFunction.class.st | 24 +- src/Roassal-Chart/RSKiviat.class.st | 836 ++++----- .../RSLineChartPopupBuilder.class.st | 104 +- src/Roassal-Chart/RSLinePlot.class.st | 146 +- .../RSLineSpineDecoration.class.st | 100 +- src/Roassal-Chart/RSLinearLocator.class.st | 42 +- src/Roassal-Chart/RSLogLocator.class.st | 182 +- src/Roassal-Chart/RSPopupDecoration.class.st | 120 +- src/Roassal-Chart/RSQuantile.class.st | 56 +- src/Roassal-Chart/RSRiceBinning.class.st | 30 +- src/Roassal-Chart/RSScatterPlot.class.st | 196 +- .../RSSimpleChartPopupBuilder.class.st | 210 +-- .../RSSimpleMarkerPopupBuilder.class.st | 44 +- .../RSStatisticalMeasures.class.st | 332 ++-- src/Roassal-Chart/RSSturgesBinning.class.st | 30 +- .../RSTickConfiguration.class.st | 326 ++-- src/Roassal-Chart/RSTickLocator.class.st | 32 +- src/Roassal-Chart/RSTimeLinePlot.class.st | 230 +-- .../RSVerticalRightTick.class.st | 168 +- src/Roassal-Chart/RSVerticalTick.class.st | 128 +- src/Roassal-Chart/RSViolinPlot.class.st | 1220 ++++++------- src/Roassal-Chart/RSViolinPlotShape.class.st | 438 ++--- src/Roassal-Chart/RSXLabelDecoration.class.st | 98 +- .../RSXMarkerDecoration.class.st | 126 +- src/Roassal-Chart/RSYLabelDecoration.class.st | 104 +- .../RSYMarkerDecoration.class.st | 108 +- .../RSZoomTickDecoration.class.st | 394 ++-- .../SequenceableCollection.extension.st | 16 +- src/Roassal-Chart/package.st | 2 +- 69 files changed, 9027 insertions(+), 9011 deletions(-) diff --git a/src/Roassal-Chart/Collection.extension.st b/src/Roassal-Chart/Collection.extension.st index 7bd534fc..8b7d900b 100644 --- a/src/Roassal-Chart/Collection.extension.st +++ b/src/Roassal-Chart/Collection.extension.st @@ -1,31 +1,31 @@ -Extension { #name : 'Collection' } - -{ #category : '*Roassal-Chart' } -Collection >> interQuartileRange [ - | quartiles q1 q3 | - quartiles := self quartiles. - q1 := quartiles first. - q3 := quartiles third. - ^ q3 - q1 -] - -{ #category : '*Roassal-Chart' } -Collection >> quartiles [ - | q1 q2 q3 quantile | - quantile := RSInvertedCDF data: self. - q1 := quantile compute: 0.25. - q2 := quantile compute: 0.50. - q3 := quantile compute: 0.75. - ^ { q1. q2. q3 } -] - -{ #category : '*Roassal-Chart' } -Collection >> standardDeviation [ - "It follows the implementation of numpy using the size N (instead of N - 1)" - | standardDeviation sum mean | - sum := 0. - mean := self average. - self do: [ :observationX | sum := sum + ((observationX - mean) squared) ]. - standardDeviation := (sum/self size) sqrt. - ^ standardDeviation -] +Extension { #name : 'Collection' } + +{ #category : '*Roassal-Chart' } +Collection >> interQuartileRange [ + | quartiles q1 q3 | + quartiles := self quartiles. + q1 := quartiles first. + q3 := quartiles third. + ^ q3 - q1 +] + +{ #category : '*Roassal-Chart' } +Collection >> quartiles [ + | q1 q2 q3 quantile | + quantile := RSInvertedCDF data: self. + q1 := quantile compute: 0.25. + q2 := quantile compute: 0.50. + q3 := quantile compute: 0.75. + ^ { q1. q2. q3 } +] + +{ #category : '*Roassal-Chart' } +Collection >> standardDeviation [ + "It follows the implementation of numpy using the size N (instead of N - 1)" + | standardDeviation sum mean | + sum := 0. + mean := self average. + self do: [ :observationX | sum := sum + ((observationX - mean) squared) ]. + standardDeviation := (sum/self size) sqrt. + ^ standardDeviation +] diff --git a/src/Roassal-Chart/RSAbstractBandPlot.class.st b/src/Roassal-Chart/RSAbstractBandPlot.class.st index e3f83130..87996253 100644 --- a/src/Roassal-Chart/RSAbstractBandPlot.class.st +++ b/src/Roassal-Chart/RSAbstractBandPlot.class.st @@ -1,188 +1,188 @@ -" -`RSAbstractBandPlot` is an abstract kind of plot that will show some graphics inside of bands. In this way a band plot have two main properties: the bands width and the bands offset. - -**Responsibility:** -- This class abstracts the common behavior and the concept of the bands. - -**Collaborators:** -- **`NSOrdinalScale`:** This class allows rendering each graphic in one band by assigning the `bandWidth` and the `bandOffset`. In this way, each graphic (`RSBoxShape`, `RSViolinPlotShape`) knows which part of the canvas it needs to fill. - -**Public API and Key Messages** - -**Instance Variables:** - -**Example:** - -" -Class { - #name : 'RSAbstractBandPlot', - #superclass : 'RSAbstractPlot', - #instVars : [ - 'bandScale', - 'dataScale', - 'bandWidth', - 'offset', - 'horizontal', - 'bandPlotShapes', - 'positions' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'rendering' } -RSAbstractBandPlot >> assignXAndYScales: aChart [ - bandScale class = NSOrdinalScale ifFalse: [ - self computeScales: aChart. ]. - horizontal ifFalse: [ - xScale := bandScale. - dataScale := self yScale. ] - ifTrue: [ - yScale := bandScale. - dataScale := self xScale. ]. - aChart yScale: yScale. - aChart xScale: xScale -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> bandScale: aNSScale [ - bandScale := aNSScale -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> bandsOffset: aNumberInRange [ - offset := aNumberInRange -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> bandsWidth [ - ^ bandWidth ifNil: [ bandWidth := self defaultBandsWidth ] -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> bandsWidth: aNumber [ - bandWidth := aNumber -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> beforeRenderingIn: aChart [ - self computeXAndYValues. - super beforeRenderingIn: aChart. - self computeTicks. - self assignXAndYScales: aChart -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> computeBandsOffset [ - | bandValues | - bandValues := horizontal ifFalse: [ xValues ] ifTrue: [ yValues ]. - bandPlotShapes doWithIndex: [ :graphic :idx | - graphic bandOffset: (bandScale scale: (bandValues at: idx)) + offset - ] -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> computeBandsWidth [ - bandPlotShapes do: [ :graphic | graphic bandWidth: self bandsWidth ] -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> computeScales: aChart [ - bandScale := NSScale ordinal. - horizontal ifFalse: [ - bandScale domain: xValues copy asOrderedCollection sort. - bandScale rangeBands: { 0. aChart extent x. }. ] - ifTrue: [ - bandScale domain: yValues copy asOrderedCollection sort. - "important to use the corresponding range of the spine" - bandScale rangeBands: yScale range. ] -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> computeTicks [ - | horizontalTick verticalTick | - horizontal - ifFalse: [ - horizontalTick := self horizontalTick. - horizontalTick ifNotNil: [ - horizontalTick isTicksDataNil ifTrue: [ self xTicks: xValues labels: xValues ] ]. - ] - ifTrue: [ - verticalTick := self verticalTick. - verticalTick ifNotNil: [ - verticalTick isTicksDataNil ifTrue: [ self yTicks: yValues labels: yValues ] ]. - ] -] - -{ #category : 'private' } -RSAbstractBandPlot >> computeXAndYValues [ - | dataValues bandPositions maxDataRelatedValue minDataRelatedValue | - maxDataRelatedValue := (bandPlotShapes collect: [ :bandPlotShape | bandPlotShape maxDataValue ]) max. - minDataRelatedValue := (bandPlotShapes collect: [ :bandPlotShape | bandPlotShape minDataValue ]) min. - dataValues := {minDataRelatedValue. maxDataRelatedValue.}. - bandPositions := self positions. - horizontal - ifTrue: [ - xValues := dataValues. - yValues := bandPositions ] - ifFalse: [ - xValues := bandPositions. - yValues := dataValues ] -] - -{ #category : 'accessing' } -RSAbstractBandPlot >> createdShapes [ - ^ self subclassResponsibility -] - -{ #category : 'accessing' } -RSAbstractBandPlot >> defaultBandsWidth [ - ^ bandScale rangeBand * 0.5 -] - -{ #category : 'accessing' } -RSAbstractBandPlot >> defaultOffset [ - ^ bandScale scale: 0 -] - -{ #category : 'public' } -RSAbstractBandPlot >> horizontal [ - bandPlotShapes do: [ :bs | bs horizontal ]. - horizontal := true -] - -{ #category : 'accessing' } -RSAbstractBandPlot >> isHorizontal [ - ^ horizontal -] - -{ #category : 'accessing - defaults' } -RSAbstractBandPlot >> numberOfBands [ - ^ bandPlotShapes size -] - -{ #category : 'accessing - defaults' } -RSAbstractBandPlot >> positions [ - positions := positions ifNil: [(1 to: self numberOfBands)]. - ^ positions -] - -{ #category : 'accessing - defaults' } -RSAbstractBandPlot >> positions: aCollection [ - self - assert: [ aCollection size = bandPlotShapes size ] - description: 'The positions collection must have the same size of bandPlotShapes'. - positions := aCollection -] - -{ #category : 'rendering' } -RSAbstractBandPlot >> showBands [ - bandPlotShapes do: [ :box | box showBand ] -] - -{ #category : 'public' } -RSAbstractBandPlot >> vertical [ - bandPlotShapes do: [ :bs | bs vertical ]. - horizontal := false -] +" +`RSAbstractBandPlot` is an abstract kind of plot that will show some graphics inside of bands. In this way a band plot have two main properties: the bands width and the bands offset. + +**Responsibility:** +- This class abstracts the common behavior and the concept of the bands. + +**Collaborators:** +- **`NSOrdinalScale`:** This class allows rendering each graphic in one band by assigning the `bandWidth` and the `bandOffset`. In this way, each graphic (`RSBoxShape`, `RSViolinPlotShape`) knows which part of the canvas it needs to fill. + +**Public API and Key Messages** + +**Instance Variables:** + +**Example:** + +" +Class { + #name : 'RSAbstractBandPlot', + #superclass : 'RSAbstractPlot', + #instVars : [ + 'bandScale', + 'dataScale', + 'bandWidth', + 'offset', + 'horizontal', + 'bandPlotShapes', + 'positions' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'rendering' } +RSAbstractBandPlot >> assignXAndYScales: aChart [ + bandScale class = NSOrdinalScale ifFalse: [ + self computeScales: aChart. ]. + horizontal ifFalse: [ + xScale := bandScale. + dataScale := self yScale. ] + ifTrue: [ + yScale := bandScale. + dataScale := self xScale. ]. + aChart yScale: yScale. + aChart xScale: xScale +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> bandScale: aNSScale [ + bandScale := aNSScale +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> bandsOffset: aNumberInRange [ + offset := aNumberInRange +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> bandsWidth [ + ^ bandWidth ifNil: [ bandWidth := self defaultBandsWidth ] +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> bandsWidth: aNumber [ + bandWidth := aNumber +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> beforeRenderingIn: aChart [ + self computeXAndYValues. + super beforeRenderingIn: aChart. + self computeTicks. + self assignXAndYScales: aChart +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> computeBandsOffset [ + | bandValues | + bandValues := horizontal ifFalse: [ xValues ] ifTrue: [ yValues ]. + bandPlotShapes doWithIndex: [ :graphic :idx | + graphic bandOffset: (bandScale scale: (bandValues at: idx)) + offset + ] +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> computeBandsWidth [ + bandPlotShapes do: [ :graphic | graphic bandWidth: self bandsWidth ] +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> computeScales: aChart [ + bandScale := NSScale ordinal. + horizontal ifFalse: [ + bandScale domain: xValues copy asOrderedCollection sort. + bandScale rangeBands: { 0. aChart extent x. }. ] + ifTrue: [ + bandScale domain: yValues copy asOrderedCollection sort. + "important to use the corresponding range of the spine" + bandScale rangeBands: yScale range. ] +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> computeTicks [ + | horizontalTick verticalTick | + horizontal + ifFalse: [ + horizontalTick := self horizontalTick. + horizontalTick ifNotNil: [ + horizontalTick isTicksDataNil ifTrue: [ self xTicks: xValues labels: xValues ] ]. + ] + ifTrue: [ + verticalTick := self verticalTick. + verticalTick ifNotNil: [ + verticalTick isTicksDataNil ifTrue: [ self yTicks: yValues labels: yValues ] ]. + ] +] + +{ #category : 'private' } +RSAbstractBandPlot >> computeXAndYValues [ + | dataValues bandPositions maxDataRelatedValue minDataRelatedValue | + maxDataRelatedValue := (bandPlotShapes collect: [ :bandPlotShape | bandPlotShape maxDataValue ]) max. + minDataRelatedValue := (bandPlotShapes collect: [ :bandPlotShape | bandPlotShape minDataValue ]) min. + dataValues := {minDataRelatedValue. maxDataRelatedValue.}. + bandPositions := self positions. + horizontal + ifTrue: [ + xValues := dataValues. + yValues := bandPositions ] + ifFalse: [ + xValues := bandPositions. + yValues := dataValues ] +] + +{ #category : 'accessing' } +RSAbstractBandPlot >> createdShapes [ + ^ self subclassResponsibility +] + +{ #category : 'accessing' } +RSAbstractBandPlot >> defaultBandsWidth [ + ^ bandScale rangeBand * 0.5 +] + +{ #category : 'accessing' } +RSAbstractBandPlot >> defaultOffset [ + ^ bandScale scale: 0 +] + +{ #category : 'public' } +RSAbstractBandPlot >> horizontal [ + bandPlotShapes do: [ :bs | bs horizontal ]. + horizontal := true +] + +{ #category : 'accessing' } +RSAbstractBandPlot >> isHorizontal [ + ^ horizontal +] + +{ #category : 'accessing - defaults' } +RSAbstractBandPlot >> numberOfBands [ + ^ bandPlotShapes size +] + +{ #category : 'accessing - defaults' } +RSAbstractBandPlot >> positions [ + positions := positions ifNil: [(1 to: self numberOfBands)]. + ^ positions +] + +{ #category : 'accessing - defaults' } +RSAbstractBandPlot >> positions: aCollection [ + self + assert: [ aCollection size = bandPlotShapes size ] + description: 'The positions collection must have the same size of bandPlotShapes'. + positions := aCollection +] + +{ #category : 'rendering' } +RSAbstractBandPlot >> showBands [ + bandPlotShapes do: [ :box | box showBand ] +] + +{ #category : 'public' } +RSAbstractBandPlot >> vertical [ + bandPlotShapes do: [ :bs | bs vertical ]. + horizontal := false +] diff --git a/src/Roassal-Chart/RSAbstractBandPlotShape.class.st b/src/Roassal-Chart/RSAbstractBandPlotShape.class.st index e0fa2f0b..24f7be23 100644 --- a/src/Roassal-Chart/RSAbstractBandPlotShape.class.st +++ b/src/Roassal-Chart/RSAbstractBandPlotShape.class.st @@ -1,133 +1,133 @@ -Class { - #name : 'RSAbstractBandPlotShape', - #superclass : 'RSComposite', - #instVars : [ - 'color', - 'bandOffset', - 'bandWidth', - 'dataScale', - 'bandScale', - 'horizontal', - 'shouldShowBand' - ], - #category : 'Roassal-Chart-Plots', - #package : 'Roassal-Chart', - #tag : 'Plots' -} - -{ #category : 'rendering' } -RSAbstractBandPlotShape >> addChildrenToComposite [ - | shapesToRender | - shapesToRender := self shapesToRender. - self addAll: shapesToRender. - self adjustToChildren. - ^ self -] - -{ #category : 'band' } -RSAbstractBandPlotShape >> bandOffset: aNumber [ - bandOffset := aNumber -] - -{ #category : 'scales' } -RSAbstractBandPlotShape >> bandScale [ - ^ bandScale -] - -{ #category : 'scales' } -RSAbstractBandPlotShape >> bandScale: aNSScale [ - bandScale := aNSScale -] - -{ #category : 'band' } -RSAbstractBandPlotShape >> bandWidth: aNumber [ - bandWidth := aNumber -] - -{ #category : 'accessing' } -RSAbstractBandPlotShape >> color [ - ^ color -] - -{ #category : 'accessing' } -RSAbstractBandPlotShape >> color: aColor [ - color := aColor -] - -{ #category : 'shapes' } -RSAbstractBandPlotShape >> computeBandRectangle [ - ^ RSPolygon new - points: { - (bandOffset-(bandWidth/2)) @ dataScale range first. - (bandOffset+(bandWidth/2)) @ dataScale range first. - (bandOffset+(bandWidth/2)) @ dataScale range last. - (bandOffset-(bandWidth/2)) @ dataScale range last - }; - color: Color blue translucent -] - -{ #category : 'shapes' } -RSAbstractBandPlotShape >> createShapesAndLines [ - ^ self subclassResponsibility -] - -{ #category : 'scales' } -RSAbstractBandPlotShape >> dataScale [ - ^ dataScale -] - -{ #category : 'scales' } -RSAbstractBandPlotShape >> dataScale: aNSScale [ - dataScale := aNSScale -] - -{ #category : 'defaults' } -RSAbstractBandPlotShape >> defaultShouldShowBand [ - ^ false -] - -{ #category : 'public' } -RSAbstractBandPlotShape >> horizontal [ - horizontal := true -] - -{ #category : 'rendering' } -RSAbstractBandPlotShape >> maxDataValue [ - ^ self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractBandPlotShape >> minDataValue [ - ^ self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractBandPlotShape >> renderIn: canvas [ - ^ self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractBandPlotShape >> shapesToRender [ - | shapesAndLines band | - shapesAndLines := self createShapesAndLines. - shouldShowBand ifTrue: [ - band := self computeBandRectangle. - shapesAndLines add: band ]. - horizontal ifTrue: [ shapesAndLines do: [ :s | s invert ] ]. - ^ shapesAndLines -] - -{ #category : 'band' } -RSAbstractBandPlotShape >> shouldShowBand: aBoolean [ - shouldShowBand := aBoolean -] - -{ #category : 'public' } -RSAbstractBandPlotShape >> showBand [ - self shouldShowBand: true -] - -{ #category : 'public' } -RSAbstractBandPlotShape >> vertical [ - horizontal := false -] +Class { + #name : 'RSAbstractBandPlotShape', + #superclass : 'RSComposite', + #instVars : [ + 'color', + 'bandOffset', + 'bandWidth', + 'dataScale', + 'bandScale', + 'horizontal', + 'shouldShowBand' + ], + #category : 'Roassal-Chart-Plots', + #package : 'Roassal-Chart', + #tag : 'Plots' +} + +{ #category : 'rendering' } +RSAbstractBandPlotShape >> addChildrenToComposite [ + | shapesToRender | + shapesToRender := self shapesToRender. + self addAll: shapesToRender. + self adjustToChildren. + ^ self +] + +{ #category : 'band' } +RSAbstractBandPlotShape >> bandOffset: aNumber [ + bandOffset := aNumber +] + +{ #category : 'scales' } +RSAbstractBandPlotShape >> bandScale [ + ^ bandScale +] + +{ #category : 'scales' } +RSAbstractBandPlotShape >> bandScale: aNSScale [ + bandScale := aNSScale +] + +{ #category : 'band' } +RSAbstractBandPlotShape >> bandWidth: aNumber [ + bandWidth := aNumber +] + +{ #category : 'accessing' } +RSAbstractBandPlotShape >> color [ + ^ color +] + +{ #category : 'accessing' } +RSAbstractBandPlotShape >> color: aColor [ + color := aColor +] + +{ #category : 'shapes' } +RSAbstractBandPlotShape >> computeBandRectangle [ + ^ RSPolygon new + points: { + (bandOffset-(bandWidth/2)) @ dataScale range first. + (bandOffset+(bandWidth/2)) @ dataScale range first. + (bandOffset+(bandWidth/2)) @ dataScale range last. + (bandOffset-(bandWidth/2)) @ dataScale range last + }; + color: Color blue translucent +] + +{ #category : 'shapes' } +RSAbstractBandPlotShape >> createShapesAndLines [ + ^ self subclassResponsibility +] + +{ #category : 'scales' } +RSAbstractBandPlotShape >> dataScale [ + ^ dataScale +] + +{ #category : 'scales' } +RSAbstractBandPlotShape >> dataScale: aNSScale [ + dataScale := aNSScale +] + +{ #category : 'defaults' } +RSAbstractBandPlotShape >> defaultShouldShowBand [ + ^ false +] + +{ #category : 'public' } +RSAbstractBandPlotShape >> horizontal [ + horizontal := true +] + +{ #category : 'rendering' } +RSAbstractBandPlotShape >> maxDataValue [ + ^ self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractBandPlotShape >> minDataValue [ + ^ self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractBandPlotShape >> renderIn: canvas [ + ^ self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractBandPlotShape >> shapesToRender [ + | shapesAndLines band | + shapesAndLines := self createShapesAndLines. + shouldShowBand ifTrue: [ + band := self computeBandRectangle. + shapesAndLines add: band ]. + horizontal ifTrue: [ shapesAndLines do: [ :s | s invert ] ]. + ^ shapesAndLines +] + +{ #category : 'band' } +RSAbstractBandPlotShape >> shouldShowBand: aBoolean [ + shouldShowBand := aBoolean +] + +{ #category : 'public' } +RSAbstractBandPlotShape >> showBand [ + self shouldShowBand: true +] + +{ #category : 'public' } +RSAbstractBandPlotShape >> vertical [ + horizontal := false +] diff --git a/src/Roassal-Chart/RSAbstractBarPlot.class.st b/src/Roassal-Chart/RSAbstractBarPlot.class.st index 19b81167..f4dd8d0a 100644 --- a/src/Roassal-Chart/RSAbstractBarPlot.class.st +++ b/src/Roassal-Chart/RSAbstractBarPlot.class.st @@ -1,135 +1,135 @@ -" - -`RSChart` is the abstract class that encompass bar plots. - -*Responsibility*: maintain and render bar plots. - -*Collaborators*: a plot closely interacts with decorations and can be added in a `RSCompositeChart`. - -*Variables*: -- barSize: the size of the bars -- bars: all the bars of the plot -- gapRatio: -- barOffset: shifts the bars left or right -" -Class { - #name : 'RSAbstractBarPlot', - #superclass : 'RSAbstractPlot', - #instVars : [ - 'barSize', - 'bars', - 'gapRatio', - 'barOffset' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'testing' } -RSAbstractBarPlot class >> isAbstract [ - - ^ self == RSAbstractBarPlot -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> barOffset [ - ^ barOffset -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> barOffset: aNumber [ - barOffset := aNumber -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> barScale [ - ^ self subclassResponsibility -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> barSize [ - "Return the width of each bar" - ^ barSize ifNil: [ self barScale rangeBand ] -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> barSize: aBarWidth [ - "Set the width of the bar" - barSize := aBarWidth -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> bars [ - ^ bars -] - -{ #category : 'hooks' } -RSAbstractBarPlot >> computeRectagleFor: aPoint index: index [ - ^ self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractBarPlot >> createBarFor: aPoint index: index [ - - ^ self shape copy - model: (self modelFor: aPoint); - color: self computeColor; - fromRectangle: (self computeRectagleFor: aPoint index: index); - yourself -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> createdShapes [ - ^ bars -] - -{ #category : 'accessing - defaults' } -RSAbstractBarPlot >> defaultShape [ - - ^ RSBox new noPaint -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> gapRatio [ - ^ gapRatio -] - -{ #category : 'accessing' } -RSAbstractBarPlot >> gapRatio: aNumber [ - "aNumber between 0 and 1" - gapRatio := aNumber -] - -{ #category : 'initialization' } -RSAbstractBarPlot >> initialize [ - super initialize. - self - gapRatio: 0.1; - barOffset: 0 -] - -{ #category : 'testing' } -RSAbstractBarPlot >> isBarPlot [ - ^ true -] - -{ #category : 'hooks' } -RSAbstractBarPlot >> modelFor: aPoint [ - ^ self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractBarPlot >> renderIn: canvas [ - | index | - super renderIn: canvas. - self checkAssertion. - index := 1. - bars := xValues collect: [ :xt | - | yt bar | - yt := yValues at: index. - bar := self createBarFor: xt@yt index: index. - index := index + 1. - bar ] as: RSGroup. - canvas addAll: bars -] +" + +`RSChart` is the abstract class that encompass bar plots. + +*Responsibility*: maintain and render bar plots. + +*Collaborators*: a plot closely interacts with decorations and can be added in a `RSCompositeChart`. + +*Variables*: +- barSize: the size of the bars +- bars: all the bars of the plot +- gapRatio: +- barOffset: shifts the bars left or right +" +Class { + #name : 'RSAbstractBarPlot', + #superclass : 'RSAbstractPlot', + #instVars : [ + 'barSize', + 'bars', + 'gapRatio', + 'barOffset' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'testing' } +RSAbstractBarPlot class >> isAbstract [ + + ^ self == RSAbstractBarPlot +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> barOffset [ + ^ barOffset +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> barOffset: aNumber [ + barOffset := aNumber +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> barScale [ + ^ self subclassResponsibility +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> barSize [ + "Return the width of each bar" + ^ barSize ifNil: [ self barScale rangeBand ] +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> barSize: aBarWidth [ + "Set the width of the bar" + barSize := aBarWidth +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> bars [ + ^ bars +] + +{ #category : 'hooks' } +RSAbstractBarPlot >> computeRectagleFor: aPoint index: index [ + ^ self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractBarPlot >> createBarFor: aPoint index: index [ + + ^ self shape copy + model: (self modelFor: aPoint); + color: self computeColor; + fromRectangle: (self computeRectagleFor: aPoint index: index); + yourself +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> createdShapes [ + ^ bars +] + +{ #category : 'accessing - defaults' } +RSAbstractBarPlot >> defaultShape [ + + ^ RSBox new noPaint +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> gapRatio [ + ^ gapRatio +] + +{ #category : 'accessing' } +RSAbstractBarPlot >> gapRatio: aNumber [ + "aNumber between 0 and 1" + gapRatio := aNumber +] + +{ #category : 'initialization' } +RSAbstractBarPlot >> initialize [ + super initialize. + self + gapRatio: 0.1; + barOffset: 0 +] + +{ #category : 'testing' } +RSAbstractBarPlot >> isBarPlot [ + ^ true +] + +{ #category : 'hooks' } +RSAbstractBarPlot >> modelFor: aPoint [ + ^ self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractBarPlot >> renderIn: canvas [ + | index | + super renderIn: canvas. + self checkAssertion. + index := 1. + bars := xValues collect: [ :xt | + | yt bar | + yt := yValues at: index. + bar := self createBarFor: xt@yt index: index. + index := index + 1. + bar ] as: RSGroup. + canvas addAll: bars +] diff --git a/src/Roassal-Chart/RSAbstractBinning.class.st b/src/Roassal-Chart/RSAbstractBinning.class.st index 994feb90..e897227d 100644 --- a/src/Roassal-Chart/RSAbstractBinning.class.st +++ b/src/Roassal-Chart/RSAbstractBinning.class.st @@ -1,27 +1,27 @@ -" -source from - -https://www.answerminer.com/blog/binning-guide-ideal-histogram -" -Class { - #name : 'RSAbstractBinning', - #superclass : 'RSObject', - #category : 'Roassal-Chart-Strategy', - #package : 'Roassal-Chart', - #tag : 'Strategy' -} - -{ #category : 'hooks' } -RSAbstractBinning >> computeNumberOfBinsFor: aCollection [ - " should return a number" - ^ self subclassResponsibility -] - -{ #category : 'hooks' } -RSAbstractBinning >> createBinsFor: aCollection [ - | size | - size := self computeNumberOfBinsFor: aCollection. - "an adjust" - size := size + 1. - ^ aCollection first to: aCollection last count: size -] +" +source from + +https://www.answerminer.com/blog/binning-guide-ideal-histogram +" +Class { + #name : 'RSAbstractBinning', + #superclass : 'RSObject', + #category : 'Roassal-Chart-Strategy', + #package : 'Roassal-Chart', + #tag : 'Strategy' +} + +{ #category : 'hooks' } +RSAbstractBinning >> computeNumberOfBinsFor: aCollection [ + " should return a number" + ^ self subclassResponsibility +] + +{ #category : 'hooks' } +RSAbstractBinning >> createBinsFor: aCollection [ + | size | + size := self computeNumberOfBinsFor: aCollection. + "an adjust" + size := size + 1. + ^ aCollection first to: aCollection last count: size +] diff --git a/src/Roassal-Chart/RSAbstractChart.class.st b/src/Roassal-Chart/RSAbstractChart.class.st index b0d79f1c..97b81fb3 100644 --- a/src/Roassal-Chart/RSAbstractChart.class.st +++ b/src/Roassal-Chart/RSAbstractChart.class.st @@ -1,635 +1,635 @@ -" - -`RSChart` is the abstract class that encompass plots and charts. - -*Responsibility*: maintain and render plots. - -*Collaborators*: a chart closely interacts with plots and decorations. - -*Variables*: -- xScale: the scale of the x-axis -- yScale: the scale of the y-axis -- extents: the size of the chart -- styler: contains information for the chart like its decorations color or text size -- colorPalette: the colors used to render plots -- decorations: the collection of all decorations (spine, ticks, labels, etc) -- spineDecoration: the spine decoration of the chart -- title: the tilte of the chart -- xlabel: the text used as label for the x-axis -- xlabelTop: the text used as label for the top x-axis -- ylabel: the text used as label for the y-axis -- ylabelRight: the text used as label for the right y-axis - -" -Class { - #name : 'RSAbstractChart', - #superclass : 'RSBuilder', - #instVars : [ - 'xScale', - 'yScale', - 'extents', - 'styler', - 'colorPalette', - 'decorations', - 'spineDecoration', - 'title', - 'xlabel', - 'xlabelTop', - 'ylabel', - 'ylabelRight' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'public - plots' } -RSAbstractChart class >> barHeights: aCollectionY [ - - ^ RSBarPlot new x: (1 to: aCollectionY size) y: aCollectionY -] - -{ #category : 'public - plots' } -RSAbstractChart class >> barWidths: aCollectionX [ - - ^ RSHorizontalBarPlot new - x: aCollectionX - y: (1 to: aCollectionX size) -] - -{ #category : 'testing' } -RSAbstractChart class >> isAbstract [ - - ^ self == RSAbstractChart -] - -{ #category : 'public - plots' } -RSAbstractChart class >> lineX: aCollectionX y: aCollectionY [ - - ^ RSLinePlot new x: aCollectionX y: aCollectionY -] - -{ #category : 'adding' } -RSAbstractChart >> addDecoration: aDecoration [ - "Add a decoration to the chart - -For example: -```Smalltalk -x := -3.14 to: 3.14 by: 0.1. -y := x sin. -c := RSChart new. -c addPlot: (RSLinePlot new x: x y: y). -c addDecoration: (RSHorizontalTick new). -c addDecoration: (RSVerticalTick new). -c -```" - - decorations add: aDecoration. - aDecoration chart: self. - aDecoration styler: self styler. - ^ aDecoration -] - -{ #category : 'accessing' } -RSAbstractChart >> chart [ - - ^ self subclassResponsibility -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> chartExtents [ - - ^ extents ifNil: [ extents := RSChartExtents new ] -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> chartExtents: aRSChartExtents [ - - extents := aRSChartExtents -] - -{ #category : 'color' } -RSAbstractChart >> colorFor: aRSPlot [ - "Return a color for the given plot. Colors are defined as in #defaultPlotColors" - - ^ colorPalette scale: aRSPlot -] - -{ #category : 'accessing' } -RSAbstractChart >> colors [ - - ^ colorPalette -] - -{ #category : 'accessing' } -RSAbstractChart >> colors: someColors [ - - colorPalette := someColors -] - -{ #category : 'rendering' } -RSAbstractChart >> createXScale [ - - | padding | - xScale ifNil: [ xScale := NSScale linear ]. - xScale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := self padding x. - xScale - domain: { - self chart minChartValueX. - self chart maxChartValueX }; - range: { - (0 + padding). - (self chart extent x - padding) } -] - -{ #category : 'rendering' } -RSAbstractChart >> createYScale [ - - | padding | - yScale ifNil: [ yScale := NSScale linear ]. - yScale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := self padding y. - yScale - domain: { - self chart minChartValueY. - self chart maxChartValueY }; - range: { - (0 - padding). - (self extent y negated + padding) } -] - -{ #category : 'accessing' } -RSAbstractChart >> decorations [ - "Return the list of decorations used to annotate plots" - - ^ decorations -] - -{ #category : 'accessing - defaults' } -RSAbstractChart >> defaultContainer [ - ^ RSCanvas new - in: [ :canvas | canvas inspectorContext interactionsToBeRegistered first noPushFront ]; - addInteraction: (RSCanvasController new - in: [ :controller | - controller zoomToFitInteraction useZoomToFitOnExtentChanged. - controller configuration maxScale: 20. - ]; - yourself); - yourself -] - -{ #category : 'accessing - color' } -RSAbstractChart >> defaultPlotColors [ - - ^ NSScale category20 -] - -{ #category : 'accessing - defaults' } -RSAbstractChart >> defaultStyler [ - ^ RSChartStyler new -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> extent [ - - ^ self chartExtents extent -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> extent: aPoint [ - - self chartExtents extent: aPoint -] - -{ #category : 'accessing' } -RSAbstractChart >> horizontalTick [ - - ^ decorations detect: #isHorizontalTick ifNone: [ nil ] -] - -{ #category : 'initialization' } -RSAbstractChart >> initialize [ - - super initialize. - styler := self defaultStyler. - decorations := OrderedCollection new. - self initializeRenderOptions. - self initializeDecorations. - colorPalette := self defaultPlotColors -] - -{ #category : 'initialization' } -RSAbstractChart >> initializeDecorations [ - "adds the basis decorations to the chart" - - self spineDecoration: RSChartSpineDecoration new. - self addDecoration: RSHorizontalTick new. - self addDecoration: RSVerticalTick new -] - -{ #category : 'initialization' } -RSAbstractChart >> initializeRenderOptions [ - "extent is 250@200 to have a bit larger chart, and a small padding not to make plots - overlap with spine decoration, for better visibility" - - self extent: 250 @ 200. - self padding: 5 @ 5 -] - -{ #category : 'inspector' } -RSAbstractChart >> inspectorCanvas [ - - self update. - ^ SpRoassalInspectorPresenter new - canvas: self canvas; - yourself -] - -{ #category : 'inspector' } -RSAbstractChart >> inspectorCanvasContext: aContext [ - - aContext withoutEvaluator -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> maxChartValueX [ - "the maximum value displayed on the x-axis of the chart" - - ^ self subclassResponsibility -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> maxChartValueX: aNumber [ - "sets the maximum value displayed on the x-axis of the chart" - - self chartExtents maxValueX: aNumber -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> maxChartValueY [ - "the maximum value displayed on the y-axis of the chart" - - ^ self subclassResponsibility -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> maxChartValueY: aNumber [ - "sets the maximum value displayed on the y-axis of the chart" - - self chartExtents maxValueY: aNumber -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> minChartValueX [ - "the minimum value displayed on the x-axis of the chart" - - ^ self subclassResponsibility -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> minChartValueX: aNumber [ - "sets the minimum value displayed on the x-axis of the chart" - - self chartExtents minValueX: aNumber -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> minChartValueY [ - "the minimum value displayed on the y-axis of the chart" - - ^ self subclassResponsibility -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> minChartValueY: aNumber [ - "sets the minimum value displayed on the y-axis of the chart" - - self chartExtents minValueY: aNumber -] - -{ #category : 'public - configuration' } -RSAbstractChart >> mustInclude0inX [ - "Make sure that the 0 value is in the x-axis of the chart" - - (0 between: self minChartValueX and: self maxChartValueX) ifTrue: [ ^ self ]. - - self maxChartValueX > 0 - ifTrue: [ self minChartValueX: 0 ] - ifFalse: [ self maxChartValueX: 0 ] -] - -{ #category : 'public - configuration' } -RSAbstractChart >> mustInclude0inY [ - "Make sure that the 0 value is in the y-axis of the chart" - - (0 between: self minChartValueY and: self maxChartValueY) ifTrue: [ ^ self ]. - - self maxChartValueY > 0 - ifTrue: [ self minChartValueY: 0 ] - ifFalse: [ self maxChartValueY: 0 ] -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> padding [ - "the space between the spine and the plots" - - ^ self chartExtents padding -] - -{ #category : 'accessing - extension' } -RSAbstractChart >> padding: aPoint [ - "adds a space corresponding to aPoint between the spine and plots" - - self chartExtents padding: aPoint asPoint -] - -{ #category : 'accessing' } -RSAbstractChart >> plots [ - - ^ self -] - -{ #category : 'removing' } -RSAbstractChart >> removeAllTicks [ - "Remove both horizontal and vertical ticks" - - self removeHorizontalTicks. - self removeVerticalTicks -] - -{ #category : 'removing' } -RSAbstractChart >> removeHorizontalTicks [ - "Remove horizontal ticks from the chart" - - decorations remove: self horizontalTick -] - -{ #category : 'removing' } -RSAbstractChart >> removeVerticalTicks [ - "Remove vertical ticks from the chart" - - decorations remove: self verticalTick -] - -{ #category : 'building' } -RSAbstractChart >> show [ - - ^ self open -] - -{ #category : 'accessing' } -RSAbstractChart >> spine [ - "returns the shape" - ^ self spineDecoration box -] - -{ #category : 'accessing' } -RSAbstractChart >> spineDecoration [ - ^ spineDecoration -] - -{ #category : 'accessing' } -RSAbstractChart >> spineDecoration: aRSChartSpineDecoration [ - - spineDecoration ifNotNil: [ - decorations remove: spineDecoration. - spineDecoration chart: nil ]. - spineDecoration := aRSChartSpineDecoration. - decorations addFirst: spineDecoration. - aRSChartSpineDecoration chart: self -] - -{ #category : 'accessing' } -RSAbstractChart >> styler [ - - ^ styler -] - -{ #category : 'accessing' } -RSAbstractChart >> styler: anRSChartStyler [ - - styler := anRSChartStyler. - decorations do: [ :each | each styler: styler ] -] - -{ #category : 'accessing' } -RSAbstractChart >> title [ - - ^ title -] - -{ #category : 'accessing' } -RSAbstractChart >> title: aTitle [ - "Set the title of a chart. For example: - -```Smalltalk -c := RSChart new. -c addPlot: (RSLinePlot new x: (1 to: 200) y: (1 to: 200) sqrt). -c title: 'Square root'. -c -``` - " - title := aTitle. - ^ self addDecoration: (RSChartTitleDecoration new title: aTitle) -] - -{ #category : 'updating' } -RSAbstractChart >> updateChart: anEvent [ - | canvas camera | - camera := anEvent camera. - canvas := anEvent canvas. - self extent: canvas extent. - self update. - canvas zoomToFit -] - -{ #category : 'accessing' } -RSAbstractChart >> verticalTick [ - - ^ decorations detect: #isVerticalTick ifNone: [ nil ] -] - -{ #category : 'public - scales' } -RSAbstractChart >> xLinear [ - - ^ self xScale: NSScale linear -] - -{ #category : 'public - scales' } -RSAbstractChart >> xLn [ - - ^ self xScale: NSScale ln -] - -{ #category : 'public - scales' } -RSAbstractChart >> xLog [ - - ^ self xScale: NSScale symlog -] - -{ #category : 'public - scales' } -RSAbstractChart >> xRawLog [ - "ensure all your data and axis do not contains zero" - - self horizontalTick locator: RSLogLocator new. - ^ self xScale: NSScale log -] - -{ #category : 'public - scales' } -RSAbstractChart >> xRawLog: aNumber [ - - self horizontalTick locator: (RSLogLocator new base: aNumber). - ^ self xScale: (NSLogScale new base: aNumber) -] - -{ #category : 'public - scales' } -RSAbstractChart >> xScale [ - - ^ xScale -] - -{ #category : 'public - scales' } -RSAbstractChart >> xScale: aScale [ - - xScale := aScale. - decorations do: [ :e | e xScale: aScale ]. - ^ aScale -] - -{ #category : 'public - scales' } -RSAbstractChart >> xSqrt [ - ^ self xScale: NSScale sqrt -] - -{ #category : 'decoration' } -RSAbstractChart >> xTickLabels: aCollection [ - self horizontalTick fromNames: aCollection -] - -{ #category : 'decoration' } -RSAbstractChart >> xTicks: collectionOfNumbers labels: aCollection [ - self horizontalTick ticks: collectionOfNumbers labels: aCollection -] - -{ #category : 'decoration' } -RSAbstractChart >> xlabel [ - - ^ xlabel -] - -{ #category : 'decoration' } -RSAbstractChart >> xlabel: aTitle [ - "Set a label on the horizontal axis" - xlabel := aTitle. - ^ self addDecoration: (RSXLabelDecoration new title: aTitle) -] - -{ #category : 'decoration' } -RSAbstractChart >> xlabel: aTitle offset: aPointOrANumber [ - "Set a label on the horizontal axis, using an offset (useful to avoid overlap with axis labels)" - ^ self addDecoration: (RSXLabelDecoration new title: aTitle; offset: aPointOrANumber) -] - -{ #category : 'decoration' } -RSAbstractChart >> xlabelTop [ - - ^ xlabelTop -] - -{ #category : 'decoration' } -RSAbstractChart >> xlabelTop: aTitle [ - "Set a label on the horizontal top axis" - xlabelTop := aTitle. - ^ self addDecoration: (RSXLabelDecoration new title: aTitle; above) -] - -{ #category : 'public - scales' } -RSAbstractChart >> yLinear [ - ^ self yScale: NSScale linear -] - -{ #category : 'public - scales' } -RSAbstractChart >> yLn [ - ^ self yScale: NSScale ln -] - -{ #category : 'public - scales' } -RSAbstractChart >> yLog [ - ^ self yScale: NSScale symlog -] - -{ #category : 'public - scales' } -RSAbstractChart >> yRawLog [ - "ensure all your data and axis do not contains zero" - self verticalTick locator: RSLogLocator new. - ^ self yScale: NSScale log -] - -{ #category : 'public - scales' } -RSAbstractChart >> yRawLog: aNumber [ - - self verticalTick locator: (RSLogLocator new base: aNumber). - ^ self yScale: (NSLogScale new base: aNumber) -] - -{ #category : 'public - scales' } -RSAbstractChart >> yScale [ - - ^ yScale -] - -{ #category : 'public - scales' } -RSAbstractChart >> yScale: aScale [ - - yScale := aScale. - decorations do: [ :e | e yScale: aScale ]. - ^ aScale -] - -{ #category : 'public - scales' } -RSAbstractChart >> ySqrt [ - ^ self yScale: NSScale sqrt -] - -{ #category : 'decoration' } -RSAbstractChart >> yTickLabels: aCollection [ - self verticalTick fromNames: aCollection -] - -{ #category : 'decoration' } -RSAbstractChart >> yTicks: collectionOfNumbers labels: aCollection [ - self verticalTick ticks: collectionOfNumbers labels: aCollection -] - -{ #category : 'decoration' } -RSAbstractChart >> ylabel [ - - ^ ylabel -] - -{ #category : 'decoration' } -RSAbstractChart >> ylabel: aTitle [ - "Set a label on the vertical axis" - ylabel := aTitle. - ^ self addDecoration: (RSYLabelDecoration new title: aTitle) -] - -{ #category : 'decoration' } -RSAbstractChart >> ylabel: aTitle offset: aPointOrANumber [ - "Set a label on the vertical axis, using an offset (useful to avoid overlap with axis labels)" - ^ self addDecoration: (RSYLabelDecoration new title: aTitle ; offset: aPointOrANumber) -] - -{ #category : 'decoration' } -RSAbstractChart >> ylabelRight [ - - ^ ylabelRight -] - -{ #category : 'decoration' } -RSAbstractChart >> ylabelRight: aTitle [ - "Set a label on the vertical axis" - ylabelRight := aTitle. - ^ self addDecoration: (RSYLabelDecoration new title: aTitle; right; yourself) -] +" + +`RSChart` is the abstract class that encompass plots and charts. + +*Responsibility*: maintain and render plots. + +*Collaborators*: a chart closely interacts with plots and decorations. + +*Variables*: +- xScale: the scale of the x-axis +- yScale: the scale of the y-axis +- extents: the size of the chart +- styler: contains information for the chart like its decorations color or text size +- colorPalette: the colors used to render plots +- decorations: the collection of all decorations (spine, ticks, labels, etc) +- spineDecoration: the spine decoration of the chart +- title: the tilte of the chart +- xlabel: the text used as label for the x-axis +- xlabelTop: the text used as label for the top x-axis +- ylabel: the text used as label for the y-axis +- ylabelRight: the text used as label for the right y-axis + +" +Class { + #name : 'RSAbstractChart', + #superclass : 'RSBuilder', + #instVars : [ + 'xScale', + 'yScale', + 'extents', + 'styler', + 'colorPalette', + 'decorations', + 'spineDecoration', + 'title', + 'xlabel', + 'xlabelTop', + 'ylabel', + 'ylabelRight' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'public - plots' } +RSAbstractChart class >> barHeights: aCollectionY [ + + ^ RSBarPlot new x: (1 to: aCollectionY size) y: aCollectionY +] + +{ #category : 'public - plots' } +RSAbstractChart class >> barWidths: aCollectionX [ + + ^ RSHorizontalBarPlot new + x: aCollectionX + y: (1 to: aCollectionX size) +] + +{ #category : 'testing' } +RSAbstractChart class >> isAbstract [ + + ^ self == RSAbstractChart +] + +{ #category : 'public - plots' } +RSAbstractChart class >> lineX: aCollectionX y: aCollectionY [ + + ^ RSLinePlot new x: aCollectionX y: aCollectionY +] + +{ #category : 'adding' } +RSAbstractChart >> addDecoration: aDecoration [ + "Add a decoration to the chart + +For example: +```Smalltalk +x := -3.14 to: 3.14 by: 0.1. +y := x sin. +c := RSChart new. +c addPlot: (RSLinePlot new x: x y: y). +c addDecoration: (RSHorizontalTick new). +c addDecoration: (RSVerticalTick new). +c +```" + + decorations add: aDecoration. + aDecoration chart: self. + aDecoration styler: self styler. + ^ aDecoration +] + +{ #category : 'accessing' } +RSAbstractChart >> chart [ + + ^ self subclassResponsibility +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> chartExtents [ + + ^ extents ifNil: [ extents := RSChartExtents new ] +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> chartExtents: aRSChartExtents [ + + extents := aRSChartExtents +] + +{ #category : 'color' } +RSAbstractChart >> colorFor: aRSPlot [ + "Return a color for the given plot. Colors are defined as in #defaultPlotColors" + + ^ colorPalette scale: aRSPlot +] + +{ #category : 'accessing' } +RSAbstractChart >> colors [ + + ^ colorPalette +] + +{ #category : 'accessing' } +RSAbstractChart >> colors: someColors [ + + colorPalette := someColors +] + +{ #category : 'rendering' } +RSAbstractChart >> createXScale [ + + | padding | + xScale ifNil: [ xScale := NSScale linear ]. + xScale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := self padding x. + xScale + domain: { + self chart minChartValueX. + self chart maxChartValueX }; + range: { + (0 + padding). + (self chart extent x - padding) } +] + +{ #category : 'rendering' } +RSAbstractChart >> createYScale [ + + | padding | + yScale ifNil: [ yScale := NSScale linear ]. + yScale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := self padding y. + yScale + domain: { + self chart minChartValueY. + self chart maxChartValueY }; + range: { + (0 - padding). + (self extent y negated + padding) } +] + +{ #category : 'accessing' } +RSAbstractChart >> decorations [ + "Return the list of decorations used to annotate plots" + + ^ decorations +] + +{ #category : 'accessing - defaults' } +RSAbstractChart >> defaultContainer [ + ^ RSCanvas new + in: [ :canvas | canvas inspectorContext interactionsToBeRegistered first noPushFront ]; + addInteraction: (RSCanvasController new + in: [ :controller | + controller zoomToFitInteraction useZoomToFitOnExtentChanged. + controller configuration maxScale: 20. + ]; + yourself); + yourself +] + +{ #category : 'accessing - color' } +RSAbstractChart >> defaultPlotColors [ + + ^ NSScale category20 +] + +{ #category : 'accessing - defaults' } +RSAbstractChart >> defaultStyler [ + ^ RSChartStyler new +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> extent [ + + ^ self chartExtents extent +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> extent: aPoint [ + + self chartExtents extent: aPoint +] + +{ #category : 'accessing' } +RSAbstractChart >> horizontalTick [ + + ^ decorations detect: #isHorizontalTick ifNone: [ nil ] +] + +{ #category : 'initialization' } +RSAbstractChart >> initialize [ + + super initialize. + styler := self defaultStyler. + decorations := OrderedCollection new. + self initializeRenderOptions. + self initializeDecorations. + colorPalette := self defaultPlotColors +] + +{ #category : 'initialization' } +RSAbstractChart >> initializeDecorations [ + "adds the basis decorations to the chart" + + self spineDecoration: RSChartSpineDecoration new. + self addDecoration: RSHorizontalTick new. + self addDecoration: RSVerticalTick new +] + +{ #category : 'initialization' } +RSAbstractChart >> initializeRenderOptions [ + "extent is 250@200 to have a bit larger chart, and a small padding not to make plots + overlap with spine decoration, for better visibility" + + self extent: 250 @ 200. + self padding: 5 @ 5 +] + +{ #category : 'inspector' } +RSAbstractChart >> inspectorCanvas [ + + self update. + ^ SpRoassalInspectorPresenter new + canvas: self canvas; + yourself +] + +{ #category : 'inspector' } +RSAbstractChart >> inspectorCanvasContext: aContext [ + + aContext withoutEvaluator +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> maxChartValueX [ + "the maximum value displayed on the x-axis of the chart" + + ^ self subclassResponsibility +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> maxChartValueX: aNumber [ + "sets the maximum value displayed on the x-axis of the chart" + + self chartExtents maxValueX: aNumber +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> maxChartValueY [ + "the maximum value displayed on the y-axis of the chart" + + ^ self subclassResponsibility +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> maxChartValueY: aNumber [ + "sets the maximum value displayed on the y-axis of the chart" + + self chartExtents maxValueY: aNumber +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> minChartValueX [ + "the minimum value displayed on the x-axis of the chart" + + ^ self subclassResponsibility +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> minChartValueX: aNumber [ + "sets the minimum value displayed on the x-axis of the chart" + + self chartExtents minValueX: aNumber +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> minChartValueY [ + "the minimum value displayed on the y-axis of the chart" + + ^ self subclassResponsibility +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> minChartValueY: aNumber [ + "sets the minimum value displayed on the y-axis of the chart" + + self chartExtents minValueY: aNumber +] + +{ #category : 'public - configuration' } +RSAbstractChart >> mustInclude0inX [ + "Make sure that the 0 value is in the x-axis of the chart" + + (0 between: self minChartValueX and: self maxChartValueX) ifTrue: [ ^ self ]. + + self maxChartValueX > 0 + ifTrue: [ self minChartValueX: 0 ] + ifFalse: [ self maxChartValueX: 0 ] +] + +{ #category : 'public - configuration' } +RSAbstractChart >> mustInclude0inY [ + "Make sure that the 0 value is in the y-axis of the chart" + + (0 between: self minChartValueY and: self maxChartValueY) ifTrue: [ ^ self ]. + + self maxChartValueY > 0 + ifTrue: [ self minChartValueY: 0 ] + ifFalse: [ self maxChartValueY: 0 ] +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> padding [ + "the space between the spine and the plots" + + ^ self chartExtents padding +] + +{ #category : 'accessing - extension' } +RSAbstractChart >> padding: aPoint [ + "adds a space corresponding to aPoint between the spine and plots" + + self chartExtents padding: aPoint asPoint +] + +{ #category : 'accessing' } +RSAbstractChart >> plots [ + + ^ self +] + +{ #category : 'removing' } +RSAbstractChart >> removeAllTicks [ + "Remove both horizontal and vertical ticks" + + self removeHorizontalTicks. + self removeVerticalTicks +] + +{ #category : 'removing' } +RSAbstractChart >> removeHorizontalTicks [ + "Remove horizontal ticks from the chart" + + decorations remove: self horizontalTick +] + +{ #category : 'removing' } +RSAbstractChart >> removeVerticalTicks [ + "Remove vertical ticks from the chart" + + decorations remove: self verticalTick +] + +{ #category : 'building' } +RSAbstractChart >> show [ + + ^ self open +] + +{ #category : 'accessing' } +RSAbstractChart >> spine [ + "returns the shape" + ^ self spineDecoration box +] + +{ #category : 'accessing' } +RSAbstractChart >> spineDecoration [ + ^ spineDecoration +] + +{ #category : 'accessing' } +RSAbstractChart >> spineDecoration: aRSChartSpineDecoration [ + + spineDecoration ifNotNil: [ + decorations remove: spineDecoration. + spineDecoration chart: nil ]. + spineDecoration := aRSChartSpineDecoration. + decorations addFirst: spineDecoration. + aRSChartSpineDecoration chart: self +] + +{ #category : 'accessing' } +RSAbstractChart >> styler [ + + ^ styler +] + +{ #category : 'accessing' } +RSAbstractChart >> styler: anRSChartStyler [ + + styler := anRSChartStyler. + decorations do: [ :each | each styler: styler ] +] + +{ #category : 'accessing' } +RSAbstractChart >> title [ + + ^ title +] + +{ #category : 'accessing' } +RSAbstractChart >> title: aTitle [ + "Set the title of a chart. For example: + +```Smalltalk +c := RSChart new. +c addPlot: (RSLinePlot new x: (1 to: 200) y: (1 to: 200) sqrt). +c title: 'Square root'. +c +``` + " + title := aTitle. + ^ self addDecoration: (RSChartTitleDecoration new title: aTitle) +] + +{ #category : 'updating' } +RSAbstractChart >> updateChart: anEvent [ + | canvas camera | + camera := anEvent camera. + canvas := anEvent canvas. + self extent: canvas extent. + self update. + canvas zoomToFit +] + +{ #category : 'accessing' } +RSAbstractChart >> verticalTick [ + + ^ decorations detect: #isVerticalTick ifNone: [ nil ] +] + +{ #category : 'public - scales' } +RSAbstractChart >> xLinear [ + + ^ self xScale: NSScale linear +] + +{ #category : 'public - scales' } +RSAbstractChart >> xLn [ + + ^ self xScale: NSScale ln +] + +{ #category : 'public - scales' } +RSAbstractChart >> xLog [ + + ^ self xScale: NSScale symlog +] + +{ #category : 'public - scales' } +RSAbstractChart >> xRawLog [ + "ensure all your data and axis do not contains zero" + + self horizontalTick locator: RSLogLocator new. + ^ self xScale: NSScale log +] + +{ #category : 'public - scales' } +RSAbstractChart >> xRawLog: aNumber [ + + self horizontalTick locator: (RSLogLocator new base: aNumber). + ^ self xScale: (NSLogScale new base: aNumber) +] + +{ #category : 'public - scales' } +RSAbstractChart >> xScale [ + + ^ xScale +] + +{ #category : 'public - scales' } +RSAbstractChart >> xScale: aScale [ + + xScale := aScale. + decorations do: [ :e | e xScale: aScale ]. + ^ aScale +] + +{ #category : 'public - scales' } +RSAbstractChart >> xSqrt [ + ^ self xScale: NSScale sqrt +] + +{ #category : 'decoration' } +RSAbstractChart >> xTickLabels: aCollection [ + self horizontalTick fromNames: aCollection +] + +{ #category : 'decoration' } +RSAbstractChart >> xTicks: collectionOfNumbers labels: aCollection [ + self horizontalTick ticks: collectionOfNumbers labels: aCollection +] + +{ #category : 'decoration' } +RSAbstractChart >> xlabel [ + + ^ xlabel +] + +{ #category : 'decoration' } +RSAbstractChart >> xlabel: aTitle [ + "Set a label on the horizontal axis" + xlabel := aTitle. + ^ self addDecoration: (RSXLabelDecoration new title: aTitle) +] + +{ #category : 'decoration' } +RSAbstractChart >> xlabel: aTitle offset: aPointOrANumber [ + "Set a label on the horizontal axis, using an offset (useful to avoid overlap with axis labels)" + ^ self addDecoration: (RSXLabelDecoration new title: aTitle; offset: aPointOrANumber) +] + +{ #category : 'decoration' } +RSAbstractChart >> xlabelTop [ + + ^ xlabelTop +] + +{ #category : 'decoration' } +RSAbstractChart >> xlabelTop: aTitle [ + "Set a label on the horizontal top axis" + xlabelTop := aTitle. + ^ self addDecoration: (RSXLabelDecoration new title: aTitle; above) +] + +{ #category : 'public - scales' } +RSAbstractChart >> yLinear [ + ^ self yScale: NSScale linear +] + +{ #category : 'public - scales' } +RSAbstractChart >> yLn [ + ^ self yScale: NSScale ln +] + +{ #category : 'public - scales' } +RSAbstractChart >> yLog [ + ^ self yScale: NSScale symlog +] + +{ #category : 'public - scales' } +RSAbstractChart >> yRawLog [ + "ensure all your data and axis do not contains zero" + self verticalTick locator: RSLogLocator new. + ^ self yScale: NSScale log +] + +{ #category : 'public - scales' } +RSAbstractChart >> yRawLog: aNumber [ + + self verticalTick locator: (RSLogLocator new base: aNumber). + ^ self yScale: (NSLogScale new base: aNumber) +] + +{ #category : 'public - scales' } +RSAbstractChart >> yScale [ + + ^ yScale +] + +{ #category : 'public - scales' } +RSAbstractChart >> yScale: aScale [ + + yScale := aScale. + decorations do: [ :e | e yScale: aScale ]. + ^ aScale +] + +{ #category : 'public - scales' } +RSAbstractChart >> ySqrt [ + ^ self yScale: NSScale sqrt +] + +{ #category : 'decoration' } +RSAbstractChart >> yTickLabels: aCollection [ + self verticalTick fromNames: aCollection +] + +{ #category : 'decoration' } +RSAbstractChart >> yTicks: collectionOfNumbers labels: aCollection [ + self verticalTick ticks: collectionOfNumbers labels: aCollection +] + +{ #category : 'decoration' } +RSAbstractChart >> ylabel [ + + ^ ylabel +] + +{ #category : 'decoration' } +RSAbstractChart >> ylabel: aTitle [ + "Set a label on the vertical axis" + ylabel := aTitle. + ^ self addDecoration: (RSYLabelDecoration new title: aTitle) +] + +{ #category : 'decoration' } +RSAbstractChart >> ylabel: aTitle offset: aPointOrANumber [ + "Set a label on the vertical axis, using an offset (useful to avoid overlap with axis labels)" + ^ self addDecoration: (RSYLabelDecoration new title: aTitle ; offset: aPointOrANumber) +] + +{ #category : 'decoration' } +RSAbstractChart >> ylabelRight [ + + ^ ylabelRight +] + +{ #category : 'decoration' } +RSAbstractChart >> ylabelRight: aTitle [ + "Set a label on the vertical axis" + ylabelRight := aTitle. + ^ self addDecoration: (RSYLabelDecoration new title: aTitle; right; yourself) +] diff --git a/src/Roassal-Chart/RSAbstractChartDecoration.class.st b/src/Roassal-Chart/RSAbstractChartDecoration.class.st index da9d724d..a5211f4b 100644 --- a/src/Roassal-Chart/RSAbstractChartDecoration.class.st +++ b/src/Roassal-Chart/RSAbstractChartDecoration.class.st @@ -1,223 +1,223 @@ -" - -`RSAbstractChartDecoration` is the abstract class for chart decorations. - -*Responsibility*: manage decorations for charts. - -*Collaborators*: interacts with charts. - -*Variables*: -- xScale: the scale of the x-axis -- yScale: the scale of the y-axis -- chart: the chart in which the decoration is -- shape: the shape of the decoration -- styler: the style of the decoration -" -Class { - #name : 'RSAbstractChartDecoration', - #superclass : 'RSObject', - #instVars : [ - 'xScale', - 'yScale', - 'chart', - 'styler', - 'masterShape' - ], - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'rendering' } -RSAbstractChartDecoration >> addShape: aShape [ - "Add a shape to the canvas" - - chart canvas add: aShape -] - -{ #category : 'private - adding' } -RSAbstractChartDecoration >> addedIn: aPlot [ - aPlot addDecoration: self -] - -{ #category : 'rendering' } -RSAbstractChartDecoration >> beforeRenderingIn: aChart [ -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> chart [ - - ^ chart -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> chart: aChart [ - - chart := aChart -] - -{ #category : 'public - shape' } -RSAbstractChartDecoration >> color [ - - ^ self shape color -] - -{ #category : 'public - shape' } -RSAbstractChartDecoration >> color: aColor [ - - self shape color: aColor -] - -{ #category : 'rendering' } -RSAbstractChartDecoration >> createXScale [ - - | padding | - xScale ifNil: [ xScale := NSScale linear ]. - xScale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := chart padding x. - xScale - domain: { - chart minChartValueX. - chart maxChartValueX }; - range: { - (0 + padding). - (chart extent x - padding) } -] - -{ #category : 'rendering' } -RSAbstractChartDecoration >> createYScale [ - - | padding | - yScale ifNil: [ yScale := NSScale linear ]. - yScale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := chart padding y. - yScale - domain: { - chart minChartValueY. - chart maxChartValueY }; - range: { - (0 - padding). - (chart extent y negated + padding) } -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> createdShapes [ - - ^ self subclassResponsibility -] - -{ #category : 'initialization' } -RSAbstractChartDecoration >> defaultShape [ - - ^ self subclassResponsibility -] - -{ #category : 'initialization' } -RSAbstractChartDecoration >> defaultStyler [ - - ^ RSChartStyler new -] - -{ #category : 'initialization' } -RSAbstractChartDecoration >> initialize [ - - super initialize. - masterShape := self defaultShape. - styler := self defaultStyler -] - -{ #category : 'testing' } -RSAbstractChartDecoration >> isHorizontalTick [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractChartDecoration >> isPlot [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractChartDecoration >> isSpineDecoration [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractChartDecoration >> isTitle [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractChartDecoration >> isVerticalTick [ - - ^ false -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> masterShape [ - - ^ masterShape -] - -{ #category : 'rendering' } -RSAbstractChartDecoration >> renderIn: aCanvas [ - - ^ self subclassResponsibility -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> shape [ - - ^ masterShape -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> shape: aRSShape [ - - masterShape := aRSShape -] - -{ #category : 'rendering' } -RSAbstractChartDecoration >> spine [ - "Return the roassal shape that describes the spine" - ^ chart spine -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> styler [ - - ^ styler -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> styler: aStyler [ - - styler := aStyler -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> xScale [ - - ^ xScale -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> xScale: aScale [ - - xScale := aScale -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> yScale [ - - ^ yScale -] - -{ #category : 'accessing' } -RSAbstractChartDecoration >> yScale: aScale [ - - yScale := aScale -] +" + +`RSAbstractChartDecoration` is the abstract class for chart decorations. + +*Responsibility*: manage decorations for charts. + +*Collaborators*: interacts with charts. + +*Variables*: +- xScale: the scale of the x-axis +- yScale: the scale of the y-axis +- chart: the chart in which the decoration is +- shape: the shape of the decoration +- styler: the style of the decoration +" +Class { + #name : 'RSAbstractChartDecoration', + #superclass : 'RSObject', + #instVars : [ + 'xScale', + 'yScale', + 'chart', + 'styler', + 'masterShape' + ], + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'rendering' } +RSAbstractChartDecoration >> addShape: aShape [ + "Add a shape to the canvas" + + chart canvas add: aShape +] + +{ #category : 'private - adding' } +RSAbstractChartDecoration >> addedIn: aPlot [ + aPlot addDecoration: self +] + +{ #category : 'rendering' } +RSAbstractChartDecoration >> beforeRenderingIn: aChart [ +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> chart [ + + ^ chart +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> chart: aChart [ + + chart := aChart +] + +{ #category : 'public - shape' } +RSAbstractChartDecoration >> color [ + + ^ self shape color +] + +{ #category : 'public - shape' } +RSAbstractChartDecoration >> color: aColor [ + + self shape color: aColor +] + +{ #category : 'rendering' } +RSAbstractChartDecoration >> createXScale [ + + | padding | + xScale ifNil: [ xScale := NSScale linear ]. + xScale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := chart padding x. + xScale + domain: { + chart minChartValueX. + chart maxChartValueX }; + range: { + (0 + padding). + (chart extent x - padding) } +] + +{ #category : 'rendering' } +RSAbstractChartDecoration >> createYScale [ + + | padding | + yScale ifNil: [ yScale := NSScale linear ]. + yScale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := chart padding y. + yScale + domain: { + chart minChartValueY. + chart maxChartValueY }; + range: { + (0 - padding). + (chart extent y negated + padding) } +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> createdShapes [ + + ^ self subclassResponsibility +] + +{ #category : 'initialization' } +RSAbstractChartDecoration >> defaultShape [ + + ^ self subclassResponsibility +] + +{ #category : 'initialization' } +RSAbstractChartDecoration >> defaultStyler [ + + ^ RSChartStyler new +] + +{ #category : 'initialization' } +RSAbstractChartDecoration >> initialize [ + + super initialize. + masterShape := self defaultShape. + styler := self defaultStyler +] + +{ #category : 'testing' } +RSAbstractChartDecoration >> isHorizontalTick [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractChartDecoration >> isPlot [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractChartDecoration >> isSpineDecoration [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractChartDecoration >> isTitle [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractChartDecoration >> isVerticalTick [ + + ^ false +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> masterShape [ + + ^ masterShape +] + +{ #category : 'rendering' } +RSAbstractChartDecoration >> renderIn: aCanvas [ + + ^ self subclassResponsibility +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> shape [ + + ^ masterShape +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> shape: aRSShape [ + + masterShape := aRSShape +] + +{ #category : 'rendering' } +RSAbstractChartDecoration >> spine [ + "Return the roassal shape that describes the spine" + ^ chart spine +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> styler [ + + ^ styler +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> styler: aStyler [ + + styler := aStyler +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> xScale [ + + ^ xScale +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> xScale: aScale [ + + xScale := aScale +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> yScale [ + + ^ yScale +] + +{ #category : 'accessing' } +RSAbstractChartDecoration >> yScale: aScale [ + + yScale := aScale +] diff --git a/src/Roassal-Chart/RSAbstractChartPopupBuilder.class.st b/src/Roassal-Chart/RSAbstractChartPopupBuilder.class.st index de96ee67..32d424c3 100644 --- a/src/Roassal-Chart/RSAbstractChartPopupBuilder.class.st +++ b/src/Roassal-Chart/RSAbstractChartPopupBuilder.class.st @@ -1,28 +1,28 @@ -" -Abstract class to descripbe how to create a popup for a given position -" -Class { - #name : 'RSAbstractChartPopupBuilder', - #superclass : 'RSObject', - #instVars : [ - 'position' - ], - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'accessing' } -RSAbstractChartPopupBuilder >> position [ - ^ position -] - -{ #category : 'accessing' } -RSAbstractChartPopupBuilder >> position: aPoint [ - position := aPoint -] - -{ #category : 'hooks' } -RSAbstractChartPopupBuilder >> shapeFor: aRSChart [ - ^ self subclassResponsibility -] +" +Abstract class to descripbe how to create a popup for a given position +" +Class { + #name : 'RSAbstractChartPopupBuilder', + #superclass : 'RSObject', + #instVars : [ + 'position' + ], + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'accessing' } +RSAbstractChartPopupBuilder >> position [ + ^ position +] + +{ #category : 'accessing' } +RSAbstractChartPopupBuilder >> position: aPoint [ + position := aPoint +] + +{ #category : 'hooks' } +RSAbstractChartPopupBuilder >> shapeFor: aRSChart [ + ^ self subclassResponsibility +] diff --git a/src/Roassal-Chart/RSAbstractLabelDecoration.class.st b/src/Roassal-Chart/RSAbstractLabelDecoration.class.st index 56d9c005..8b260ddc 100644 --- a/src/Roassal-Chart/RSAbstractLabelDecoration.class.st +++ b/src/Roassal-Chart/RSAbstractLabelDecoration.class.st @@ -1,124 +1,124 @@ -" - -`RSAbstractLabelDecoration` is the abstract class for chart labels. - -*Responsibility*: manage label decorations for charts. - -*Collaborators*: interacts with charts. - -*Variables*: -- location: how the label is located. -- label: the label as an `RSLabel` object. -- title: the title of the label. -" -Class { - #name : 'RSAbstractLabelDecoration', - #superclass : 'RSAbstractChartDecoration', - #instVars : [ - 'location', - 'label', - 'title' - ], - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'rendering' } -RSAbstractLabelDecoration >> createLabel [ - "Utility method to create a label" - ^ self shape copy - text: title; - color: self styler textColor; - yourself -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> createdShapes [ - ^ { label } -] - -{ #category : 'initialization' } -RSAbstractLabelDecoration >> defaultShape [ - ^ RSLabel new -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> fontSize [ - "Return the font size to use when generating labels" - ^ self shape fontSize -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> fontSize: fontSizeToUse [ - "Set the font size to use when generating labels" - self shape fontSize: fontSizeToUse -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> horizontal [ - "Set the label horizontal" - self rotationAngle: 0 -] - -{ #category : 'initialization' } -RSAbstractLabelDecoration >> initialize [ - super initialize. - location := RSLocation new -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> label [ - ^ label -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> location [ - ^ location -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> location: aRSLocation [ - ^ location -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> offset [ - ^ location offset -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> offset: aPointOrNumber [ - "Set the translation distance to set the label" - location offset: aPointOrNumber -] - -{ #category : 'rendering' } -RSAbstractLabelDecoration >> renderIn: canvas [ - label := self createLabel. - location move: label on: canvas encompassingRectangle. - canvas add: label -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> rotationAngle: anAngleAsFloat [ - "Set the rotation the label should have" - ^ self shape rotateByDegrees: anAngleAsFloat -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> title [ - ^ title -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> title: aLabelAsString [ - "This method is useful to set the name of an axis" - title := aLabelAsString -] - -{ #category : 'accessing' } -RSAbstractLabelDecoration >> vertical [ - "Set the label vertical" - self rotationAngle: -90 -] +" + +`RSAbstractLabelDecoration` is the abstract class for chart labels. + +*Responsibility*: manage label decorations for charts. + +*Collaborators*: interacts with charts. + +*Variables*: +- location: how the label is located. +- label: the label as an `RSLabel` object. +- title: the title of the label. +" +Class { + #name : 'RSAbstractLabelDecoration', + #superclass : 'RSAbstractChartDecoration', + #instVars : [ + 'location', + 'label', + 'title' + ], + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'rendering' } +RSAbstractLabelDecoration >> createLabel [ + "Utility method to create a label" + ^ self shape copy + text: title; + color: self styler textColor; + yourself +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> createdShapes [ + ^ { label } +] + +{ #category : 'initialization' } +RSAbstractLabelDecoration >> defaultShape [ + ^ RSLabel new +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> fontSize [ + "Return the font size to use when generating labels" + ^ self shape fontSize +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> fontSize: fontSizeToUse [ + "Set the font size to use when generating labels" + self shape fontSize: fontSizeToUse +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> horizontal [ + "Set the label horizontal" + self rotationAngle: 0 +] + +{ #category : 'initialization' } +RSAbstractLabelDecoration >> initialize [ + super initialize. + location := RSLocation new +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> label [ + ^ label +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> location [ + ^ location +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> location: aRSLocation [ + ^ location +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> offset [ + ^ location offset +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> offset: aPointOrNumber [ + "Set the translation distance to set the label" + location offset: aPointOrNumber +] + +{ #category : 'rendering' } +RSAbstractLabelDecoration >> renderIn: canvas [ + label := self createLabel. + location move: label on: canvas encompassingRectangle. + canvas add: label +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> rotationAngle: anAngleAsFloat [ + "Set the rotation the label should have" + ^ self shape rotateByDegrees: anAngleAsFloat +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> title [ + ^ title +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> title: aLabelAsString [ + "This method is useful to set the name of an axis" + title := aLabelAsString +] + +{ #category : 'accessing' } +RSAbstractLabelDecoration >> vertical [ + "Set the label vertical" + self rotationAngle: -90 +] diff --git a/src/Roassal-Chart/RSAbstractMarkerDecoration.class.st b/src/Roassal-Chart/RSAbstractMarkerDecoration.class.st index ba8ae55b..e86d172c 100644 --- a/src/Roassal-Chart/RSAbstractMarkerDecoration.class.st +++ b/src/Roassal-Chart/RSAbstractMarkerDecoration.class.st @@ -1,123 +1,123 @@ -" -Class: RSAbstractMarkerDecoration - -Set a marker in the chart. For example: - -=-=-=-==-=-=-==-=-=-= -x := (-3.14 to: 3.14 by: 0.01). -c := RSCompositeChart new. - -p := RSLinePlot new. -p x: x y: x sin * 0.22. -c addPlot: p. - -p := RSLinePlot new. -p x: x y: x cos * 0.18. -c addPlot: p. - -c verticalTick asFloat. -c addDecoration: (RSYMarkerDecoration new ). -c -=-=-=-==-=-=-==-=-=-= -" -Class { - #name : 'RSAbstractMarkerDecoration', - #superclass : 'RSAbstractChartDecoration', - #traits : 'RSTLine', - #classTraits : 'RSTLine classTrait', - #instVars : [ - 'markerColor', - 'getRelevantValueBlock', - 'lines' - ], - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'public - configuration' } -RSAbstractMarkerDecoration >> average [ - self subclassResponsibility -] - -{ #category : 'public - shape' } -RSAbstractMarkerDecoration >> color: aColor [ - - super color: aColor. - markerColor := aColor -] - -{ #category : 'rendering' } -RSAbstractMarkerDecoration >> createMarkerLineFromPlot: aPlot [ - "This method should be overriden to produce a line" - self subclassResponsibility -] - -{ #category : 'accessing' } -RSAbstractMarkerDecoration >> createdShapes [ - ^ lines -] - -{ #category : 'rendering' } -RSAbstractMarkerDecoration >> defaultDashStyle [ - ^ #(2 2 2) -] - -{ #category : 'initialization' } -RSAbstractMarkerDecoration >> defaultMarkerColor [ - ^ Color red -] - -{ #category : 'initialization' } -RSAbstractMarkerDecoration >> defaultShape [ - ^ RSLine new -] - -{ #category : 'rendering' } -RSAbstractMarkerDecoration >> getValueToBeMarkedFromPlot: p [ - - ^ getRelevantValueBlock rsValue: p -] - -{ #category : 'initialization' } -RSAbstractMarkerDecoration >> initialize [ - - super initialize. - - "Per default, mark the max value" - self max. - markerColor := self color ifNil: [ self defaultMarkerColor ] ifNotNil: [ self color ] -] - -{ #category : 'accessing' } -RSAbstractMarkerDecoration >> lines [ - ^ lines -] - -{ #category : 'public - configuration' } -RSAbstractMarkerDecoration >> max [ - self subclassResponsibility -] - -{ #category : 'public - configuration' } -RSAbstractMarkerDecoration >> min [ - self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractMarkerDecoration >> renderIn: canvas [ - - | line | - line := self createMarkerLineFromPlot: chart. - line - dashArray: self defaultDashStyle; - color: markerColor. - self addShape: line. - lines := { line } as: RSGroup -] - -{ #category : 'public - configuration' } -RSAbstractMarkerDecoration >> value: aValue [ - "Mark the value" - getRelevantValueBlock := aValue -] +" +Class: RSAbstractMarkerDecoration + +Set a marker in the chart. For example: + +=-=-=-==-=-=-==-=-=-= +x := (-3.14 to: 3.14 by: 0.01). +c := RSCompositeChart new. + +p := RSLinePlot new. +p x: x y: x sin * 0.22. +c addPlot: p. + +p := RSLinePlot new. +p x: x y: x cos * 0.18. +c addPlot: p. + +c verticalTick asFloat. +c addDecoration: (RSYMarkerDecoration new ). +c +=-=-=-==-=-=-==-=-=-= +" +Class { + #name : 'RSAbstractMarkerDecoration', + #superclass : 'RSAbstractChartDecoration', + #traits : 'RSTLine', + #classTraits : 'RSTLine classTrait', + #instVars : [ + 'markerColor', + 'getRelevantValueBlock', + 'lines' + ], + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'public - configuration' } +RSAbstractMarkerDecoration >> average [ + self subclassResponsibility +] + +{ #category : 'public - shape' } +RSAbstractMarkerDecoration >> color: aColor [ + + super color: aColor. + markerColor := aColor +] + +{ #category : 'rendering' } +RSAbstractMarkerDecoration >> createMarkerLineFromPlot: aPlot [ + "This method should be overriden to produce a line" + self subclassResponsibility +] + +{ #category : 'accessing' } +RSAbstractMarkerDecoration >> createdShapes [ + ^ lines +] + +{ #category : 'rendering' } +RSAbstractMarkerDecoration >> defaultDashStyle [ + ^ #(2 2 2) +] + +{ #category : 'initialization' } +RSAbstractMarkerDecoration >> defaultMarkerColor [ + ^ Color red +] + +{ #category : 'initialization' } +RSAbstractMarkerDecoration >> defaultShape [ + ^ RSLine new +] + +{ #category : 'rendering' } +RSAbstractMarkerDecoration >> getValueToBeMarkedFromPlot: p [ + + ^ getRelevantValueBlock rsValue: p +] + +{ #category : 'initialization' } +RSAbstractMarkerDecoration >> initialize [ + + super initialize. + + "Per default, mark the max value" + self max. + markerColor := self color ifNil: [ self defaultMarkerColor ] ifNotNil: [ self color ] +] + +{ #category : 'accessing' } +RSAbstractMarkerDecoration >> lines [ + ^ lines +] + +{ #category : 'public - configuration' } +RSAbstractMarkerDecoration >> max [ + self subclassResponsibility +] + +{ #category : 'public - configuration' } +RSAbstractMarkerDecoration >> min [ + self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractMarkerDecoration >> renderIn: canvas [ + + | line | + line := self createMarkerLineFromPlot: chart. + line + dashArray: self defaultDashStyle; + color: markerColor. + self addShape: line. + lines := { line } as: RSGroup +] + +{ #category : 'public - configuration' } +RSAbstractMarkerDecoration >> value: aValue [ + "Mark the value" + getRelevantValueBlock := aValue +] diff --git a/src/Roassal-Chart/RSAbstractPlot.class.st b/src/Roassal-Chart/RSAbstractPlot.class.st index a7176285..b2cd9ddd 100644 --- a/src/Roassal-Chart/RSAbstractPlot.class.st +++ b/src/Roassal-Chart/RSAbstractPlot.class.st @@ -1,415 +1,415 @@ -" - -`RSAbstractPlot` is the abstract class that encompass plots. - -*Responsibility*: maintain and render plots. - -*Collaborators*: a plot closely interacts with decorations and can be added in a `RSCompositeChart`. - -*Variables*: -- chart: if the plot is added into a chart, refers to this chart, else refers to the plot itself -- shape: the shape of the plot -- xValues: the x values of the plot -- yValues: the y values of the plot -" -Class { - #name : 'RSAbstractPlot', - #superclass : 'RSAbstractChart', - #instVars : [ - 'chart', - 'shape', - 'xValues', - 'yValues', - 'rawData' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'testing' } -RSAbstractPlot class >> isAbstract [ - - ^ self == RSAbstractPlot -] - -{ #category : 'instance creation' } -RSAbstractPlot class >> points: aCollectionOfPoints [ - ^ self new - x: (aCollectionOfPoints collect: #x) y: (aCollectionOfPoints collect: #y); - yourself -] - -{ #category : 'instance creation' } -RSAbstractPlot class >> x: collectionX y: collectionY [ - ^ self new - x: collectionX y: collectionY; - yourself -] - -{ #category : 'accessing' } -RSAbstractPlot >> + aRSPlot [ - | newChart | - newChart := (aRSPlot canHandleCluster and: [ self canHandleCluster ]) - ifTrue: [ RSClusterChart new ] - ifFalse: [ RSCompositeChart new ]. - newChart add: self. - newChart add: aRSPlot. - ^ newChart -] - -{ #category : 'rendering' } -RSAbstractPlot >> beforeRenderingIn: aChart [ - - self createXScale. - self createYScale -] - -{ #category : 'testing' } -RSAbstractPlot >> canHandleCluster [ - ^ false -] - -{ #category : 'accessing' } -RSAbstractPlot >> chart [ - - ^ chart ifNil: [ self ] -] - -{ #category : 'accessing' } -RSAbstractPlot >> chart: aChart [ - - chart := aChart -] - -{ #category : 'testing' } -RSAbstractPlot >> checkAssertion [ - - self - assert: [ xValues isNotNil and: [ yValues isNotNil ] ] - description: 'X and Y values must be added'. - self - assert: [ xValues size = yValues size ] - description: - 'X and Y values have not the same size, and they should' -] - -{ #category : 'public - shape' } -RSAbstractPlot >> color [ - - ^ self shape color -] - -{ #category : 'accessing - computed' } -RSAbstractPlot >> color: aColor [ - - self shape color: aColor -] - -{ #category : 'accessing - computed' } -RSAbstractPlot >> computeColor [ - "Return the color used by the chart element. The color is computed from the chart and from the colorBlock variable" - - ^ self color ifNil: [ self chart colorFor: self ] -] - -{ #category : 'accessing' } -RSAbstractPlot >> createdShapes [ - - ^ self subclassResponsibility -] - -{ #category : 'accessing - defaults' } -RSAbstractPlot >> defaultShape [ - - ^ self subclassResponsibility -] - -{ #category : 'rendering' } -RSAbstractPlot >> definedValuesX [ - "Return the list of X values that are defined" - - ^ xValues select: [ :v | v isNaN not and: [ v isInfinite not ] ] -] - -{ #category : 'rendering' } -RSAbstractPlot >> definedValuesY [ - "Return the list Y values that are defined" - - ^ yValues select: [ :v | v isNaN not and: [ v isInfinite not ] ] -] - -{ #category : 'initialization' } -RSAbstractPlot >> initialize [ - - super initialize. - shape := self defaultShape -] - -{ #category : 'rendering' } -RSAbstractPlot >> invertCoordinates [ - | auxScale | - auxScale := self yScale. - self yScale: self xScale. - self xScale: auxScale. - self x: self y y: self x -] - -{ #category : 'testing' } -RSAbstractPlot >> isBarPlot [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractPlot >> isBoxPlot [ - ^ false -] - -{ #category : 'testing' } -RSAbstractPlot >> isHorizontalBarPlot [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractPlot >> isLinePlot [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractPlot >> isPlot [ - - ^ true -] - -{ #category : 'testing' } -RSAbstractPlot >> isPointWellDefined: aPoint [ - "Indicate whether the point deserves to be displayed" - - ^ (aPoint x isInfinite not and: [ aPoint y isInfinite not ]) and: [ - aPoint x isNaN not and: [ aPoint y isNaN not ] ] -] - -{ #category : 'testing' } -RSAbstractPlot >> isScatterPlot [ - - ^ false -] - -{ #category : 'testing' } -RSAbstractPlot >> isVerticalBarPlot [ - - ^ false -] - -{ #category : 'accessing' } -RSAbstractPlot >> masterShape [ - ^ self shape -] - -{ #category : 'accessing' } -RSAbstractPlot >> masterShape: aShape [ - self shape: aShape -] - -{ #category : 'accessing - extension' } -RSAbstractPlot >> maxChartValueX [ - "if not set before, returns the maximum x value of the plot" - - ^ self chartExtents maxValueX - ifNil: [ - | res | - self chartExtents maxValueX: (res := self maxValueX). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'accessing - extension' } -RSAbstractPlot >> maxChartValueY [ - "if not set before, returns the maximum y value of the plot" - - ^ self chartExtents maxValueY - ifNil: [ - | res | - self chartExtents maxValueY: (res := self maxValueY). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'rendering' } -RSAbstractPlot >> maxValueX [ - "Return the maximum X value of the plot, excluding NaN and infinite" - - ^ self definedValuesX max -] - -{ #category : 'rendering' } -RSAbstractPlot >> maxValueY [ - "Return the maximum Y value of the plot, excluding NaN and infinite" - - ^ self definedValuesY max -] - -{ #category : 'accessing - extension' } -RSAbstractPlot >> minChartValueX [ - "if not set before, returns the minimum x value of the plot" - - ^ self chartExtents minValueX - ifNil: [ - | res | - self chartExtents minValueX: (res := self minValueX). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'accessing - extension' } -RSAbstractPlot >> minChartValueY [ - "if not set before, returns the minimum y value of the plot" - - ^ self chartExtents minValueY - ifNil: [ - | res | - self chartExtents minValueY: (res := self minValueY). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'rendering' } -RSAbstractPlot >> minValueX [ - "Return the minimum X value of the plot, excluding NaN and infinite" - - ^ self definedValuesX min -] - -{ #category : 'rendering' } -RSAbstractPlot >> minValueY [ - "Return the minimum Y value of the plot, excluding NaN and infinite" - - ^ self definedValuesY min -] - -{ #category : 'public' } -RSAbstractPlot >> rawData: aCollection x: bloc1 y: bloc2 [ - - rawData := aCollection. - self x: (rawData collect: bloc1) y: (rawData collect: bloc2) -] - -{ #category : 'rendering' } -RSAbstractPlot >> renderIn: aCanvas [ - - self beforeRenderingIn: self chart. - decorations do: [ :e | e beforeRenderingIn: self ]. - decorations do: [ :e | e renderIn: aCanvas ]. - shapes := decorations - flatCollect: [ :element | element createdShapes ] - as: RSGroup -] - -{ #category : 'accessing - computed' } -RSAbstractPlot >> scalePoint: aPoint [ - - ^ (xScale scale: aPoint x) @ (yScale scale: aPoint y) -] - -{ #category : 'accessing' } -RSAbstractPlot >> shape [ - - ^ shape -] - -{ #category : 'accessing' } -RSAbstractPlot >> shape: aRSShape [ - - shape := aRSShape -] - -{ #category : 'accessing' } -RSAbstractPlot >> x [ - - ^ xValues -] - -{ #category : 'public' } -RSAbstractPlot >> x: aCollection y: aCollection2 [ - "Define a plot with the X and Y coordinates. Both X and Y are collections of the same size. - - For example: -``` -x := (-3.14 to: 3.14 by: 0.01). -c := RSChart new. - -p := RSLinePlot new. -p x: x y: x sin * 0.22. -c addPlot: p. - -p := RSLinePlot new. -p x: x y: x cos * 0.18. -c addPlot: p. - -c addDecoration: RSHorizontalTick new. -c addDecoration: RSVerticalTick new asFloat. -c addDecoration: RSYMarkerDecoration new. -c -``` - " - - self - assert: [ aCollection isCollection ] - description: 'Should be a collection'. - self - assert: [ aCollection2 isCollection ] - description: 'Should be a collection'. - self - assert: [ aCollection size = aCollection2 size ] - description: 'The two collections must have the same size'. - - xValues := aCollection. - yValues := aCollection2 -] - -{ #category : 'accessing' } -RSAbstractPlot >> xValues [ - - ^ xValues -] - -{ #category : 'accessing' } -RSAbstractPlot >> y [ - - ^ yValues -] - -{ #category : 'public' } -RSAbstractPlot >> y: aCollection [ - "Define a plot with only a Y coordinate. The X coordinate is the index of the data point. - - For example: --=-=-=-=-=-=-=-=-= -x := (-3.14 to: 3.14 by: 0.01). -c := RSChart new. - -p := RSLinePlot new. -p y: x sin * 0.22. -c addPlot: p. - -p := RSLinePlot new. -p y: x cos * 0.18. -c addPlot: p. - -c addDecoration: RSHorizontalTick new. -c addDecoration: (RSVerticalTick new asFloat). -c --=-=-=-=-=-=-=-=-= - " - - self x: (1 to: aCollection size) y: aCollection -] - -{ #category : 'accessing' } -RSAbstractPlot >> yValues [ - - ^ yValues -] +" + +`RSAbstractPlot` is the abstract class that encompass plots. + +*Responsibility*: maintain and render plots. + +*Collaborators*: a plot closely interacts with decorations and can be added in a `RSCompositeChart`. + +*Variables*: +- chart: if the plot is added into a chart, refers to this chart, else refers to the plot itself +- shape: the shape of the plot +- xValues: the x values of the plot +- yValues: the y values of the plot +" +Class { + #name : 'RSAbstractPlot', + #superclass : 'RSAbstractChart', + #instVars : [ + 'chart', + 'shape', + 'xValues', + 'yValues', + 'rawData' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'testing' } +RSAbstractPlot class >> isAbstract [ + + ^ self == RSAbstractPlot +] + +{ #category : 'instance creation' } +RSAbstractPlot class >> points: aCollectionOfPoints [ + ^ self new + x: (aCollectionOfPoints collect: #x) y: (aCollectionOfPoints collect: #y); + yourself +] + +{ #category : 'instance creation' } +RSAbstractPlot class >> x: collectionX y: collectionY [ + ^ self new + x: collectionX y: collectionY; + yourself +] + +{ #category : 'accessing' } +RSAbstractPlot >> + aRSPlot [ + | newChart | + newChart := (aRSPlot canHandleCluster and: [ self canHandleCluster ]) + ifTrue: [ RSClusterChart new ] + ifFalse: [ RSCompositeChart new ]. + newChart add: self. + newChart add: aRSPlot. + ^ newChart +] + +{ #category : 'rendering' } +RSAbstractPlot >> beforeRenderingIn: aChart [ + + self createXScale. + self createYScale +] + +{ #category : 'testing' } +RSAbstractPlot >> canHandleCluster [ + ^ false +] + +{ #category : 'accessing' } +RSAbstractPlot >> chart [ + + ^ chart ifNil: [ self ] +] + +{ #category : 'accessing' } +RSAbstractPlot >> chart: aChart [ + + chart := aChart +] + +{ #category : 'testing' } +RSAbstractPlot >> checkAssertion [ + + self + assert: [ xValues isNotNil and: [ yValues isNotNil ] ] + description: 'X and Y values must be added'. + self + assert: [ xValues size = yValues size ] + description: + 'X and Y values have not the same size, and they should' +] + +{ #category : 'public - shape' } +RSAbstractPlot >> color [ + + ^ self shape color +] + +{ #category : 'accessing - computed' } +RSAbstractPlot >> color: aColor [ + + self shape color: aColor +] + +{ #category : 'accessing - computed' } +RSAbstractPlot >> computeColor [ + "Return the color used by the chart element. The color is computed from the chart and from the colorBlock variable" + + ^ self color ifNil: [ self chart colorFor: self ] +] + +{ #category : 'accessing' } +RSAbstractPlot >> createdShapes [ + + ^ self subclassResponsibility +] + +{ #category : 'accessing - defaults' } +RSAbstractPlot >> defaultShape [ + + ^ self subclassResponsibility +] + +{ #category : 'rendering' } +RSAbstractPlot >> definedValuesX [ + "Return the list of X values that are defined" + + ^ xValues select: [ :v | v isNaN not and: [ v isInfinite not ] ] +] + +{ #category : 'rendering' } +RSAbstractPlot >> definedValuesY [ + "Return the list Y values that are defined" + + ^ yValues select: [ :v | v isNaN not and: [ v isInfinite not ] ] +] + +{ #category : 'initialization' } +RSAbstractPlot >> initialize [ + + super initialize. + shape := self defaultShape +] + +{ #category : 'rendering' } +RSAbstractPlot >> invertCoordinates [ + | auxScale | + auxScale := self yScale. + self yScale: self xScale. + self xScale: auxScale. + self x: self y y: self x +] + +{ #category : 'testing' } +RSAbstractPlot >> isBarPlot [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractPlot >> isBoxPlot [ + ^ false +] + +{ #category : 'testing' } +RSAbstractPlot >> isHorizontalBarPlot [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractPlot >> isLinePlot [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractPlot >> isPlot [ + + ^ true +] + +{ #category : 'testing' } +RSAbstractPlot >> isPointWellDefined: aPoint [ + "Indicate whether the point deserves to be displayed" + + ^ (aPoint x isInfinite not and: [ aPoint y isInfinite not ]) and: [ + aPoint x isNaN not and: [ aPoint y isNaN not ] ] +] + +{ #category : 'testing' } +RSAbstractPlot >> isScatterPlot [ + + ^ false +] + +{ #category : 'testing' } +RSAbstractPlot >> isVerticalBarPlot [ + + ^ false +] + +{ #category : 'accessing' } +RSAbstractPlot >> masterShape [ + ^ self shape +] + +{ #category : 'accessing' } +RSAbstractPlot >> masterShape: aShape [ + self shape: aShape +] + +{ #category : 'accessing - extension' } +RSAbstractPlot >> maxChartValueX [ + "if not set before, returns the maximum x value of the plot" + + ^ self chartExtents maxValueX + ifNil: [ + | res | + self chartExtents maxValueX: (res := self maxValueX). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'accessing - extension' } +RSAbstractPlot >> maxChartValueY [ + "if not set before, returns the maximum y value of the plot" + + ^ self chartExtents maxValueY + ifNil: [ + | res | + self chartExtents maxValueY: (res := self maxValueY). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'rendering' } +RSAbstractPlot >> maxValueX [ + "Return the maximum X value of the plot, excluding NaN and infinite" + + ^ self definedValuesX max +] + +{ #category : 'rendering' } +RSAbstractPlot >> maxValueY [ + "Return the maximum Y value of the plot, excluding NaN and infinite" + + ^ self definedValuesY max +] + +{ #category : 'accessing - extension' } +RSAbstractPlot >> minChartValueX [ + "if not set before, returns the minimum x value of the plot" + + ^ self chartExtents minValueX + ifNil: [ + | res | + self chartExtents minValueX: (res := self minValueX). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'accessing - extension' } +RSAbstractPlot >> minChartValueY [ + "if not set before, returns the minimum y value of the plot" + + ^ self chartExtents minValueY + ifNil: [ + | res | + self chartExtents minValueY: (res := self minValueY). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'rendering' } +RSAbstractPlot >> minValueX [ + "Return the minimum X value of the plot, excluding NaN and infinite" + + ^ self definedValuesX min +] + +{ #category : 'rendering' } +RSAbstractPlot >> minValueY [ + "Return the minimum Y value of the plot, excluding NaN and infinite" + + ^ self definedValuesY min +] + +{ #category : 'public' } +RSAbstractPlot >> rawData: aCollection x: bloc1 y: bloc2 [ + + rawData := aCollection. + self x: (rawData collect: bloc1) y: (rawData collect: bloc2) +] + +{ #category : 'rendering' } +RSAbstractPlot >> renderIn: aCanvas [ + + self beforeRenderingIn: self chart. + decorations do: [ :e | e beforeRenderingIn: self ]. + decorations do: [ :e | e renderIn: aCanvas ]. + shapes := decorations + flatCollect: [ :element | element createdShapes ] + as: RSGroup +] + +{ #category : 'accessing - computed' } +RSAbstractPlot >> scalePoint: aPoint [ + + ^ (xScale scale: aPoint x) @ (yScale scale: aPoint y) +] + +{ #category : 'accessing' } +RSAbstractPlot >> shape [ + + ^ shape +] + +{ #category : 'accessing' } +RSAbstractPlot >> shape: aRSShape [ + + shape := aRSShape +] + +{ #category : 'accessing' } +RSAbstractPlot >> x [ + + ^ xValues +] + +{ #category : 'public' } +RSAbstractPlot >> x: aCollection y: aCollection2 [ + "Define a plot with the X and Y coordinates. Both X and Y are collections of the same size. + + For example: +``` +x := (-3.14 to: 3.14 by: 0.01). +c := RSChart new. + +p := RSLinePlot new. +p x: x y: x sin * 0.22. +c addPlot: p. + +p := RSLinePlot new. +p x: x y: x cos * 0.18. +c addPlot: p. + +c addDecoration: RSHorizontalTick new. +c addDecoration: RSVerticalTick new asFloat. +c addDecoration: RSYMarkerDecoration new. +c +``` + " + + self + assert: [ aCollection isCollection ] + description: 'Should be a collection'. + self + assert: [ aCollection2 isCollection ] + description: 'Should be a collection'. + self + assert: [ aCollection size = aCollection2 size ] + description: 'The two collections must have the same size'. + + xValues := aCollection. + yValues := aCollection2 +] + +{ #category : 'accessing' } +RSAbstractPlot >> xValues [ + + ^ xValues +] + +{ #category : 'accessing' } +RSAbstractPlot >> y [ + + ^ yValues +] + +{ #category : 'public' } +RSAbstractPlot >> y: aCollection [ + "Define a plot with only a Y coordinate. The X coordinate is the index of the data point. + + For example: +-=-=-=-=-=-=-=-=-= +x := (-3.14 to: 3.14 by: 0.01). +c := RSChart new. + +p := RSLinePlot new. +p y: x sin * 0.22. +c addPlot: p. + +p := RSLinePlot new. +p y: x cos * 0.18. +c addPlot: p. + +c addDecoration: RSHorizontalTick new. +c addDecoration: (RSVerticalTick new asFloat). +c +-=-=-=-=-=-=-=-=-= + " + + self x: (1 to: aCollection size) y: aCollection +] + +{ #category : 'accessing' } +RSAbstractPlot >> yValues [ + + ^ yValues +] diff --git a/src/Roassal-Chart/RSAbstractTick.class.st b/src/Roassal-Chart/RSAbstractTick.class.st index e1b46d15..f072cf2e 100644 --- a/src/Roassal-Chart/RSAbstractTick.class.st +++ b/src/Roassal-Chart/RSAbstractTick.class.st @@ -1,349 +1,349 @@ -" -I define basic configuration for ticks, my subclasses creates lines and labels for each defined axis -" -Class { - #name : 'RSAbstractTick', - #superclass : 'RSAbstractChartDecoration', - #instVars : [ - 'ticks', - 'labels', - 'labelLocation', - 'configuration', - 'tickLocator', - 'ticksData' - ], - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'public' } -RSAbstractTick >> asFloat [ - self asFloat: 3 -] - -{ #category : 'public' } -RSAbstractTick >> asFloat: numberOfDecimals [ - "Do not convert the value when determining labels" - self labelConversion: [ :value | value asFloat round: numberOfDecimals ] -] - -{ #category : 'rendering' } -RSAbstractTick >> beforeRenderingIn: aChart [ - - self createXScale. - self createYScale. - - self updateChartMaxAndMinValues: aChart. - - "We need to recalculate the scales because in the above method, - updateChartMaxAndMinValues:, we change the dimensions of the chart. And now we need to - recalculate the scales to be consistent with the dimensions." - self createXScale. - chart createXScale. - self createYScale. - chart createYScale -] - -{ #category : 'public' } -RSAbstractTick >> computeTicksData [ - - | scaleToUse data | - scaleToUse := self scaleToUse. - data := tickLocator - generateTicks: scaleToUse - with: self configuration numberOfTicks. - data ifEmpty: [ Error signal: 'Empty ticks data' ]. - configuration numberOfTicks: data size. - - ^ data -] - -{ #category : 'accessing' } -RSAbstractTick >> configuration [ - ^ configuration ifNil: [ configuration := self defaultConfiguration ] -] - -{ #category : 'accessing' } -RSAbstractTick >> configuration: aRSTickConfiguration [ - configuration := aRSTickConfiguration -] - -{ #category : 'rendering' } -RSAbstractTick >> createLabelFor: aValue index: index [ - "Create a label and returns it." - | tick lbl | - tick := self ticks at: index. - lbl := self configuration createLabelFor: aValue. - lbl color: self styler textColor. - self labelLocation move: lbl on: tick. - ^ lbl -] - -{ #category : 'rendering' } -RSAbstractTick >> createTickLineFor: aNumber [ - ^ self subclassResponsibility -] - -{ #category : 'accessing' } -RSAbstractTick >> createdShapes [ - ^ labels, ticks -] - -{ #category : 'accessing - defaults' } -RSAbstractTick >> defaultConfiguration [ - ^ RSTickConfiguration new -] - -{ #category : 'accessing - defaults' } -RSAbstractTick >> defaultLabelLocation [ - ^ self subclassResponsibility -] - -{ #category : 'initialization' } -RSAbstractTick >> defaultLocator [ - - ^ RSAutoLocator new -] - -{ #category : 'initialization' } -RSAbstractTick >> defaultShape [ - ^ RSLine new -] - -{ #category : 'public' } -RSAbstractTick >> doNotUseNiceLabel [ - - self locator: RSLinearLocator new -] - -{ #category : 'accessing' } -RSAbstractTick >> fontSize: fontSizeToSet [ - "Set the font size used by the labels associated to ticks" - -" -For example: --=-=-=-=-=-=-=-=-= -x := (-10 to: 10). -y := x * x. - -c := RSChart new. -d := RSLinePlot new x: x y: y. -c addPlot: d. - -c extent: 400 @ 400. -c addDecoration: (RSVerticalTick new fontSize: 20). -c addDecoration: (RSHorizontalTick new fontSize: 20). -c --=-=-=-=-=-=-=-=-= -" - self configuration fontSize: fontSizeToSet -] - -{ #category : 'public' } -RSAbstractTick >> fromNames: aCollectionOfStrings [ - | size | - size := aCollectionOfStrings size. - self ticks: (1 to: size) labels: aCollectionOfStrings -] - -{ #category : 'initialization' } -RSAbstractTick >> initialize [ - - super initialize. - tickLocator := self defaultLocator -] - -{ #category : 'public' } -RSAbstractTick >> integer [ - "Do not convert the value when determining labels" - self labelConversion: [ :value | value asInteger ] -] - -{ #category : 'public' } -RSAbstractTick >> integerWithCommas [ - "Do not convert the value when determining labels" - self labelConversion: [ :value | value asInteger asStringWithCommas ] -] - -{ #category : 'testing' } -RSAbstractTick >> isTicksDataNil [ - ^ ticksData isNil -] - -{ #category : 'public' } -RSAbstractTick >> labelConversion: oneArgBlock [ - "This method is used to convert numerical values into a label. This is useful to particularlize labels on the X or Y axes. The parameter block accepts one argument, which is a numerical value. The block should return a string or any other object. When displayed, the message asString will be sent to it. - - For example: --=-=-=-=-=-=-=-=-= -x := #(-2 -1 0 1 2). -y := #(5 10 12 14 20). - -c := RSChart new. -d := RSLinePlot new x: x y: y. -c addPlot: d. - -c addDecoration: (RSVerticalTick new). -c addDecoration: (RSHorizontalTick new - numberOfTicks: x size; - labelConversion: [ :val | Date today addDays: val ] ). -c mustInclude0inY. -c --=-=-=-=-=-=-=-=-= - " - self configuration labelConversion: oneArgBlock -] - -{ #category : 'accessing' } -RSAbstractTick >> labelLocation [ - ^ labelLocation ifNil: [ labelLocation := self defaultLabelLocation ] -] - -{ #category : 'accessing' } -RSAbstractTick >> labelRotation [ - ^ self configuration labelRotation -] - -{ #category : 'accessing' } -RSAbstractTick >> labelRotation: aNumber [ - self configuration labelRotation: aNumber -] - -{ #category : 'accessing' } -RSAbstractTick >> labels [ - ^ labels -] - -{ #category : 'accessing' } -RSAbstractTick >> locator [ - - ^ tickLocator -] - -{ #category : 'accessing' } -RSAbstractTick >> locator: aLocator [ - - tickLocator := aLocator -] - -{ #category : 'accessing' } -RSAbstractTick >> max [ - ^ self subclassResponsibility -] - -{ #category : 'accessing' } -RSAbstractTick >> min [ - ^ self subclassResponsibility -] - -{ #category : 'instance creation' } -RSAbstractTick >> newLineTick [ - ^ self shape copy - color: self styler tickColor; - yourself -] - -{ #category : 'public' } -RSAbstractTick >> noConvertion [ - "Do not convert the value when determining labels" - self labelConversion: [ :value | value ] -] - -{ #category : 'accessing' } -RSAbstractTick >> numberOfTicks: aNumber [ - "Set the number of ticks to be used. Can be used with doNotUseNiceLabels or not. - -For example: -```Smalltalk -x := (-3.14 to: 3.14 count: 20). -y := x sin. - -c := RSChart new. -d := RSLinePlot new x: x y: y. -c addPlot: d. -c addDecoration: (RSVerticalTick new). -c addDecoration: (RSHorizontalTick new numberOfTicks: 10; asFloat: 2). -c -```Smalltalk - " - self configuration numberOfTicks: aNumber -] - -{ #category : 'rendering' } -RSAbstractTick >> renderIn: canvas [ - ticksData := self ticksData. - ticks := ticksData collect: [ :value | self createTickLineFor: value ]. - canvas addAll: ticks. - self configuration shouldHaveLabels ifFalse: [ - labels := #(). - ^ self ]. - labels := ticksData collectWithIndex: [ :value :index | - self createLabelFor: value index: index ]. - canvas addAll: labels -] - -{ #category : 'public' } -RSAbstractTick >> scaleToUse [ - - ^ self isHorizontalTick - ifTrue: [ xScale ] - ifFalse: [ yScale ] -] - -{ #category : 'accessing' } -RSAbstractTick >> ticks [ - ^ ticks -] - -{ #category : 'public' } -RSAbstractTick >> ticks: collectionOfNumbers labels: collectionOfStrings [ - self - assert: collectionOfNumbers size = collectionOfStrings size - description: 'Both collections should have same size'. - self - assert: collectionOfStrings isNotEmpty - description: 'collectionOfStrings should not be empty'. - self ticksData: collectionOfNumbers. - self labelConversion: [ :number | - | index | - index := collectionOfNumbers indexOf: number. - (index between: 1 and: collectionOfStrings size) - ifTrue: [ collectionOfStrings at: index ] - ifFalse: [ '' ] - ] -] - -{ #category : 'public' } -RSAbstractTick >> ticksData [ - ^ ticksData ifNil: [ ticksData := self computeTicksData ] -] - -{ #category : 'public' } -RSAbstractTick >> ticksData: collectionOfNumbers [ - ticksData := collectionOfNumbers -] - -{ #category : 'rendering' } -RSAbstractTick >> updateChartMaxAndMinValues: aChart [ - - self subclassResponsibility -] - -{ #category : 'public' } -RSAbstractTick >> useNiceLabel [ - - self locator: RSAutoLocator new -] - -{ #category : 'public' } -RSAbstractTick >> withLabels [ - "Make the tick have label" - self configuration shouldHaveLabels: true -] - -{ #category : 'public' } -RSAbstractTick >> withNoLabels [ - "Ticks have no label" - self configuration shouldHaveLabels: false -] +" +I define basic configuration for ticks, my subclasses creates lines and labels for each defined axis +" +Class { + #name : 'RSAbstractTick', + #superclass : 'RSAbstractChartDecoration', + #instVars : [ + 'ticks', + 'labels', + 'labelLocation', + 'configuration', + 'tickLocator', + 'ticksData' + ], + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'public' } +RSAbstractTick >> asFloat [ + self asFloat: 3 +] + +{ #category : 'public' } +RSAbstractTick >> asFloat: numberOfDecimals [ + "Do not convert the value when determining labels" + self labelConversion: [ :value | value asFloat round: numberOfDecimals ] +] + +{ #category : 'rendering' } +RSAbstractTick >> beforeRenderingIn: aChart [ + + self createXScale. + self createYScale. + + self updateChartMaxAndMinValues: aChart. + + "We need to recalculate the scales because in the above method, + updateChartMaxAndMinValues:, we change the dimensions of the chart. And now we need to + recalculate the scales to be consistent with the dimensions." + self createXScale. + chart createXScale. + self createYScale. + chart createYScale +] + +{ #category : 'public' } +RSAbstractTick >> computeTicksData [ + + | scaleToUse data | + scaleToUse := self scaleToUse. + data := tickLocator + generateTicks: scaleToUse + with: self configuration numberOfTicks. + data ifEmpty: [ Error signal: 'Empty ticks data' ]. + configuration numberOfTicks: data size. + + ^ data +] + +{ #category : 'accessing' } +RSAbstractTick >> configuration [ + ^ configuration ifNil: [ configuration := self defaultConfiguration ] +] + +{ #category : 'accessing' } +RSAbstractTick >> configuration: aRSTickConfiguration [ + configuration := aRSTickConfiguration +] + +{ #category : 'rendering' } +RSAbstractTick >> createLabelFor: aValue index: index [ + "Create a label and returns it." + | tick lbl | + tick := self ticks at: index. + lbl := self configuration createLabelFor: aValue. + lbl color: self styler textColor. + self labelLocation move: lbl on: tick. + ^ lbl +] + +{ #category : 'rendering' } +RSAbstractTick >> createTickLineFor: aNumber [ + ^ self subclassResponsibility +] + +{ #category : 'accessing' } +RSAbstractTick >> createdShapes [ + ^ labels, ticks +] + +{ #category : 'accessing - defaults' } +RSAbstractTick >> defaultConfiguration [ + ^ RSTickConfiguration new +] + +{ #category : 'accessing - defaults' } +RSAbstractTick >> defaultLabelLocation [ + ^ self subclassResponsibility +] + +{ #category : 'initialization' } +RSAbstractTick >> defaultLocator [ + + ^ RSAutoLocator new +] + +{ #category : 'initialization' } +RSAbstractTick >> defaultShape [ + ^ RSLine new +] + +{ #category : 'public' } +RSAbstractTick >> doNotUseNiceLabel [ + + self locator: RSLinearLocator new +] + +{ #category : 'accessing' } +RSAbstractTick >> fontSize: fontSizeToSet [ + "Set the font size used by the labels associated to ticks" + +" +For example: +-=-=-=-=-=-=-=-=-= +x := (-10 to: 10). +y := x * x. + +c := RSChart new. +d := RSLinePlot new x: x y: y. +c addPlot: d. + +c extent: 400 @ 400. +c addDecoration: (RSVerticalTick new fontSize: 20). +c addDecoration: (RSHorizontalTick new fontSize: 20). +c +-=-=-=-=-=-=-=-=-= +" + self configuration fontSize: fontSizeToSet +] + +{ #category : 'public' } +RSAbstractTick >> fromNames: aCollectionOfStrings [ + | size | + size := aCollectionOfStrings size. + self ticks: (1 to: size) labels: aCollectionOfStrings +] + +{ #category : 'initialization' } +RSAbstractTick >> initialize [ + + super initialize. + tickLocator := self defaultLocator +] + +{ #category : 'public' } +RSAbstractTick >> integer [ + "Do not convert the value when determining labels" + self labelConversion: [ :value | value asInteger ] +] + +{ #category : 'public' } +RSAbstractTick >> integerWithCommas [ + "Do not convert the value when determining labels" + self labelConversion: [ :value | value asInteger asStringWithCommas ] +] + +{ #category : 'testing' } +RSAbstractTick >> isTicksDataNil [ + ^ ticksData isNil +] + +{ #category : 'public' } +RSAbstractTick >> labelConversion: oneArgBlock [ + "This method is used to convert numerical values into a label. This is useful to particularlize labels on the X or Y axes. The parameter block accepts one argument, which is a numerical value. The block should return a string or any other object. When displayed, the message asString will be sent to it. + + For example: +-=-=-=-=-=-=-=-=-= +x := #(-2 -1 0 1 2). +y := #(5 10 12 14 20). + +c := RSChart new. +d := RSLinePlot new x: x y: y. +c addPlot: d. + +c addDecoration: (RSVerticalTick new). +c addDecoration: (RSHorizontalTick new + numberOfTicks: x size; + labelConversion: [ :val | Date today addDays: val ] ). +c mustInclude0inY. +c +-=-=-=-=-=-=-=-=-= + " + self configuration labelConversion: oneArgBlock +] + +{ #category : 'accessing' } +RSAbstractTick >> labelLocation [ + ^ labelLocation ifNil: [ labelLocation := self defaultLabelLocation ] +] + +{ #category : 'accessing' } +RSAbstractTick >> labelRotation [ + ^ self configuration labelRotation +] + +{ #category : 'accessing' } +RSAbstractTick >> labelRotation: aNumber [ + self configuration labelRotation: aNumber +] + +{ #category : 'accessing' } +RSAbstractTick >> labels [ + ^ labels +] + +{ #category : 'accessing' } +RSAbstractTick >> locator [ + + ^ tickLocator +] + +{ #category : 'accessing' } +RSAbstractTick >> locator: aLocator [ + + tickLocator := aLocator +] + +{ #category : 'accessing' } +RSAbstractTick >> max [ + ^ self subclassResponsibility +] + +{ #category : 'accessing' } +RSAbstractTick >> min [ + ^ self subclassResponsibility +] + +{ #category : 'instance creation' } +RSAbstractTick >> newLineTick [ + ^ self shape copy + color: self styler tickColor; + yourself +] + +{ #category : 'public' } +RSAbstractTick >> noConvertion [ + "Do not convert the value when determining labels" + self labelConversion: [ :value | value ] +] + +{ #category : 'accessing' } +RSAbstractTick >> numberOfTicks: aNumber [ + "Set the number of ticks to be used. Can be used with doNotUseNiceLabels or not. + +For example: +```Smalltalk +x := (-3.14 to: 3.14 count: 20). +y := x sin. + +c := RSChart new. +d := RSLinePlot new x: x y: y. +c addPlot: d. +c addDecoration: (RSVerticalTick new). +c addDecoration: (RSHorizontalTick new numberOfTicks: 10; asFloat: 2). +c +```Smalltalk + " + self configuration numberOfTicks: aNumber +] + +{ #category : 'rendering' } +RSAbstractTick >> renderIn: canvas [ + ticksData := self ticksData. + ticks := ticksData collect: [ :value | self createTickLineFor: value ]. + canvas addAll: ticks. + self configuration shouldHaveLabels ifFalse: [ + labels := #(). + ^ self ]. + labels := ticksData collectWithIndex: [ :value :index | + self createLabelFor: value index: index ]. + canvas addAll: labels +] + +{ #category : 'public' } +RSAbstractTick >> scaleToUse [ + + ^ self isHorizontalTick + ifTrue: [ xScale ] + ifFalse: [ yScale ] +] + +{ #category : 'accessing' } +RSAbstractTick >> ticks [ + ^ ticks +] + +{ #category : 'public' } +RSAbstractTick >> ticks: collectionOfNumbers labels: collectionOfStrings [ + self + assert: collectionOfNumbers size = collectionOfStrings size + description: 'Both collections should have same size'. + self + assert: collectionOfStrings isNotEmpty + description: 'collectionOfStrings should not be empty'. + self ticksData: collectionOfNumbers. + self labelConversion: [ :number | + | index | + index := collectionOfNumbers indexOf: number. + (index between: 1 and: collectionOfStrings size) + ifTrue: [ collectionOfStrings at: index ] + ifFalse: [ '' ] + ] +] + +{ #category : 'public' } +RSAbstractTick >> ticksData [ + ^ ticksData ifNil: [ ticksData := self computeTicksData ] +] + +{ #category : 'public' } +RSAbstractTick >> ticksData: collectionOfNumbers [ + ticksData := collectionOfNumbers +] + +{ #category : 'rendering' } +RSAbstractTick >> updateChartMaxAndMinValues: aChart [ + + self subclassResponsibility +] + +{ #category : 'public' } +RSAbstractTick >> useNiceLabel [ + + self locator: RSAutoLocator new +] + +{ #category : 'public' } +RSAbstractTick >> withLabels [ + "Make the tick have label" + self configuration shouldHaveLabels: true +] + +{ #category : 'public' } +RSAbstractTick >> withNoLabels [ + "Ticks have no label" + self configuration shouldHaveLabels: false +] diff --git a/src/Roassal-Chart/RSAutoLocator.class.st b/src/Roassal-Chart/RSAutoLocator.class.st index 32a6e44e..585fa617 100644 --- a/src/Roassal-Chart/RSAutoLocator.class.st +++ b/src/Roassal-Chart/RSAutoLocator.class.st @@ -1,21 +1,21 @@ -" - -`RSAutoLocator` places ticks in a ""nice"" way, meaning their values are ""round"". However the final tick may be greater than the maximum data value, and the number of ticks may be close but not equal to the one requested. - -*Responsibility*: Places ticks nicely. - -*Collaborators*: `RSAutoLocator` is used when rendering ticks. -" -Class { - #name : 'RSAutoLocator', - #superclass : 'RSTickLocator', - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'generate' } -RSAutoLocator >> generateTicks: aScale with: numberOfTicks [ - - ^ aScale niceTicks: numberOfTicks -] +" + +`RSAutoLocator` places ticks in a ""nice"" way, meaning their values are ""round"". However the final tick may be greater than the maximum data value, and the number of ticks may be close but not equal to the one requested. + +*Responsibility*: Places ticks nicely. + +*Collaborators*: `RSAutoLocator` is used when rendering ticks. +" +Class { + #name : 'RSAutoLocator', + #superclass : 'RSTickLocator', + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'generate' } +RSAutoLocator >> generateTicks: aScale with: numberOfTicks [ + + ^ aScale niceTicks: numberOfTicks +] diff --git a/src/Roassal-Chart/RSBarPlot.class.st b/src/Roassal-Chart/RSBarPlot.class.st index b6596cdb..b0221f6e 100644 --- a/src/Roassal-Chart/RSBarPlot.class.st +++ b/src/Roassal-Chart/RSBarPlot.class.st @@ -1,175 +1,175 @@ -" - -`RSBarPlot` is a plot that represents a bar chart. - -*Responsibility*: display a bar chart, with adequate color & bar width value per default. - -*Collaborators*: a bar plot is added to `RSCompositeChart`. - -*Variables*: -- `barWidth`: width of a bar. A default computed value is assigned. - -*Example*: -```Smalltalk -data := #(4 10 5 9). - -d := RSBarPlot new. -d color: Color green darker darker darker. -d y: data. - -d verticalTick integer. - -d addDecoration: (RSYLabelDecoration new title: 'Difference'; rotationAngle: -90; offset: -25 @ 0). -d addDecoration: (RSXLabelDecoration new title: 'Evolution'). -d -``` -" -Class { - #name : 'RSBarPlot', - #superclass : 'RSAbstractBarPlot', - #instVars : [ - 'bottom', - 'errors', - 'errorLines', - 'errorLine' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSBarPlot >> barScale [ - ^ xScale -] - -{ #category : 'rendering' } -RSBarPlot >> beforeRenderingIn: aChart [ - - | barScale | - super beforeRenderingIn: aChart. - xScale class = NSOrdinalScale ifTrue: [ ^ self ]. - barScale := NSOrdinalScale new - domain: xValues; - rangeBands: xScale range padding: gapRatio. - aChart xScale: barScale -] - -{ #category : 'accessing' } -RSBarPlot >> bottom [ - ^ bottom -] - -{ #category : 'accessing' } -RSBarPlot >> bottom: aCollection [ - self - assert: xValues size = aCollection size - description: 'Invalid size'. - bottom := aCollection -] - -{ #category : 'testing' } -RSBarPlot >> canHandleCluster [ - ^ true -] - -{ #category : 'hooks' } -RSBarPlot >> computeRectagleFor: aPoint index: index [ - | origin corner sizeOffset offset zero | - zero := 0. - bottom ifNotNil: [ zero := bottom at: index ]. - origin := self scalePoint: aPoint + (0@ zero). - corner := origin x @ (yScale scale: zero ). - sizeOffset := (self barSize / 2.0) @ 0. - offset := self barOffset @ 0. - ^ Rectangle - origin: origin + offset - sizeOffset - corner: corner + offset + sizeOffset -] - -{ #category : 'defaults' } -RSBarPlot >> defaultErrorLine [ - ^ RSLine new - color: Color black; - yourself -] - -{ #category : 'rendering' } -RSBarPlot >> definedValuesY [ - "Return the list Y values that are defined" - | res | - res := yValues. - bottom ifNotNil: [ res := res + bottom ]. - errors ifNotNil: [ res := res + errors ]. - ^ res, {0} -] - -{ #category : 'accessing' } -RSBarPlot >> errorLine [ - ^ errorLine -] - -{ #category : 'accessing' } -RSBarPlot >> errorLines [ - ^ errorLines -] - -{ #category : 'accessing' } -RSBarPlot >> errors: anArray [ - self assert: anArray isCollection description: 'you should provide a collection'. - self assert: (anArray allSatisfy: [ :each | each isNumber ]) description: 'you should provide a number'. - self assert: yValues size = anArray size description: 'both collections should have the same size'. - errors := anArray -] - -{ #category : 'initialization' } -RSBarPlot >> initialize [ - super initialize. - errorLine := self defaultErrorLine -] - -{ #category : 'testing' } -RSBarPlot >> isVerticalBarPlot [ - ^ true -] - -{ #category : 'hooks' } -RSBarPlot >> modelFor: aPoint [ - ^ aPoint y -] - -{ #category : 'rendering' } -RSBarPlot >> newErrorLineFor: index [ - | top bar error p1 p2 errorPoint marker size | - bar := bars at: index. - error := errors at: index. - top := bar encompassingRectangle topCenter. - errorPoint := 0@(self scalePoint: 0@ error) y. - size := self barSize / 4. - p1 := size @ 0. - p2 := 0 - (size @0). - marker := { RSLine new - color: errorLine color; - controlPoints: { p1. p2 }; - yourself } asShape. - p1 := top + errorPoint. - p2 := top - errorPoint. - ^ errorLine copy - marker: marker; - controlPoints: { p1. p2 }; - yourself. -] - -{ #category : 'rendering' } -RSBarPlot >> renderErrorLinesIfNecessary: canvas [ - errors ifNil: [ ^ self ]. - errorLines := (1 to: errors size) collect: [ :index | - self newErrorLineFor: index ]. - canvas addAll: errorLines -] - -{ #category : 'rendering' } -RSBarPlot >> renderIn: canvas [ - super renderIn: canvas. - self renderErrorLinesIfNecessary: canvas. -] +" + +`RSBarPlot` is a plot that represents a bar chart. + +*Responsibility*: display a bar chart, with adequate color & bar width value per default. + +*Collaborators*: a bar plot is added to `RSCompositeChart`. + +*Variables*: +- `barWidth`: width of a bar. A default computed value is assigned. + +*Example*: +```Smalltalk +data := #(4 10 5 9). + +d := RSBarPlot new. +d color: Color green darker darker darker. +d y: data. + +d verticalTick integer. + +d addDecoration: (RSYLabelDecoration new title: 'Difference'; rotationAngle: -90; offset: -25 @ 0). +d addDecoration: (RSXLabelDecoration new title: 'Evolution'). +d +``` +" +Class { + #name : 'RSBarPlot', + #superclass : 'RSAbstractBarPlot', + #instVars : [ + 'bottom', + 'errors', + 'errorLines', + 'errorLine' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSBarPlot >> barScale [ + ^ xScale +] + +{ #category : 'rendering' } +RSBarPlot >> beforeRenderingIn: aChart [ + + | barScale | + super beforeRenderingIn: aChart. + xScale class = NSOrdinalScale ifTrue: [ ^ self ]. + barScale := NSOrdinalScale new + domain: xValues; + rangeBands: xScale range padding: gapRatio. + aChart xScale: barScale +] + +{ #category : 'accessing' } +RSBarPlot >> bottom [ + ^ bottom +] + +{ #category : 'accessing' } +RSBarPlot >> bottom: aCollection [ + self + assert: xValues size = aCollection size + description: 'Invalid size'. + bottom := aCollection +] + +{ #category : 'testing' } +RSBarPlot >> canHandleCluster [ + ^ true +] + +{ #category : 'hooks' } +RSBarPlot >> computeRectagleFor: aPoint index: index [ + | origin corner sizeOffset offset zero | + zero := 0. + bottom ifNotNil: [ zero := bottom at: index ]. + origin := self scalePoint: aPoint + (0@ zero). + corner := origin x @ (yScale scale: zero ). + sizeOffset := (self barSize / 2.0) @ 0. + offset := self barOffset @ 0. + ^ Rectangle + origin: origin + offset - sizeOffset + corner: corner + offset + sizeOffset +] + +{ #category : 'defaults' } +RSBarPlot >> defaultErrorLine [ + ^ RSLine new + color: Color black; + yourself +] + +{ #category : 'rendering' } +RSBarPlot >> definedValuesY [ + "Return the list Y values that are defined" + | res | + res := yValues. + bottom ifNotNil: [ res := res + bottom ]. + errors ifNotNil: [ res := res + errors ]. + ^ res, {0} +] + +{ #category : 'accessing' } +RSBarPlot >> errorLine [ + ^ errorLine +] + +{ #category : 'accessing' } +RSBarPlot >> errorLines [ + ^ errorLines +] + +{ #category : 'accessing' } +RSBarPlot >> errors: anArray [ + self assert: anArray isCollection description: 'you should provide a collection'. + self assert: (anArray allSatisfy: [ :each | each isNumber ]) description: 'you should provide a number'. + self assert: yValues size = anArray size description: 'both collections should have the same size'. + errors := anArray +] + +{ #category : 'initialization' } +RSBarPlot >> initialize [ + super initialize. + errorLine := self defaultErrorLine +] + +{ #category : 'testing' } +RSBarPlot >> isVerticalBarPlot [ + ^ true +] + +{ #category : 'hooks' } +RSBarPlot >> modelFor: aPoint [ + ^ aPoint y +] + +{ #category : 'rendering' } +RSBarPlot >> newErrorLineFor: index [ + | top bar error p1 p2 errorPoint marker size | + bar := bars at: index. + error := errors at: index. + top := bar encompassingRectangle topCenter. + errorPoint := 0@(self scalePoint: 0@ error) y. + size := self barSize / 4. + p1 := size @ 0. + p2 := 0 - (size @0). + marker := { RSLine new + color: errorLine color; + controlPoints: { p1. p2 }; + yourself } asShape. + p1 := top + errorPoint. + p2 := top - errorPoint. + ^ errorLine copy + marker: marker; + controlPoints: { p1. p2 }; + yourself. +] + +{ #category : 'rendering' } +RSBarPlot >> renderErrorLinesIfNecessary: canvas [ + errors ifNil: [ ^ self ]. + errorLines := (1 to: errors size) collect: [ :index | + self newErrorLineFor: index ]. + canvas addAll: errorLines +] + +{ #category : 'rendering' } +RSBarPlot >> renderIn: canvas [ + super renderIn: canvas. + self renderErrorLinesIfNecessary: canvas. +] diff --git a/src/Roassal-Chart/RSBarPlotNew.class.st b/src/Roassal-Chart/RSBarPlotNew.class.st index a5732011..c88c3af8 100644 --- a/src/Roassal-Chart/RSBarPlotNew.class.st +++ b/src/Roassal-Chart/RSBarPlotNew.class.st @@ -1,110 +1,110 @@ -Class { - #name : 'RSBarPlotNew', - #superclass : 'RSAbstractBandPlot', - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'examples' } -RSBarPlotNew class >> example01BarPlot [ - | p1 data1 | - data1 := { 9. 9. 7. 20. 5. 10. }. - p1 := self new data: data1. - ^ p1 open -] - -{ #category : 'examples' } -RSBarPlotNew class >> example02BarPlotHorizontal [ - | p1 data1 | - data1 := { 9. 9. 7. 19. 5. 10. }. - p1 := (self new) data: data1. - p1 horizontal. - ^ p1 open -] - -{ #category : 'examples' } -RSBarPlotNew class >> example03BarPlotPositions [ - | p1 data1 | - data1 := { 9. 9. 7. 20. 5. 10. }. - p1 := self new data: data1. - p1 positions: { 1. 2. 3. 6. 7. 8. }. - ^ p1 open -] - -{ #category : 'examples' } -RSBarPlotNew class >> example04BarPlotPositions [ - | x y plot1 | - - x := 4 to: 10 by: 0.1. - y := x sin. - plot1 := self new data: y; positions: x. - ^ plot1 open -] - -{ #category : 'examples' } -RSBarPlotNew class >> exampleClusterBarPlots [ - | p1 p2 chart data1 data2 x | - data1 := { 10. 9. 7 }. "blue" - data2 := { 20. 15. 8 }. "orange" - x := { 1. 2. 3. }. - p1 := (self new) data: data1. - p2 := (self new) data: data2. - chart := p1 + p2. - ^ chart open -] - -{ #category : 'examples' } -RSBarPlotNew class >> exampleClusterBarPlots4Bars [ - | p1 p2 chart data1 data2 x p3 p4| - data1 := { 10. 9. 7 }. "blue" - data2 := { 20. 15. 8 }. "orange" - x := { 1. 2. 3. }. - p1 := self new data: data1. - p2 := self new data: data2. - p3 := self new data: data1. - p4 := self new data: data2. - chart := p1 + p2 + p3 + p4. - ^ chart open -] - -{ #category : 'testing' } -RSBarPlotNew >> canHandleCluster [ - ^ true -] - -{ #category : 'accessing' } -RSBarPlotNew >> createdShapes [ - ^ { bandPlotShapes } -] - -{ #category : 'public' } -RSBarPlotNew >> data: aCollection [ - bandPlotShapes := aCollection collect: [ :value | RSBarPlotShape data: value ] -] - -{ #category : 'accessing - defaults' } -RSBarPlotNew >> defaultShape [ - - ^ RSBox new noPaint -] - -{ #category : 'initialization' } -RSBarPlotNew >> initialize [ - super initialize. - horizontal := false. - bandPlotShapes := OrderedCollection new. - offset := 0 -] - -{ #category : 'rendering' } -RSBarPlotNew >> renderIn: canvas [ - super renderIn: canvas. - self computeBandsOffset. - self computeBandsWidth. - bandPlotShapes doWithIndex: [ :barPlotShape :idx | - barPlotShape color ifNil: [ barPlotShape color: self computeColor. ]. - barPlotShape bandScale: bandScale. - barPlotShape dataScale: dataScale. - barPlotShape renderIn: canvas ] -] +Class { + #name : 'RSBarPlotNew', + #superclass : 'RSAbstractBandPlot', + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'examples' } +RSBarPlotNew class >> example01BarPlot [ + | p1 data1 | + data1 := { 9. 9. 7. 20. 5. 10. }. + p1 := self new data: data1. + ^ p1 open +] + +{ #category : 'examples' } +RSBarPlotNew class >> example02BarPlotHorizontal [ + | p1 data1 | + data1 := { 9. 9. 7. 19. 5. 10. }. + p1 := (self new) data: data1. + p1 horizontal. + ^ p1 open +] + +{ #category : 'examples' } +RSBarPlotNew class >> example03BarPlotPositions [ + | p1 data1 | + data1 := { 9. 9. 7. 20. 5. 10. }. + p1 := self new data: data1. + p1 positions: { 1. 2. 3. 6. 7. 8. }. + ^ p1 open +] + +{ #category : 'examples' } +RSBarPlotNew class >> example04BarPlotPositions [ + | x y plot1 | + + x := 4 to: 10 by: 0.1. + y := x sin. + plot1 := self new data: y; positions: x. + ^ plot1 open +] + +{ #category : 'examples' } +RSBarPlotNew class >> exampleClusterBarPlots [ + | p1 p2 chart data1 data2 x | + data1 := { 10. 9. 7 }. "blue" + data2 := { 20. 15. 8 }. "orange" + x := { 1. 2. 3. }. + p1 := (self new) data: data1. + p2 := (self new) data: data2. + chart := p1 + p2. + ^ chart open +] + +{ #category : 'examples' } +RSBarPlotNew class >> exampleClusterBarPlots4Bars [ + | p1 p2 chart data1 data2 x p3 p4| + data1 := { 10. 9. 7 }. "blue" + data2 := { 20. 15. 8 }. "orange" + x := { 1. 2. 3. }. + p1 := self new data: data1. + p2 := self new data: data2. + p3 := self new data: data1. + p4 := self new data: data2. + chart := p1 + p2 + p3 + p4. + ^ chart open +] + +{ #category : 'testing' } +RSBarPlotNew >> canHandleCluster [ + ^ true +] + +{ #category : 'accessing' } +RSBarPlotNew >> createdShapes [ + ^ { bandPlotShapes } +] + +{ #category : 'public' } +RSBarPlotNew >> data: aCollection [ + bandPlotShapes := aCollection collect: [ :value | RSBarPlotShape data: value ] +] + +{ #category : 'accessing - defaults' } +RSBarPlotNew >> defaultShape [ + + ^ RSBox new noPaint +] + +{ #category : 'initialization' } +RSBarPlotNew >> initialize [ + super initialize. + horizontal := false. + bandPlotShapes := OrderedCollection new. + offset := 0 +] + +{ #category : 'rendering' } +RSBarPlotNew >> renderIn: canvas [ + super renderIn: canvas. + self computeBandsOffset. + self computeBandsWidth. + bandPlotShapes doWithIndex: [ :barPlotShape :idx | + barPlotShape color ifNil: [ barPlotShape color: self computeColor. ]. + barPlotShape bandScale: bandScale. + barPlotShape dataScale: dataScale. + barPlotShape renderIn: canvas ] +] diff --git a/src/Roassal-Chart/RSBarPlotShape.class.st b/src/Roassal-Chart/RSBarPlotShape.class.st index 318c0d26..4471ba82 100644 --- a/src/Roassal-Chart/RSBarPlotShape.class.st +++ b/src/Roassal-Chart/RSBarPlotShape.class.st @@ -1,78 +1,78 @@ -Class { - #name : 'RSBarPlotShape', - #superclass : 'RSAbstractBandPlotShape', - #instVars : [ - 'data', - 'bar' - ], - #category : 'Roassal-Chart-Plots', - #package : 'Roassal-Chart', - #tag : 'Plots' -} - -{ #category : 'accessing' } -RSBarPlotShape class >> data: aValue [ - ^ (self new) data: aValue -] - -{ #category : 'shapes' } -RSBarPlotShape >> createBar [ - | barPoints | - - barPoints := { - (bandOffset-(bandWidth/2)) @ (dataScale scale: 0)."bottom left" - (bandOffset+(bandWidth/2)) @ (dataScale scale: 0)."bottom right" - (bandOffset+(bandWidth/2)) @ (dataScale scale: data)."top right" - (bandOffset-(bandWidth/2)) @ (dataScale scale: data)."top left" - } asOrderedCollection. - - bar points: barPoints -] - -{ #category : 'shapes' } -RSBarPlotShape >> createShapesAndLines [ - | shapes | - self createBar. - bar color: color. - shapes := OrderedCollection withAll: { bar. }. - ^ shapes -] - -{ #category : 'accessing' } -RSBarPlotShape >> data: aValue [ - data := aValue -] - -{ #category : 'shapes' } -RSBarPlotShape >> defaultBar [ - ^ RSPolygon new - color: color; - border: (RSBorder new color: Color black; joinRound) -] - -{ #category : 'initialization' } -RSBarPlotShape >> initialize [ - super initialize. - bar := self defaultBar. - horizontal := false. - shouldShowBand := self defaultShouldShowBand -] - -{ #category : 'rendering' } -RSBarPlotShape >> maxDataValue [ - ^ data -] - -{ #category : 'rendering' } -RSBarPlotShape >> minDataValue [ - | minValue | - minValue := 0. - data < 0 ifTrue: [ minValue := data ]. - ^ minValue -] - -{ #category : 'rendering' } -RSBarPlotShape >> renderIn: canvas [ - self addChildrenToComposite. - canvas add: self -] +Class { + #name : 'RSBarPlotShape', + #superclass : 'RSAbstractBandPlotShape', + #instVars : [ + 'data', + 'bar' + ], + #category : 'Roassal-Chart-Plots', + #package : 'Roassal-Chart', + #tag : 'Plots' +} + +{ #category : 'accessing' } +RSBarPlotShape class >> data: aValue [ + ^ (self new) data: aValue +] + +{ #category : 'shapes' } +RSBarPlotShape >> createBar [ + | barPoints | + + barPoints := { + (bandOffset-(bandWidth/2)) @ (dataScale scale: 0)."bottom left" + (bandOffset+(bandWidth/2)) @ (dataScale scale: 0)."bottom right" + (bandOffset+(bandWidth/2)) @ (dataScale scale: data)."top right" + (bandOffset-(bandWidth/2)) @ (dataScale scale: data)."top left" + } asOrderedCollection. + + bar points: barPoints +] + +{ #category : 'shapes' } +RSBarPlotShape >> createShapesAndLines [ + | shapes | + self createBar. + bar color: color. + shapes := OrderedCollection withAll: { bar. }. + ^ shapes +] + +{ #category : 'accessing' } +RSBarPlotShape >> data: aValue [ + data := aValue +] + +{ #category : 'shapes' } +RSBarPlotShape >> defaultBar [ + ^ RSPolygon new + color: color; + border: (RSBorder new color: Color black; joinRound) +] + +{ #category : 'initialization' } +RSBarPlotShape >> initialize [ + super initialize. + bar := self defaultBar. + horizontal := false. + shouldShowBand := self defaultShouldShowBand +] + +{ #category : 'rendering' } +RSBarPlotShape >> maxDataValue [ + ^ data +] + +{ #category : 'rendering' } +RSBarPlotShape >> minDataValue [ + | minValue | + minValue := 0. + data < 0 ifTrue: [ minValue := data ]. + ^ minValue +] + +{ #category : 'rendering' } +RSBarPlotShape >> renderIn: canvas [ + self addChildrenToComposite. + canvas add: self +] diff --git a/src/Roassal-Chart/RSBinSizeBinning.class.st b/src/Roassal-Chart/RSBinSizeBinning.class.st index cc63de12..484d8546 100644 --- a/src/Roassal-Chart/RSBinSizeBinning.class.st +++ b/src/Roassal-Chart/RSBinSizeBinning.class.st @@ -1,39 +1,39 @@ -" -I am an strategy used by RSHistogramPlot by the method binSize: -" -Class { - #name : 'RSBinSizeBinning', - #superclass : 'RSAbstractBinning', - #instVars : [ - 'size' - ], - #category : 'Roassal-Chart-Strategy', - #package : 'Roassal-Chart', - #tag : 'Strategy' -} - -{ #category : 'hooks' } -RSBinSizeBinning >> computeNumberOfBinsFor: aCollection [ - ^ (self createBinsFor: aCollection) size - 1 -] - -{ #category : 'hooks' } -RSBinSizeBinning >> createBinsFor: aCollection [ - | interval | - interval := aCollection first to: aCollection last by: size. - ^ interval last < aCollection last - ifTrue: [ interval , { aCollection last } ] - ifFalse: [ interval ] -] - -{ #category : 'accessing' } -RSBinSizeBinning >> size [ - - ^ size -] - -{ #category : 'accessing' } -RSBinSizeBinning >> size: anObject [ - - size := anObject -] +" +I am an strategy used by RSHistogramPlot by the method binSize: +" +Class { + #name : 'RSBinSizeBinning', + #superclass : 'RSAbstractBinning', + #instVars : [ + 'size' + ], + #category : 'Roassal-Chart-Strategy', + #package : 'Roassal-Chart', + #tag : 'Strategy' +} + +{ #category : 'hooks' } +RSBinSizeBinning >> computeNumberOfBinsFor: aCollection [ + ^ (self createBinsFor: aCollection) size - 1 +] + +{ #category : 'hooks' } +RSBinSizeBinning >> createBinsFor: aCollection [ + | interval | + interval := aCollection first to: aCollection last by: size. + ^ interval last < aCollection last + ifTrue: [ interval , { aCollection last } ] + ifFalse: [ interval ] +] + +{ #category : 'accessing' } +RSBinSizeBinning >> size [ + + ^ size +] + +{ #category : 'accessing' } +RSBinSizeBinning >> size: anObject [ + + size := anObject +] diff --git a/src/Roassal-Chart/RSBlockChartPopupBuilder.class.st b/src/Roassal-Chart/RSBlockChartPopupBuilder.class.st index 502e2ac5..47b29851 100644 --- a/src/Roassal-Chart/RSBlockChartPopupBuilder.class.st +++ b/src/Roassal-Chart/RSBlockChartPopupBuilder.class.st @@ -1,32 +1,32 @@ -" -This uses a block, avoid to use this class and create a subclass of RSLineChartPopupBuilder -" -Class { - #name : 'RSBlockChartPopupBuilder', - #superclass : 'RSLineChartPopupBuilder', - #instVars : [ - 'rowShapeBlock' - ], - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'accessing' } -RSBlockChartPopupBuilder >> rowShapeBlock [ - ^ rowShapeBlock -] - -{ #category : 'accessing' } -RSBlockChartPopupBuilder >> rowShapeBlock: aBlock [ - "two arguments block - first argument is the line plot. - second argument is the point value - should return a RSShape " - rowShapeBlock := aBlock -] - -{ #category : 'hooks' } -RSBlockChartPopupBuilder >> rowShapeFor: aRSLinePlot point: aPoint [ - ^ rowShapeBlock value: aRSLinePlot value: aPoint -] +" +This uses a block, avoid to use this class and create a subclass of RSLineChartPopupBuilder +" +Class { + #name : 'RSBlockChartPopupBuilder', + #superclass : 'RSLineChartPopupBuilder', + #instVars : [ + 'rowShapeBlock' + ], + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'accessing' } +RSBlockChartPopupBuilder >> rowShapeBlock [ + ^ rowShapeBlock +] + +{ #category : 'accessing' } +RSBlockChartPopupBuilder >> rowShapeBlock: aBlock [ + "two arguments block + first argument is the line plot. + second argument is the point value + should return a RSShape " + rowShapeBlock := aBlock +] + +{ #category : 'hooks' } +RSBlockChartPopupBuilder >> rowShapeFor: aRSLinePlot point: aPoint [ + ^ rowShapeBlock value: aRSLinePlot value: aPoint +] diff --git a/src/Roassal-Chart/RSBoxPlot.class.st b/src/Roassal-Chart/RSBoxPlot.class.st index d42e600a..1064d4a2 100644 --- a/src/Roassal-Chart/RSBoxPlot.class.st +++ b/src/Roassal-Chart/RSBoxPlot.class.st @@ -1,808 +1,808 @@ -" -`RSBoxPlot` is a visual representation of the distribution of one or more datasets (one box for each dataset). It provides a visual summary of the data's spread, central tendency, and the presence of outliers. -- The box represents the interquartile range (IQR) of the data, which is the middle 50% of the dataset. It spans from the first quartile (Q1) to the third quartile (Q3). -- The central line marks the median value. -- The whiskers extend from the edges of the box and indicate the data range. The upper whisker extends to the maximum value within a certain limit (by default, 1.5 times the IQR), and the lower whisker extends to the minimum value within the same limit. -- The outliers are data points that fall outside the whiskers. - -**Responsibility:** -- This class plots one or more boxes (and their corresponding whiskers and outliers) given a dataset or a collection of datasets. -- It provides options to customize the boxes (which can be accessed as `boxShapes`). -- Note: the data will be sorted by the class. - -**Collaborators:** -- **`RSBoxPlotShape`:** The instance variable `boxShapes` is a collection of `RSBoxPlotShape`. Each of these boxShapes is a box and whisker visualization which represents the distribution of a 1D dataset. Each shape also knows how to render itself. -- **`NSOrdinalScale`:** This class allows rendering each boxShape in one band by assigning the `bandWidth` and the `bandOffset`. In this way, each boxShape knows which part of the canvas it needs to fill. - -**Public API and Key Messages** -- `data: aCollection` to create instances passing dataset (one or several datasets, 1D or 2D collections) as argument. - *Note: the `data` will be sorted by the class* -- `open` to open the visualization. -- `+ aRSBoxPlot` (returns a new `RSClusterChart`) to create a cluster chart that contains both box plots, each box plot will have a specific color for its boxes, and a cluster will be conformed by several colors. The number of clusters will be determined by how many boxes each plot has. -- `color: aColor` to change the color fill to all the boxes of the plot. -- `colors: aCollection` to change the color to each box in the plot by the color with the same index in the input collection. -- `boxShapes` returns the collection of `RSBoxPlotShape` stored in the instance variable. -- `medianLines` returns a `RSGroup` with the median lines (each one is a `RSPolyline`, lines at the median of each box) to customize the style of the lines as group or each one. (`color:`, `do:`, etc. check `RSGroup`) -- `whiskers` returns a `RSGroup` with the whisker model line (each one is a `RSPolyline`, lines extending to the most extreme, non-outlier data points) to customize the style of the lines as group or each one. (`color:`, `do:`, etc. check `RSGroup`) -- `whiskerFormat: aString` to format the whiskers line stroke and markers. -- `outliers` returns a `RSGroup` with the outlier model shape (each one is by default a `RSEllipse`) to customize the outlier points as group or each one. (`color:`, `do:`, etc. check `RSGroup`) -- `outlierMarker: aString` to change the shape of the marker (by default a `RSEllipse`). - -**Instance Variables:** -- `boxShapes`: a collection of `RSBoxPlotShape`. Each of these boxShapes is a box and whisker visualization which represents the distribution of a 1D dataset. Each shape also knows how to render itself. -- `bandWidth`: a `Number` expressed in pixels (scaled) that determine the box width of every box in the plot. -- `bandOffset` a `Number` expressed in pixels (scaled) that determine an offset for all the boxes in the plot. - -**Example:** -One box with one dataset (1D Collection) -```Smalltalk -| boxPlot data | -data := { 1. 2. 3. 4. 5. } . -boxPlot := RSBoxPlot data: data. -^ boxPlot open. -``` - -Three boxes With three datasets (2D Collection) -```Smalltalk -| boxPlot data | -data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. -boxPlot := self data: data. -^ boxPlot open. -``` - -Clustered boxes (adding plots) -```Smalltalk -| chart p1 p2 p3 y1 y2 y3 | - -y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - -y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - -y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - -p1 := self data: y1. ""blue"" -p2 := self data: y2. ""sky blue"" -p3 := self data: y3. ""orange"" -chart := p1 + p2 + p3. -chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. -^ chart open. -``` - -The plot can be notched to represent the confidence interval around the median. There is 3 possible percentages for the interval: 90%, 95% (default one) and 99%. -Please note that the values used for 90% and 99% may not be exact due to a lack of data about it, but it is correct for 95%. - -``` -| y p1 p2 c size | - -y := { { 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. } . { 1. 2. 3. 4. 5. 6. 7. } }. - -size := 50. - -c := RSCompositeChart new. -p1 := RSBoxPlot new. -p1 y: y. -p1 notch: true. -p1 barSize: size. -p1 barOffset: size * -0.55. -p2 := RSBoxPlot new y: y. -p2 barSize: size. -p2 barOffset: size * 0.55. -c add: p1; add: p2. -c open -``` -" -Class { - #name : 'RSBoxPlot', - #superclass : 'RSAbstractBandPlot', - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'instance creation' } -RSBoxPlot class >> data: aCollection [ - | boxPlot | - boxPlot := self new. - boxPlot data: aCollection. - ^ boxPlot -] - -{ #category : 'examples' } -RSBoxPlot class >> example01BoxPlot [ - | boxPlot data | - data := { 1. 2. 3. 4. 5. } . - boxPlot := self data: data. - ^ boxPlot open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> example01Days [ - | chart p1 p2 p3 y1 y2 y3 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - - y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - - p1 := self data: y1. "blue" - p2 := self data: y2. "sky blue" - p3 := self data: y3. "orange" - chart := p1 + p2 + p3. - chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. - ^ chart open -] - -{ #category : 'examples' } -RSBoxPlot class >> example02BoxPlotOfMultipleDatasets [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. }. - { 1. 10. 10. 10. 10. 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - ^ boxPlot open -] - -{ #category : 'examples' } -RSBoxPlot class >> example03BoxWithNotch [ - | p1 y1 | - - "y1 := { { 12. 12. 13. 14. 15. 24. } }." - y1 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - p1 := self data: y1. "blue" - p1 notch: true. - ^ p1 open -] - -{ #category : 'examples' } -RSBoxPlot class >> example04BoxPlotHorizontal [ - | boxPlot data | - data := { 1. 2. 3. 4. 5. } . - boxPlot := self data: data. - boxPlot horizontal. - ^ boxPlot open -] - -{ #category : 'examples' } -RSBoxPlot class >> example05BoxPlotsHorizontal [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot horizontal. - ^ boxPlot open -] - -{ #category : 'examples' } -RSBoxPlot class >> example05BoxPlotsHorizontalShowBands [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot horizontal. - boxPlot showBands. - ^ boxPlot open -] - -{ #category : 'examples' } -RSBoxPlot class >> example06Days [ - | chart p1 p2 p3 y1 y2 y3 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - - y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - - p1 := self data: y1. "blue" - p2 := self data: y2. "sky blue" - p3 := self data: y3. "orange" - chart := p1 + p2 + p3. - chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. - ^ chart open -] - -{ #category : 'examples' } -RSBoxPlot class >> example06DaysHorizontal [ - | chart p1 p2 p3 y1 y2 y3 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - - y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - - p1 := self data: y1. "blue" - p2 := self data: y2. "sky blue" - p3 := self data: y3. "orange" - chart := p1 + p2 + p3. - chart horizontal. - chart yTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. - ^ chart open -] - -{ #category : 'examples' } -RSBoxPlot class >> example06DaysShowBands [ - | chart p1 p2 p3 y1 y2 y3 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - - y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - - p1 := self data: y1. "blue" - p1 showBands. - p2 := self data: y2. "sky blue" - p2 showBands. - p3 := self data: y3. "orange" - p3 showBands. - chart := p1 + p2 + p3. - chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. - ^ chart open -] - -{ #category : 'examples' } -RSBoxPlot class >> example07BoxPlotGroupsHorizontal [ - | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | - "TO DO" - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot1 horizontal. - boxPlot2 := self data: data2. - boxPlot2 horizontal. - boxPlot3 := self data: data3. - boxPlot3 horizontal. - boxPlot := boxPlot1 + boxPlot2 + boxPlot3. - ^ boxPlot open -] - -{ #category : 'examples' } -RSBoxPlot class >> example08Positions [ - | boxPlot1 data1 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. }. - { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . }. - boxPlot1 := self data: data1. - boxPlot1 positions: { 1. 6. 13. 15.}. - boxPlot1 showBands. - ^ boxPlot1 open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotChangeBoxWidth [ - | boxPlot1 data1 | - data1 := { 12. 12. 13. 14. 15. 24. }. - boxPlot1 := self data: data1. - boxPlot1 bandsWidth: 50. - boxPlot1 xlabel: 'x axis'. - boxPlot1 ylabel: 'y axis'. - ^ boxPlot1 open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotChangeSpaceBetweenBoxes [ - | clusterChart boxPlot1 boxPlot2 data1 data2 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - clusterChart := boxPlot1 + boxPlot2. - - "bandsMargin value between 0 and 1" - clusterChart bandsMargin: 0.5. - - ^ clusterChart open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotChangeSpaceBetweenClusters [ - | clusterChart boxPlot1 boxPlot2 data1 data2 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - clusterChart := boxPlot1 + boxPlot2. - - "clustersMargin value between 0 and 1" - clusterChart clustersMargin: 0.5. - - ^ clusterChart open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotGroups [ - | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot3 := self data: data3. - boxPlot := boxPlot1 + boxPlot2 + boxPlot3. - ^ boxPlot open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotGroupsCustomColorPalette [ - | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot3 := self data: data3. - boxPlot := boxPlot1 + boxPlot2 + boxPlot3. - boxPlot colors: RSColorPalette qualitative accent8. - ^ boxPlot open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotGroupsEven [ - | boxPlot boxPlot1 boxPlot2 data1 data2 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot := boxPlot1 + boxPlot2. - ^ boxPlot open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotGroupsEven4 [ - | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 boxPlot4 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot3 := self data: data2. - boxPlot4 := self data: data2. - boxPlot := boxPlot1 + boxPlot2 + boxPlot3 + boxPlot4. - ^ boxPlot open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotGroupsOdd [ - | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot3 := self data: data3. - boxPlot := boxPlot1 + boxPlot2 + boxPlot3. - ^ boxPlot open -] - -{ #category : 'examples - cluster' } -RSBoxPlot class >> exampleBoxPlotGroupsOdd5 [ - | boxPlot boxPlot1 boxPlot2 boxPlot3 boxPlot4 boxPlot5 data1 data2 data3 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot3 := self data: data3. - boxPlot4 := self data: data3. - boxPlot5 := self data: data3. - boxPlot := boxPlot1 + boxPlot2 + boxPlot3 + boxPlot4 + boxPlot5. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyle [ - | boxPlot data paint | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - paint := LinearGradientPaint fromArray: { 0 -> Color gray. 1 -> Color red }. - paint start: 0@ -50. - paint stop: 0@ 50. - boxPlot := self data: data. - boxPlot notch: true. - boxPlot boxShapes do: [ :bs | bs box cornerRadii: 2 ]. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleBoxFillColor [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot color: Color red translucent. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleBoxFillColorsCollection [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot colors: {Color red. Color green. Color blue.}. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleMedianLine [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot medianLines do: [:ml | - ml dashArray: {4.}. - ml color: Color red. - ]. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleMedianLineColor [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot medianLines color: Color red. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleOneMedianLineColor [ - | boxPlot data | - data := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot := self data: data. - boxPlot medianLines last color: Color red. - (boxPlot medianLines at: 2) color: Color white. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleOutlierShape [ - | boxPlot1 data1 | - data1 := { { 12. 12. 13. 14. 15. 24. } . - { 12. 12. 13. 14. 15. 24. } . - { 12. 12. 13. 14. 15. 24. } }. - boxPlot1 := self data: data1. - boxPlot1 outlierMarker: '+'. - "boxPlot1 boxShapes first outlier: (RSShapeFactory circle size: 10; color: Color red)." - "boxPlot1 outlier: (RSShapeFactory circle size: 10; color: Color red)." - ^ boxPlot1 open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleOutliersColor [ - | boxPlot boxPlot1 boxPlot2 data1 data2 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot1 outliers color: Color red. - boxPlot2 := self data: data2. - boxPlot2 outliers color: Color purple. - boxPlot := boxPlot1 + boxPlot2. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleBoxPlotStyleWhiskerFormat [ - | boxPlot boxPlot1 data1 data2 boxPlot2 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - boxPlot1 := self data: data1. - boxPlot2 := self data: data2. - boxPlot1 whiskerFormat: '--'. - boxPlot2 whiskers do: [ :whiskerLine | whiskerLine format: '.']. - boxPlot := boxPlot1 + boxPlot2. - ^ boxPlot open -] - -{ #category : 'examples - style' } -RSBoxPlot class >> exampleSetYTicksLabels [ - | p1 y1 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - p1 := self data: y1. "blue" - p1 xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3'}. - ^ p1 open -] - -{ #category : 'accessing' } -RSBoxPlot >> boxShapes [ - ^ bandPlotShapes -] - -{ #category : 'rendering' } -RSBoxPlot >> boxShapes: collectionOfRSBoxPlotShape [ - bandPlotShapes := collectionOfRSBoxPlotShape -] - -{ #category : 'rendering' } -RSBoxPlot >> boxes [ - ^ RSGroup new addAll: (bandPlotShapes collect: [ :boxShape | boxShape box ]); yourself -] - -{ #category : 'public' } -RSBoxPlot >> buildChart [ - chart add: self. - chart - extent: 360 @ 300; - padding: 15. - ^ chart -] - -{ #category : 'testing' } -RSBoxPlot >> canHandleCluster [ - ^ true -] - -{ #category : 'accessing - computed' } -RSBoxPlot >> color: aColor [ - bandPlotShapes do: [ :boxShape | boxShape color: aColor ] -] - -{ #category : 'accessing' } -RSBoxPlot >> colors: collectionOfColors [ - bandPlotShapes with: collectionOfColors do: [ :bandShape :color | bandShape color: color ] -] - -{ #category : 'private' } -RSBoxPlot >> computeBoxGraphicsCenters [ - | lastBoxCenter | - lastBoxCenter := 1. - self boxShapes: (bandPlotShapes collect: [ :boxGraph | - boxGraph graphCenter: lastBoxCenter "+groupOffset". - "lastBoxCenter := lastBoxCenter + self defaultOffset". - boxGraph. - ]) -] - -{ #category : 'accessing' } -RSBoxPlot >> confidenceInterval [ - ^ self confidenceIntervals first -] - -{ #category : 'accessing' } -RSBoxPlot >> confidenceIntervals [ - ^ bandPlotShapes collect: [ :boxShape | boxShape confidenceInterval ] -] - -{ #category : 'accessing' } -RSBoxPlot >> confidencePercentage [ - ^ bandPlotShapes collect: [ :boxShape | boxShape confidencePercentage ] -] - -{ #category : 'accessing' } -RSBoxPlot >> confidencePercentage: aPercentage [ - bandPlotShapes do: [ :boxShape | boxShape confidencePercentage: aPercentage ] -] - -{ #category : 'accessing' } -RSBoxPlot >> createdShapes [ - ^ { self boxShapes. } -] - -{ #category : 'rendering' } -RSBoxPlot >> data: aCollection [ - | collectionOfDatasets | - - "if is not a collection of collections. transform in a collection of collections" - collectionOfDatasets := aCollection first isCollection - ifFalse: [ { aCollection } ] - ifTrue: [ aCollection ]. - self boxShapes: (collectionOfDatasets collect: [ :dataSet | RSBoxPlotShape data: dataSet.]). - self computeBoxGraphicsCenters -] - -{ #category : 'accessing - defaults' } -RSBoxPlot >> defaultShape [ - ^ RSPolygon new noPaint -] - -{ #category : 'initialization' } -RSBoxPlot >> initialize [ - super initialize. - offset := 0. - horizontal := false -] - -{ #category : 'rendering' } -RSBoxPlot >> iqr [ - ^ self iqrs first -] - -{ #category : 'rendering' } -RSBoxPlot >> iqrs [ - ^ bandPlotShapes collect: [ :box | box iqr ] -] - -{ #category : 'testing' } -RSBoxPlot >> isBoxPlot [ - ^ true -] - -{ #category : 'rendering' } -RSBoxPlot >> median [ - ^ self medians first -] - -{ #category : 'accessing' } -RSBoxPlot >> medianConfidenceInterval [ - ^ self medianConfidenceIntervals first -] - -{ #category : 'accessing' } -RSBoxPlot >> medianConfidenceIntervals [ - ^ bandPlotShapes collect: [ :boxShape | boxShape medianConfidenceInterval ] -] - -{ #category : 'accessing' } -RSBoxPlot >> medianLines [ - ^ RSGroup new addAll: (bandPlotShapes collect: [ :box | box medianLine ]); yourself -] - -{ #category : 'rendering' } -RSBoxPlot >> medians [ - ^ bandPlotShapes collect: [ :box | box median ] -] - -{ #category : 'accessing' } -RSBoxPlot >> notch: aBool [ - bandPlotShapes do: [ :boxShape | boxShape notch: aBool ]. -] - -{ #category : 'rendering' } -RSBoxPlot >> numberOfBoxes [ - ^ self boxShapes size -] - -{ #category : 'rendering' } -RSBoxPlot >> outlier: markerShape [ - bandPlotShapes do: [ :box | box outlier: markerShape ] -] - -{ #category : 'rendering' } -RSBoxPlot >> outlierMarker: markerShapeString [ - bandPlotShapes do: [ :box | box outlierMarker: markerShapeString ] -] - -{ #category : 'rendering' } -RSBoxPlot >> outliers [ - ^ RSGroup new addAll: (bandPlotShapes collect: [ :box | box outliers ]); yourself -] - -{ #category : 'accessing' } -RSBoxPlot >> quartiles [ - | quartilesArray | - quartilesArray := bandPlotShapes collect: [ :boxShape | boxShape quartiles ]. - bandPlotShapes size = 1 ifTrue: [ ^quartilesArray first ] ifFalse: [ quartilesArray ] -] - -{ #category : 'rendering' } -RSBoxPlot >> renderIn: canvas [ - super renderIn: canvas. - self computeBandsOffset. - self computeBandsWidth. - bandPlotShapes doWithIndex: [ :aRSBoxPlotShape :idx | - aRSBoxPlotShape color ifNil: [ aRSBoxPlotShape color: self computeColor. ]. - aRSBoxPlotShape bandScale: bandScale. - aRSBoxPlotShape dataScale: dataScale. - aRSBoxPlotShape renderIn: canvas ] -] - -{ #category : 'rendering' } -RSBoxPlot >> whiskerFormat: aString [ - bandPlotShapes do: [ :box | box whiskerFormat: aString ] -] - -{ #category : 'accessing' } -RSBoxPlot >> whiskers [ - ^ RSGroup new addAll: (bandPlotShapes collect: [ :box | box whiskers ]); yourself -] - -{ #category : 'public' } -RSBoxPlot >> y: aCollection [ - self data: aCollection -] +" +`RSBoxPlot` is a visual representation of the distribution of one or more datasets (one box for each dataset). It provides a visual summary of the data's spread, central tendency, and the presence of outliers. +- The box represents the interquartile range (IQR) of the data, which is the middle 50% of the dataset. It spans from the first quartile (Q1) to the third quartile (Q3). +- The central line marks the median value. +- The whiskers extend from the edges of the box and indicate the data range. The upper whisker extends to the maximum value within a certain limit (by default, 1.5 times the IQR), and the lower whisker extends to the minimum value within the same limit. +- The outliers are data points that fall outside the whiskers. + +**Responsibility:** +- This class plots one or more boxes (and their corresponding whiskers and outliers) given a dataset or a collection of datasets. +- It provides options to customize the boxes (which can be accessed as `boxShapes`). +- Note: the data will be sorted by the class. + +**Collaborators:** +- **`RSBoxPlotShape`:** The instance variable `boxShapes` is a collection of `RSBoxPlotShape`. Each of these boxShapes is a box and whisker visualization which represents the distribution of a 1D dataset. Each shape also knows how to render itself. +- **`NSOrdinalScale`:** This class allows rendering each boxShape in one band by assigning the `bandWidth` and the `bandOffset`. In this way, each boxShape knows which part of the canvas it needs to fill. + +**Public API and Key Messages** +- `data: aCollection` to create instances passing dataset (one or several datasets, 1D or 2D collections) as argument. + *Note: the `data` will be sorted by the class* +- `open` to open the visualization. +- `+ aRSBoxPlot` (returns a new `RSClusterChart`) to create a cluster chart that contains both box plots, each box plot will have a specific color for its boxes, and a cluster will be conformed by several colors. The number of clusters will be determined by how many boxes each plot has. +- `color: aColor` to change the color fill to all the boxes of the plot. +- `colors: aCollection` to change the color to each box in the plot by the color with the same index in the input collection. +- `boxShapes` returns the collection of `RSBoxPlotShape` stored in the instance variable. +- `medianLines` returns a `RSGroup` with the median lines (each one is a `RSPolyline`, lines at the median of each box) to customize the style of the lines as group or each one. (`color:`, `do:`, etc. check `RSGroup`) +- `whiskers` returns a `RSGroup` with the whisker model line (each one is a `RSPolyline`, lines extending to the most extreme, non-outlier data points) to customize the style of the lines as group or each one. (`color:`, `do:`, etc. check `RSGroup`) +- `whiskerFormat: aString` to format the whiskers line stroke and markers. +- `outliers` returns a `RSGroup` with the outlier model shape (each one is by default a `RSEllipse`) to customize the outlier points as group or each one. (`color:`, `do:`, etc. check `RSGroup`) +- `outlierMarker: aString` to change the shape of the marker (by default a `RSEllipse`). + +**Instance Variables:** +- `boxShapes`: a collection of `RSBoxPlotShape`. Each of these boxShapes is a box and whisker visualization which represents the distribution of a 1D dataset. Each shape also knows how to render itself. +- `bandWidth`: a `Number` expressed in pixels (scaled) that determine the box width of every box in the plot. +- `bandOffset` a `Number` expressed in pixels (scaled) that determine an offset for all the boxes in the plot. + +**Example:** +One box with one dataset (1D Collection) +```Smalltalk +| boxPlot data | +data := { 1. 2. 3. 4. 5. } . +boxPlot := RSBoxPlot data: data. +^ boxPlot open. +``` + +Three boxes With three datasets (2D Collection) +```Smalltalk +| boxPlot data | +data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. +boxPlot := self data: data. +^ boxPlot open. +``` + +Clustered boxes (adding plots) +```Smalltalk +| chart p1 p2 p3 y1 y2 y3 | + +y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + +y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + +y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + +p1 := self data: y1. ""blue"" +p2 := self data: y2. ""sky blue"" +p3 := self data: y3. ""orange"" +chart := p1 + p2 + p3. +chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. +^ chart open. +``` + +The plot can be notched to represent the confidence interval around the median. There is 3 possible percentages for the interval: 90%, 95% (default one) and 99%. +Please note that the values used for 90% and 99% may not be exact due to a lack of data about it, but it is correct for 95%. + +``` +| y p1 p2 c size | + +y := { { 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. } . { 1. 2. 3. 4. 5. 6. 7. } }. + +size := 50. + +c := RSCompositeChart new. +p1 := RSBoxPlot new. +p1 y: y. +p1 notch: true. +p1 barSize: size. +p1 barOffset: size * -0.55. +p2 := RSBoxPlot new y: y. +p2 barSize: size. +p2 barOffset: size * 0.55. +c add: p1; add: p2. +c open +``` +" +Class { + #name : 'RSBoxPlot', + #superclass : 'RSAbstractBandPlot', + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'instance creation' } +RSBoxPlot class >> data: aCollection [ + | boxPlot | + boxPlot := self new. + boxPlot data: aCollection. + ^ boxPlot +] + +{ #category : 'examples' } +RSBoxPlot class >> example01BoxPlot [ + | boxPlot data | + data := { 1. 2. 3. 4. 5. } . + boxPlot := self data: data. + ^ boxPlot open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> example01Days [ + | chart p1 p2 p3 y1 y2 y3 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + + y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + + p1 := self data: y1. "blue" + p2 := self data: y2. "sky blue" + p3 := self data: y3. "orange" + chart := p1 + p2 + p3. + chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. + ^ chart open +] + +{ #category : 'examples' } +RSBoxPlot class >> example02BoxPlotOfMultipleDatasets [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. }. + { 1. 10. 10. 10. 10. 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + ^ boxPlot open +] + +{ #category : 'examples' } +RSBoxPlot class >> example03BoxWithNotch [ + | p1 y1 | + + "y1 := { { 12. 12. 13. 14. 15. 24. } }." + y1 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + p1 := self data: y1. "blue" + p1 notch: true. + ^ p1 open +] + +{ #category : 'examples' } +RSBoxPlot class >> example04BoxPlotHorizontal [ + | boxPlot data | + data := { 1. 2. 3. 4. 5. } . + boxPlot := self data: data. + boxPlot horizontal. + ^ boxPlot open +] + +{ #category : 'examples' } +RSBoxPlot class >> example05BoxPlotsHorizontal [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot horizontal. + ^ boxPlot open +] + +{ #category : 'examples' } +RSBoxPlot class >> example05BoxPlotsHorizontalShowBands [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot horizontal. + boxPlot showBands. + ^ boxPlot open +] + +{ #category : 'examples' } +RSBoxPlot class >> example06Days [ + | chart p1 p2 p3 y1 y2 y3 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + + y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + + p1 := self data: y1. "blue" + p2 := self data: y2. "sky blue" + p3 := self data: y3. "orange" + chart := p1 + p2 + p3. + chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. + ^ chart open +] + +{ #category : 'examples' } +RSBoxPlot class >> example06DaysHorizontal [ + | chart p1 p2 p3 y1 y2 y3 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + + y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + + p1 := self data: y1. "blue" + p2 := self data: y2. "sky blue" + p3 := self data: y3. "orange" + chart := p1 + p2 + p3. + chart horizontal. + chart yTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. + ^ chart open +] + +{ #category : 'examples' } +RSBoxPlot class >> example06DaysShowBands [ + | chart p1 p2 p3 y1 y2 y3 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + + y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + + p1 := self data: y1. "blue" + p1 showBands. + p2 := self data: y2. "sky blue" + p2 showBands. + p3 := self data: y3. "orange" + p3 showBands. + chart := p1 + p2 + p3. + chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. + ^ chart open +] + +{ #category : 'examples' } +RSBoxPlot class >> example07BoxPlotGroupsHorizontal [ + | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | + "TO DO" + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot1 horizontal. + boxPlot2 := self data: data2. + boxPlot2 horizontal. + boxPlot3 := self data: data3. + boxPlot3 horizontal. + boxPlot := boxPlot1 + boxPlot2 + boxPlot3. + ^ boxPlot open +] + +{ #category : 'examples' } +RSBoxPlot class >> example08Positions [ + | boxPlot1 data1 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. }. + { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . }. + boxPlot1 := self data: data1. + boxPlot1 positions: { 1. 6. 13. 15.}. + boxPlot1 showBands. + ^ boxPlot1 open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotChangeBoxWidth [ + | boxPlot1 data1 | + data1 := { 12. 12. 13. 14. 15. 24. }. + boxPlot1 := self data: data1. + boxPlot1 bandsWidth: 50. + boxPlot1 xlabel: 'x axis'. + boxPlot1 ylabel: 'y axis'. + ^ boxPlot1 open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotChangeSpaceBetweenBoxes [ + | clusterChart boxPlot1 boxPlot2 data1 data2 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + clusterChart := boxPlot1 + boxPlot2. + + "bandsMargin value between 0 and 1" + clusterChart bandsMargin: 0.5. + + ^ clusterChart open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotChangeSpaceBetweenClusters [ + | clusterChart boxPlot1 boxPlot2 data1 data2 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + clusterChart := boxPlot1 + boxPlot2. + + "clustersMargin value between 0 and 1" + clusterChart clustersMargin: 0.5. + + ^ clusterChart open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotGroups [ + | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot3 := self data: data3. + boxPlot := boxPlot1 + boxPlot2 + boxPlot3. + ^ boxPlot open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotGroupsCustomColorPalette [ + | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot3 := self data: data3. + boxPlot := boxPlot1 + boxPlot2 + boxPlot3. + boxPlot colors: RSColorPalette qualitative accent8. + ^ boxPlot open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotGroupsEven [ + | boxPlot boxPlot1 boxPlot2 data1 data2 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot := boxPlot1 + boxPlot2. + ^ boxPlot open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotGroupsEven4 [ + | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 boxPlot4 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot3 := self data: data2. + boxPlot4 := self data: data2. + boxPlot := boxPlot1 + boxPlot2 + boxPlot3 + boxPlot4. + ^ boxPlot open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotGroupsOdd [ + | boxPlot boxPlot1 boxPlot2 boxPlot3 data1 data2 data3 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot3 := self data: data3. + boxPlot := boxPlot1 + boxPlot2 + boxPlot3. + ^ boxPlot open +] + +{ #category : 'examples - cluster' } +RSBoxPlot class >> exampleBoxPlotGroupsOdd5 [ + | boxPlot boxPlot1 boxPlot2 boxPlot3 boxPlot4 boxPlot5 data1 data2 data3 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + data3 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot3 := self data: data3. + boxPlot4 := self data: data3. + boxPlot5 := self data: data3. + boxPlot := boxPlot1 + boxPlot2 + boxPlot3 + boxPlot4 + boxPlot5. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyle [ + | boxPlot data paint | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + paint := LinearGradientPaint fromArray: { 0 -> Color gray. 1 -> Color red }. + paint start: 0@ -50. + paint stop: 0@ 50. + boxPlot := self data: data. + boxPlot notch: true. + boxPlot boxShapes do: [ :bs | bs box cornerRadii: 2 ]. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleBoxFillColor [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot color: Color red translucent. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleBoxFillColorsCollection [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot colors: {Color red. Color green. Color blue.}. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleMedianLine [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot medianLines do: [:ml | + ml dashArray: {4.}. + ml color: Color red. + ]. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleMedianLineColor [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot medianLines color: Color red. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleOneMedianLineColor [ + | boxPlot data | + data := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot := self data: data. + boxPlot medianLines last color: Color red. + (boxPlot medianLines at: 2) color: Color white. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleOutlierShape [ + | boxPlot1 data1 | + data1 := { { 12. 12. 13. 14. 15. 24. } . + { 12. 12. 13. 14. 15. 24. } . + { 12. 12. 13. 14. 15. 24. } }. + boxPlot1 := self data: data1. + boxPlot1 outlierMarker: '+'. + "boxPlot1 boxShapes first outlier: (RSShapeFactory circle size: 10; color: Color red)." + "boxPlot1 outlier: (RSShapeFactory circle size: 10; color: Color red)." + ^ boxPlot1 open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleOutliersColor [ + | boxPlot boxPlot1 boxPlot2 data1 data2 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot1 outliers color: Color red. + boxPlot2 := self data: data2. + boxPlot2 outliers color: Color purple. + boxPlot := boxPlot1 + boxPlot2. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleBoxPlotStyleWhiskerFormat [ + | boxPlot boxPlot1 data1 data2 boxPlot2 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + data2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + boxPlot1 := self data: data1. + boxPlot2 := self data: data2. + boxPlot1 whiskerFormat: '--'. + boxPlot2 whiskers do: [ :whiskerLine | whiskerLine format: '.']. + boxPlot := boxPlot1 + boxPlot2. + ^ boxPlot open +] + +{ #category : 'examples - style' } +RSBoxPlot class >> exampleSetYTicksLabels [ + | p1 y1 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + p1 := self data: y1. "blue" + p1 xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3'}. + ^ p1 open +] + +{ #category : 'accessing' } +RSBoxPlot >> boxShapes [ + ^ bandPlotShapes +] + +{ #category : 'rendering' } +RSBoxPlot >> boxShapes: collectionOfRSBoxPlotShape [ + bandPlotShapes := collectionOfRSBoxPlotShape +] + +{ #category : 'rendering' } +RSBoxPlot >> boxes [ + ^ RSGroup new addAll: (bandPlotShapes collect: [ :boxShape | boxShape box ]); yourself +] + +{ #category : 'public' } +RSBoxPlot >> buildChart [ + chart add: self. + chart + extent: 360 @ 300; + padding: 15. + ^ chart +] + +{ #category : 'testing' } +RSBoxPlot >> canHandleCluster [ + ^ true +] + +{ #category : 'accessing - computed' } +RSBoxPlot >> color: aColor [ + bandPlotShapes do: [ :boxShape | boxShape color: aColor ] +] + +{ #category : 'accessing' } +RSBoxPlot >> colors: collectionOfColors [ + bandPlotShapes with: collectionOfColors do: [ :bandShape :color | bandShape color: color ] +] + +{ #category : 'private' } +RSBoxPlot >> computeBoxGraphicsCenters [ + | lastBoxCenter | + lastBoxCenter := 1. + self boxShapes: (bandPlotShapes collect: [ :boxGraph | + boxGraph graphCenter: lastBoxCenter "+groupOffset". + "lastBoxCenter := lastBoxCenter + self defaultOffset". + boxGraph. + ]) +] + +{ #category : 'accessing' } +RSBoxPlot >> confidenceInterval [ + ^ self confidenceIntervals first +] + +{ #category : 'accessing' } +RSBoxPlot >> confidenceIntervals [ + ^ bandPlotShapes collect: [ :boxShape | boxShape confidenceInterval ] +] + +{ #category : 'accessing' } +RSBoxPlot >> confidencePercentage [ + ^ bandPlotShapes collect: [ :boxShape | boxShape confidencePercentage ] +] + +{ #category : 'accessing' } +RSBoxPlot >> confidencePercentage: aPercentage [ + bandPlotShapes do: [ :boxShape | boxShape confidencePercentage: aPercentage ] +] + +{ #category : 'accessing' } +RSBoxPlot >> createdShapes [ + ^ { self boxShapes. } +] + +{ #category : 'rendering' } +RSBoxPlot >> data: aCollection [ + | collectionOfDatasets | + + "if is not a collection of collections. transform in a collection of collections" + collectionOfDatasets := aCollection first isCollection + ifFalse: [ { aCollection } ] + ifTrue: [ aCollection ]. + self boxShapes: (collectionOfDatasets collect: [ :dataSet | RSBoxPlotShape data: dataSet.]). + self computeBoxGraphicsCenters +] + +{ #category : 'accessing - defaults' } +RSBoxPlot >> defaultShape [ + ^ RSPolygon new noPaint +] + +{ #category : 'initialization' } +RSBoxPlot >> initialize [ + super initialize. + offset := 0. + horizontal := false +] + +{ #category : 'rendering' } +RSBoxPlot >> iqr [ + ^ self iqrs first +] + +{ #category : 'rendering' } +RSBoxPlot >> iqrs [ + ^ bandPlotShapes collect: [ :box | box iqr ] +] + +{ #category : 'testing' } +RSBoxPlot >> isBoxPlot [ + ^ true +] + +{ #category : 'rendering' } +RSBoxPlot >> median [ + ^ self medians first +] + +{ #category : 'accessing' } +RSBoxPlot >> medianConfidenceInterval [ + ^ self medianConfidenceIntervals first +] + +{ #category : 'accessing' } +RSBoxPlot >> medianConfidenceIntervals [ + ^ bandPlotShapes collect: [ :boxShape | boxShape medianConfidenceInterval ] +] + +{ #category : 'accessing' } +RSBoxPlot >> medianLines [ + ^ RSGroup new addAll: (bandPlotShapes collect: [ :box | box medianLine ]); yourself +] + +{ #category : 'rendering' } +RSBoxPlot >> medians [ + ^ bandPlotShapes collect: [ :box | box median ] +] + +{ #category : 'accessing' } +RSBoxPlot >> notch: aBool [ + bandPlotShapes do: [ :boxShape | boxShape notch: aBool ]. +] + +{ #category : 'rendering' } +RSBoxPlot >> numberOfBoxes [ + ^ self boxShapes size +] + +{ #category : 'rendering' } +RSBoxPlot >> outlier: markerShape [ + bandPlotShapes do: [ :box | box outlier: markerShape ] +] + +{ #category : 'rendering' } +RSBoxPlot >> outlierMarker: markerShapeString [ + bandPlotShapes do: [ :box | box outlierMarker: markerShapeString ] +] + +{ #category : 'rendering' } +RSBoxPlot >> outliers [ + ^ RSGroup new addAll: (bandPlotShapes collect: [ :box | box outliers ]); yourself +] + +{ #category : 'accessing' } +RSBoxPlot >> quartiles [ + | quartilesArray | + quartilesArray := bandPlotShapes collect: [ :boxShape | boxShape quartiles ]. + bandPlotShapes size = 1 ifTrue: [ ^quartilesArray first ] ifFalse: [ quartilesArray ] +] + +{ #category : 'rendering' } +RSBoxPlot >> renderIn: canvas [ + super renderIn: canvas. + self computeBandsOffset. + self computeBandsWidth. + bandPlotShapes doWithIndex: [ :aRSBoxPlotShape :idx | + aRSBoxPlotShape color ifNil: [ aRSBoxPlotShape color: self computeColor. ]. + aRSBoxPlotShape bandScale: bandScale. + aRSBoxPlotShape dataScale: dataScale. + aRSBoxPlotShape renderIn: canvas ] +] + +{ #category : 'rendering' } +RSBoxPlot >> whiskerFormat: aString [ + bandPlotShapes do: [ :box | box whiskerFormat: aString ] +] + +{ #category : 'accessing' } +RSBoxPlot >> whiskers [ + ^ RSGroup new addAll: (bandPlotShapes collect: [ :box | box whiskers ]); yourself +] + +{ #category : 'public' } +RSBoxPlot >> y: aCollection [ + self data: aCollection +] diff --git a/src/Roassal-Chart/RSBoxPlotShape.class.st b/src/Roassal-Chart/RSBoxPlotShape.class.st index d4ee248e..528a988a 100644 --- a/src/Roassal-Chart/RSBoxPlotShape.class.st +++ b/src/Roassal-Chart/RSBoxPlotShape.class.st @@ -1,441 +1,441 @@ -" -`RSBoxPlotShape` is a visual representation of the distribution of one dataset. It provides a visual summary of the data's spread, central tendency, and the presence of outliers. -- The box represents the interquartile range (IQR) of the data, which is the middle 50% of the dataset. It spans from the first quartile (Q1) to the third quartile (Q3). -- The central line marks the median value. -- The whiskers extend from the edges of the box and indicate the data range. The upper whisker extends to the maximum value within a certain limit (by default, 1.5 times the IQR), and the lower whisker extends to the minimum value within the same limit. -- The outliers are data points that fall outside the whiskers. - -**Responsibility:** -- This class plots one box and their corresponding whiskers and outliers given a dataset. -- It provides options to customize the box. -- Note: the data will be sorted by the class. - -**Collaborators:** -- **`RSStatisticalMeasures`:** The instance variable `statisticalMeasures` is an object which given a dataset store the data sorted, calculates and returns the statistical measures (median, mean, quartiles, etc.). - -**Public API and Key Messages** -- `TO DO` - -**Instance Variables:** -- `bandWidth`: a `Number` expressed in pixels (scaled) that determine the box width. -- `bandOffset`: a `Number` expressed in pixels (scaled) that determine the offset of the box in the plot. - -**Example:** -Instance creation -```Smalltalk -| boxPlotShape data | -data := { 1. 2. 3. 4. 5. } . -boxPlotShape := RSBoxPlotShape data: data. -``` -" -Class { - #name : 'RSBoxPlotShape', - #superclass : 'RSAbstractBandPlotShape', - #instVars : [ - 'statisticalMeasures', - 'box', - 'medianLine', - 'outliers', - 'graphCenter', - 'outlier', - 'upperWhisker', - 'lowerWhisker', - 'whisker', - 'medianConfidencePercentage', - 'notched', - 'notchWidthPercentage' - ], - #category : 'Roassal-Chart-Plots', - #package : 'Roassal-Chart', - #tag : 'Plots' -} - -{ #category : 'accessing' } -RSBoxPlotShape class >> data: collectionOfData1D [ - | boxGraph | - boxGraph := self new. - boxGraph data: collectionOfData1D. - ^ boxGraph -] - -{ #category : 'accessing' } -RSBoxPlotShape class >> data: collectionOfData1D scales: collectionOfNSScales [ - | boxGraph | - boxGraph := self data: collectionOfData1D. - boxGraph scales: collectionOfNSScales. - ^ boxGraph -] - -{ #category : 'accessing' } -RSBoxPlotShape >> box [ - ^ box -] - -{ #category : 'accessing' } -RSBoxPlotShape >> boxWidth [ - ^ bandWidth -] - -{ #category : 'accessing' } -RSBoxPlotShape >> center [ - ^ graphCenter -] - -{ #category : 'accessing' } -RSBoxPlotShape >> confidenceInterval [ - ^ statisticalMeasures confidenceInterval -] - -{ #category : 'accessing' } -RSBoxPlotShape >> confidencePercentage [ - ^ medianConfidencePercentage -] - -{ #category : 'accessing' } -RSBoxPlotShape >> confidencePercentage: aNumber [ - medianConfidencePercentage := aNumber -] - -{ #category : 'shapes' } -RSBoxPlotShape >> createBoxShape [ - | boxPoints q025 q075 quartiles median medianConfidenceInterval medianCIMax medianCIMin notchWidth | - quartiles := statisticalMeasures quartiles. - q025 := quartiles at: 1. - q075 := quartiles at: 3. - - boxPoints := { - (graphCenter-(bandWidth/2)) @ (dataScale scale: q025)."bottom left" - (graphCenter+(bandWidth/2)) @ (dataScale scale: q025)."bottom right" - } asOrderedCollection. - - notchWidth := bandWidth * notchWidthPercentage. - median := quartiles at: 2. - medianConfidenceInterval := self medianConfidenceInterval. - medianCIMax := medianConfidenceInterval max. - medianCIMin := medianConfidenceInterval min. - - notched ifTrue: [ - boxPoints addAll: { - (graphCenter+(bandWidth/2))@(dataScale scale: medianCIMin). - (graphCenter+(notchWidth/2))@(dataScale scale: median). - (graphCenter+(bandWidth/2))@(dataScale scale: medianCIMax).} - ]. - - boxPoints addAll: { - (graphCenter+(bandWidth/2)) @ (dataScale scale: q075)."top right" - (graphCenter-(bandWidth/2)) @ (dataScale scale: q075)."top left" }. - - notched ifTrue: [ - boxPoints addAll: { - (graphCenter-(bandWidth/2))@(dataScale scale: medianCIMax). - (graphCenter-(notchWidth/2))@(dataScale scale: median). - (graphCenter-(bandWidth/2))@(dataScale scale: medianCIMin). } - ]. - - box points: boxPoints -] - -{ #category : 'shapes' } -RSBoxPlotShape >> createMedianLine [ - | q050 medianLinePoints lineWidth | - q050 := statisticalMeasures quartiles second. - "graphCenter was already computed in shapesWithCenterAt" - lineWidth := bandWidth * notchWidthPercentage. - - medianLinePoints := { - (graphCenter-(lineWidth/2))@ (dataScale scale: q050). - (graphCenter+(lineWidth/2))@ (dataScale scale: q050). }. - medianLine := medianLine controlPoints: medianLinePoints -] - -{ #category : 'shapes' } -RSBoxPlotShape >> createOutliersShapes [ - | outliersPoints outlierDefaultSize| - "graphCenter was already computed in shapesWithCenterAt" - outliersPoints := statisticalMeasures outliers collect: [ :y | graphCenter@ (dataScale scale: y) ]. - outlierDefaultSize := (bandWidth * 0.15) abs. - outlier extent x isZero ifTrue: [ outlier size: outlierDefaultSize ]. - outliers := outliersPoints collect: [ :out | - | e | - e := outlier copy. - e translateTo: out. - ] as: RSGroup -] - -{ #category : 'shapes' } -RSBoxPlotShape >> createShapesAndLines [ - | shapes | - self graphCenter: bandOffset. - notched ifTrue: [ notchWidthPercentage := 0.5 ]. - self createWhiskerLines. - self createMedianLine. - self createBoxShape. - box color: color. - self createOutliersShapes. - shapes := OrderedCollection withAll: { - upperWhisker copy. - lowerWhisker copy. - box copy. - medianLine copy. }. - shapes addAll: outliers. - ^ shapes -] - -{ #category : 'shapes' } -RSBoxPlotShape >> createWhiskerLines [ - | upperLimit lowerLimit q1 q3 upperWhiskerPoints lowerWhiskerPoints | - upperLimit := statisticalMeasures upperLimit. - lowerLimit := statisticalMeasures lowerLimit. - q1 := statisticalMeasures quartiles first. - q3 := statisticalMeasures quartiles third. - "graphCenter was already computed in shapesWithCenterAt" - upperWhiskerPoints := { graphCenter@(dataScale scale: q3). graphCenter@ (dataScale scale: upperLimit). }. - lowerWhiskerPoints := { graphCenter@(dataScale scale: q1). graphCenter@ (dataScale scale: lowerLimit). }. - upperWhisker := whisker copy. - lowerWhisker := whisker copy. - upperWhisker controlPoints: upperWhiskerPoints. - lowerWhisker controlPoints: lowerWhiskerPoints -] - -{ #category : 'accessing' } -RSBoxPlotShape >> data [ - ^ self statisticalMeasures data -] - -{ #category : 'accessing' } -RSBoxPlotShape >> data: collectionOfData1D [ - self statisticalMeasures: (RSStatisticalMeasures data: collectionOfData1D) -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultBandPadding [ - ^ 0.2 -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultBox [ - ^ RSPolygon new - color: color; - border: (RSBorder new color: Color black; joinRound) -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultBoxWidth [ - ^ 0.7 -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultGraphCenter [ - ^ 1 -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultMedianConfidencePercentage [ - ^ 95 -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultMedianLine [ - ^ RSLine new - width: 2; - color: Color black -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultNotched [ - ^ false -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultOutlier [ - ^ RSEllipse new - radius: 4; - color: Color black; - size: 0 -] - -{ #category : 'defaults' } -RSBoxPlotShape >> defaultWhisker [ - ^ RSLine new - width: 1; - color: Color black -] - -{ #category : 'accessing' } -RSBoxPlotShape >> graphCenter [ - ^ graphCenter -] - -{ #category : 'accessing' } -RSBoxPlotShape >> graphCenter: aNumber [ - graphCenter := aNumber -] - -{ #category : 'defaults' } -RSBoxPlotShape >> hasOutliers [ - ^ self outlierValues isNotEmpty -] - -{ #category : 'initialization' } -RSBoxPlotShape >> initialize [ - super initialize. - graphCenter := self defaultGraphCenter. - whisker := self defaultWhisker. - upperWhisker := self defaultWhisker. - lowerWhisker := self defaultWhisker. - medianLine := self defaultMedianLine. - box := self defaultBox. - outlier := self defaultOutlier. - shouldShowBand := self defaultShouldShowBand. - medianConfidencePercentage := self defaultMedianConfidencePercentage. - notched := self defaultNotched. - notchWidthPercentage := 1. - horizontal := false -] - -{ #category : 'accessing' } -RSBoxPlotShape >> iqr [ - ^ statisticalMeasures iqr -] - -{ #category : 'accessing' } -RSBoxPlotShape >> lowerLimit [ - ^ statisticalMeasures lowerLimit -] - -{ #category : 'accessing' } -RSBoxPlotShape >> lowerWhisker [ - ^ lowerWhisker -] - -{ #category : 'rendering' } -RSBoxPlotShape >> maxDataValue [ - | maxValue | - maxValue := self upperLimit. - notched ifTrue: [ maxValue := maxValue max: self medianConfidenceInterval max ]. - self hasOutliers - ifTrue: [ maxValue := maxValue max: (self outlierValues max) ]. - ^ maxValue -] - -{ #category : 'accessing' } -RSBoxPlotShape >> median [ - ^ statisticalMeasures median -] - -{ #category : 'accessing' } -RSBoxPlotShape >> medianConfidenceInterval [ - ^ statisticalMeasures medianConfidenceInterval -] - -{ #category : 'accessing' } -RSBoxPlotShape >> medianConfidencePercentage: aNumber [ - medianConfidencePercentage := aNumber -] - -{ #category : 'accessing' } -RSBoxPlotShape >> medianLine [ - ^ medianLine -] - -{ #category : 'accessing' } -RSBoxPlotShape >> medianLine: aRSLineModel [ - medianLine := aRSLineModel copy -] - -{ #category : 'rendering' } -RSBoxPlotShape >> minDataValue [ - | minValue | - minValue := self lowerLimit. - notched ifTrue: [ minValue := minValue min: (self medianConfidenceInterval min) ]. - self hasOutliers - ifTrue: [ minValue := minValue min: (self outlierValues min) ]. - ^ minValue -] - -{ #category : 'accessing' } -RSBoxPlotShape >> notch: aBoolean [ - notched := aBoolean -] - -{ #category : 'accessing' } -RSBoxPlotShape >> outlier: markerShape [ - outlier := markerShape -] - -{ #category : 'accessing' } -RSBoxPlotShape >> outlierMarker: markerShapeString [ - outlier := (RSShapeFactory shapeFromString: markerShapeString) color: Color black -] - -{ #category : 'accessing' } -RSBoxPlotShape >> outlierSize: aDimensionInPixels [ - outlier size: aDimensionInPixels -] - -{ #category : 'accessing' } -RSBoxPlotShape >> outlierValues [ - ^ statisticalMeasures outliers -] - -{ #category : 'accessing' } -RSBoxPlotShape >> outliers [ - ^ outlier -] - -{ #category : 'accessing' } -RSBoxPlotShape >> outliers: aRSGroupOfRSElipses [ - outliers := aRSGroupOfRSElipses -] - -{ #category : 'accessing' } -RSBoxPlotShape >> quartiles [ - ^ statisticalMeasures quartiles -] - -{ #category : 'rendering' } -RSBoxPlotShape >> renderIn: canvas [ - self addChildrenToComposite. - canvas add: self -] - -{ #category : 'accessing' } -RSBoxPlotShape >> scalePoint: aPoint [ - ^ (bandScale scale: aPoint x) @ (dataScale scale: aPoint y) -] - -{ #category : 'accessing' } -RSBoxPlotShape >> scales: collectionOfNSScales [ - self bandScale: collectionOfNSScales first. - self dataScale: collectionOfNSScales second -] - -{ #category : 'accessing' } -RSBoxPlotShape >> statisticalMeasures [ - ^ statisticalMeasures -] - -{ #category : 'accessing' } -RSBoxPlotShape >> statisticalMeasures: aRSStatisticalMeasures [ - statisticalMeasures := aRSStatisticalMeasures -] - -{ #category : 'accessing' } -RSBoxPlotShape >> upperLimit [ - ^ statisticalMeasures upperLimit -] - -{ #category : 'accessing' } -RSBoxPlotShape >> upperWhisker [ - ^ upperWhisker -] - -{ #category : 'public' } -RSBoxPlotShape >> whiskerFormat: aString [ - whisker format: aString -] - -{ #category : 'accessing' } -RSBoxPlotShape >> whiskers [ - ^ whisker -] +" +`RSBoxPlotShape` is a visual representation of the distribution of one dataset. It provides a visual summary of the data's spread, central tendency, and the presence of outliers. +- The box represents the interquartile range (IQR) of the data, which is the middle 50% of the dataset. It spans from the first quartile (Q1) to the third quartile (Q3). +- The central line marks the median value. +- The whiskers extend from the edges of the box and indicate the data range. The upper whisker extends to the maximum value within a certain limit (by default, 1.5 times the IQR), and the lower whisker extends to the minimum value within the same limit. +- The outliers are data points that fall outside the whiskers. + +**Responsibility:** +- This class plots one box and their corresponding whiskers and outliers given a dataset. +- It provides options to customize the box. +- Note: the data will be sorted by the class. + +**Collaborators:** +- **`RSStatisticalMeasures`:** The instance variable `statisticalMeasures` is an object which given a dataset store the data sorted, calculates and returns the statistical measures (median, mean, quartiles, etc.). + +**Public API and Key Messages** +- `TO DO` + +**Instance Variables:** +- `bandWidth`: a `Number` expressed in pixels (scaled) that determine the box width. +- `bandOffset`: a `Number` expressed in pixels (scaled) that determine the offset of the box in the plot. + +**Example:** +Instance creation +```Smalltalk +| boxPlotShape data | +data := { 1. 2. 3. 4. 5. } . +boxPlotShape := RSBoxPlotShape data: data. +``` +" +Class { + #name : 'RSBoxPlotShape', + #superclass : 'RSAbstractBandPlotShape', + #instVars : [ + 'statisticalMeasures', + 'box', + 'medianLine', + 'outliers', + 'graphCenter', + 'outlier', + 'upperWhisker', + 'lowerWhisker', + 'whisker', + 'medianConfidencePercentage', + 'notched', + 'notchWidthPercentage' + ], + #category : 'Roassal-Chart-Plots', + #package : 'Roassal-Chart', + #tag : 'Plots' +} + +{ #category : 'accessing' } +RSBoxPlotShape class >> data: collectionOfData1D [ + | boxGraph | + boxGraph := self new. + boxGraph data: collectionOfData1D. + ^ boxGraph +] + +{ #category : 'accessing' } +RSBoxPlotShape class >> data: collectionOfData1D scales: collectionOfNSScales [ + | boxGraph | + boxGraph := self data: collectionOfData1D. + boxGraph scales: collectionOfNSScales. + ^ boxGraph +] + +{ #category : 'accessing' } +RSBoxPlotShape >> box [ + ^ box +] + +{ #category : 'accessing' } +RSBoxPlotShape >> boxWidth [ + ^ bandWidth +] + +{ #category : 'accessing' } +RSBoxPlotShape >> center [ + ^ graphCenter +] + +{ #category : 'accessing' } +RSBoxPlotShape >> confidenceInterval [ + ^ statisticalMeasures confidenceInterval +] + +{ #category : 'accessing' } +RSBoxPlotShape >> confidencePercentage [ + ^ medianConfidencePercentage +] + +{ #category : 'accessing' } +RSBoxPlotShape >> confidencePercentage: aNumber [ + medianConfidencePercentage := aNumber +] + +{ #category : 'shapes' } +RSBoxPlotShape >> createBoxShape [ + | boxPoints q025 q075 quartiles median medianConfidenceInterval medianCIMax medianCIMin notchWidth | + quartiles := statisticalMeasures quartiles. + q025 := quartiles at: 1. + q075 := quartiles at: 3. + + boxPoints := { + (graphCenter-(bandWidth/2)) @ (dataScale scale: q025)."bottom left" + (graphCenter+(bandWidth/2)) @ (dataScale scale: q025)."bottom right" + } asOrderedCollection. + + notchWidth := bandWidth * notchWidthPercentage. + median := quartiles at: 2. + medianConfidenceInterval := self medianConfidenceInterval. + medianCIMax := medianConfidenceInterval max. + medianCIMin := medianConfidenceInterval min. + + notched ifTrue: [ + boxPoints addAll: { + (graphCenter+(bandWidth/2))@(dataScale scale: medianCIMin). + (graphCenter+(notchWidth/2))@(dataScale scale: median). + (graphCenter+(bandWidth/2))@(dataScale scale: medianCIMax).} + ]. + + boxPoints addAll: { + (graphCenter+(bandWidth/2)) @ (dataScale scale: q075)."top right" + (graphCenter-(bandWidth/2)) @ (dataScale scale: q075)."top left" }. + + notched ifTrue: [ + boxPoints addAll: { + (graphCenter-(bandWidth/2))@(dataScale scale: medianCIMax). + (graphCenter-(notchWidth/2))@(dataScale scale: median). + (graphCenter-(bandWidth/2))@(dataScale scale: medianCIMin). } + ]. + + box points: boxPoints +] + +{ #category : 'shapes' } +RSBoxPlotShape >> createMedianLine [ + | q050 medianLinePoints lineWidth | + q050 := statisticalMeasures quartiles second. + "graphCenter was already computed in shapesWithCenterAt" + lineWidth := bandWidth * notchWidthPercentage. + + medianLinePoints := { + (graphCenter-(lineWidth/2))@ (dataScale scale: q050). + (graphCenter+(lineWidth/2))@ (dataScale scale: q050). }. + medianLine := medianLine controlPoints: medianLinePoints +] + +{ #category : 'shapes' } +RSBoxPlotShape >> createOutliersShapes [ + | outliersPoints outlierDefaultSize| + "graphCenter was already computed in shapesWithCenterAt" + outliersPoints := statisticalMeasures outliers collect: [ :y | graphCenter@ (dataScale scale: y) ]. + outlierDefaultSize := (bandWidth * 0.15) abs. + outlier extent x isZero ifTrue: [ outlier size: outlierDefaultSize ]. + outliers := outliersPoints collect: [ :out | + | e | + e := outlier copy. + e translateTo: out. + ] as: RSGroup +] + +{ #category : 'shapes' } +RSBoxPlotShape >> createShapesAndLines [ + | shapes | + self graphCenter: bandOffset. + notched ifTrue: [ notchWidthPercentage := 0.5 ]. + self createWhiskerLines. + self createMedianLine. + self createBoxShape. + box color: color. + self createOutliersShapes. + shapes := OrderedCollection withAll: { + upperWhisker copy. + lowerWhisker copy. + box copy. + medianLine copy. }. + shapes addAll: outliers. + ^ shapes +] + +{ #category : 'shapes' } +RSBoxPlotShape >> createWhiskerLines [ + | upperLimit lowerLimit q1 q3 upperWhiskerPoints lowerWhiskerPoints | + upperLimit := statisticalMeasures upperLimit. + lowerLimit := statisticalMeasures lowerLimit. + q1 := statisticalMeasures quartiles first. + q3 := statisticalMeasures quartiles third. + "graphCenter was already computed in shapesWithCenterAt" + upperWhiskerPoints := { graphCenter@(dataScale scale: q3). graphCenter@ (dataScale scale: upperLimit). }. + lowerWhiskerPoints := { graphCenter@(dataScale scale: q1). graphCenter@ (dataScale scale: lowerLimit). }. + upperWhisker := whisker copy. + lowerWhisker := whisker copy. + upperWhisker controlPoints: upperWhiskerPoints. + lowerWhisker controlPoints: lowerWhiskerPoints +] + +{ #category : 'accessing' } +RSBoxPlotShape >> data [ + ^ self statisticalMeasures data +] + +{ #category : 'accessing' } +RSBoxPlotShape >> data: collectionOfData1D [ + self statisticalMeasures: (RSStatisticalMeasures data: collectionOfData1D) +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultBandPadding [ + ^ 0.2 +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultBox [ + ^ RSPolygon new + color: color; + border: (RSBorder new color: Color black; joinRound) +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultBoxWidth [ + ^ 0.7 +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultGraphCenter [ + ^ 1 +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultMedianConfidencePercentage [ + ^ 95 +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultMedianLine [ + ^ RSLine new + width: 2; + color: Color black +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultNotched [ + ^ false +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultOutlier [ + ^ RSEllipse new + radius: 4; + color: Color black; + size: 0 +] + +{ #category : 'defaults' } +RSBoxPlotShape >> defaultWhisker [ + ^ RSLine new + width: 1; + color: Color black +] + +{ #category : 'accessing' } +RSBoxPlotShape >> graphCenter [ + ^ graphCenter +] + +{ #category : 'accessing' } +RSBoxPlotShape >> graphCenter: aNumber [ + graphCenter := aNumber +] + +{ #category : 'defaults' } +RSBoxPlotShape >> hasOutliers [ + ^ self outlierValues isNotEmpty +] + +{ #category : 'initialization' } +RSBoxPlotShape >> initialize [ + super initialize. + graphCenter := self defaultGraphCenter. + whisker := self defaultWhisker. + upperWhisker := self defaultWhisker. + lowerWhisker := self defaultWhisker. + medianLine := self defaultMedianLine. + box := self defaultBox. + outlier := self defaultOutlier. + shouldShowBand := self defaultShouldShowBand. + medianConfidencePercentage := self defaultMedianConfidencePercentage. + notched := self defaultNotched. + notchWidthPercentage := 1. + horizontal := false +] + +{ #category : 'accessing' } +RSBoxPlotShape >> iqr [ + ^ statisticalMeasures iqr +] + +{ #category : 'accessing' } +RSBoxPlotShape >> lowerLimit [ + ^ statisticalMeasures lowerLimit +] + +{ #category : 'accessing' } +RSBoxPlotShape >> lowerWhisker [ + ^ lowerWhisker +] + +{ #category : 'rendering' } +RSBoxPlotShape >> maxDataValue [ + | maxValue | + maxValue := self upperLimit. + notched ifTrue: [ maxValue := maxValue max: self medianConfidenceInterval max ]. + self hasOutliers + ifTrue: [ maxValue := maxValue max: (self outlierValues max) ]. + ^ maxValue +] + +{ #category : 'accessing' } +RSBoxPlotShape >> median [ + ^ statisticalMeasures median +] + +{ #category : 'accessing' } +RSBoxPlotShape >> medianConfidenceInterval [ + ^ statisticalMeasures medianConfidenceInterval +] + +{ #category : 'accessing' } +RSBoxPlotShape >> medianConfidencePercentage: aNumber [ + medianConfidencePercentage := aNumber +] + +{ #category : 'accessing' } +RSBoxPlotShape >> medianLine [ + ^ medianLine +] + +{ #category : 'accessing' } +RSBoxPlotShape >> medianLine: aRSLineModel [ + medianLine := aRSLineModel copy +] + +{ #category : 'rendering' } +RSBoxPlotShape >> minDataValue [ + | minValue | + minValue := self lowerLimit. + notched ifTrue: [ minValue := minValue min: (self medianConfidenceInterval min) ]. + self hasOutliers + ifTrue: [ minValue := minValue min: (self outlierValues min) ]. + ^ minValue +] + +{ #category : 'accessing' } +RSBoxPlotShape >> notch: aBoolean [ + notched := aBoolean +] + +{ #category : 'accessing' } +RSBoxPlotShape >> outlier: markerShape [ + outlier := markerShape +] + +{ #category : 'accessing' } +RSBoxPlotShape >> outlierMarker: markerShapeString [ + outlier := (RSShapeFactory shapeFromString: markerShapeString) color: Color black +] + +{ #category : 'accessing' } +RSBoxPlotShape >> outlierSize: aDimensionInPixels [ + outlier size: aDimensionInPixels +] + +{ #category : 'accessing' } +RSBoxPlotShape >> outlierValues [ + ^ statisticalMeasures outliers +] + +{ #category : 'accessing' } +RSBoxPlotShape >> outliers [ + ^ outlier +] + +{ #category : 'accessing' } +RSBoxPlotShape >> outliers: aRSGroupOfRSElipses [ + outliers := aRSGroupOfRSElipses +] + +{ #category : 'accessing' } +RSBoxPlotShape >> quartiles [ + ^ statisticalMeasures quartiles +] + +{ #category : 'rendering' } +RSBoxPlotShape >> renderIn: canvas [ + self addChildrenToComposite. + canvas add: self +] + +{ #category : 'accessing' } +RSBoxPlotShape >> scalePoint: aPoint [ + ^ (bandScale scale: aPoint x) @ (dataScale scale: aPoint y) +] + +{ #category : 'accessing' } +RSBoxPlotShape >> scales: collectionOfNSScales [ + self bandScale: collectionOfNSScales first. + self dataScale: collectionOfNSScales second +] + +{ #category : 'accessing' } +RSBoxPlotShape >> statisticalMeasures [ + ^ statisticalMeasures +] + +{ #category : 'accessing' } +RSBoxPlotShape >> statisticalMeasures: aRSStatisticalMeasures [ + statisticalMeasures := aRSStatisticalMeasures +] + +{ #category : 'accessing' } +RSBoxPlotShape >> upperLimit [ + ^ statisticalMeasures upperLimit +] + +{ #category : 'accessing' } +RSBoxPlotShape >> upperWhisker [ + ^ upperWhisker +] + +{ #category : 'public' } +RSBoxPlotShape >> whiskerFormat: aString [ + whisker format: aString +] + +{ #category : 'accessing' } +RSBoxPlotShape >> whiskers [ + ^ whisker +] diff --git a/src/Roassal-Chart/RSChart.class.st b/src/Roassal-Chart/RSChart.class.st index 0f71c4fe..f2c250a7 100644 --- a/src/Roassal-Chart/RSChart.class.st +++ b/src/Roassal-Chart/RSChart.class.st @@ -1,15 +1,15 @@ -" -RSChart is a class that is going to be removed, please use RSCompositeChart -" -Class { - #name : 'RSChart', - #superclass : 'RSCompositeChart', - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'testing' } -RSChart class >> isDeprecated [ - ^ true -] +" +RSChart is a class that is going to be removed, please use RSCompositeChart +" +Class { + #name : 'RSChart', + #superclass : 'RSCompositeChart', + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'testing' } +RSChart class >> isDeprecated [ + ^ true +] diff --git a/src/Roassal-Chart/RSChartExtents.class.st b/src/Roassal-Chart/RSChartExtents.class.st index af47be71..3b53605f 100644 --- a/src/Roassal-Chart/RSChartExtents.class.st +++ b/src/Roassal-Chart/RSChartExtents.class.st @@ -1,82 +1,82 @@ -" -This class provides some extents for RSChart - -- extent: the extent of one chart -- max min: for each axis of this 2d chart -- padding: an internal gap between plots and base rectangle -" -Class { - #name : 'RSChartExtents', - #superclass : 'RSObject', - #instVars : [ - 'extent', - 'maxValueX', - 'maxValueY', - 'minValueX', - 'minValueY', - 'padding' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSChartExtents >> extent [ - ^ extent -] - -{ #category : 'accessing' } -RSChartExtents >> extent: aPoint [ - extent := aPoint -] - -{ #category : 'accessing' } -RSChartExtents >> maxValueX [ - ^ maxValueX -] - -{ #category : 'accessing' } -RSChartExtents >> maxValueX: aNumber [ - maxValueX := aNumber -] - -{ #category : 'accessing' } -RSChartExtents >> maxValueY [ - ^ maxValueY -] - -{ #category : 'accessing' } -RSChartExtents >> maxValueY: aNumber [ - maxValueY := aNumber -] - -{ #category : 'accessing' } -RSChartExtents >> minValueX [ - ^ minValueX -] - -{ #category : 'accessing' } -RSChartExtents >> minValueX: aNumber [ - minValueX := aNumber -] - -{ #category : 'accessing' } -RSChartExtents >> minValueY [ - ^ minValueY -] - -{ #category : 'accessing' } -RSChartExtents >> minValueY: aNumber [ - minValueY := aNumber -] - -{ #category : 'accessing' } -RSChartExtents >> padding [ - ^ padding ifNil: [ padding := 0@0 ] -] - -{ #category : 'accessing' } -RSChartExtents >> padding: aPoint [ - padding := aPoint -] +" +This class provides some extents for RSChart + +- extent: the extent of one chart +- max min: for each axis of this 2d chart +- padding: an internal gap between plots and base rectangle +" +Class { + #name : 'RSChartExtents', + #superclass : 'RSObject', + #instVars : [ + 'extent', + 'maxValueX', + 'maxValueY', + 'minValueX', + 'minValueY', + 'padding' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSChartExtents >> extent [ + ^ extent +] + +{ #category : 'accessing' } +RSChartExtents >> extent: aPoint [ + extent := aPoint +] + +{ #category : 'accessing' } +RSChartExtents >> maxValueX [ + ^ maxValueX +] + +{ #category : 'accessing' } +RSChartExtents >> maxValueX: aNumber [ + maxValueX := aNumber +] + +{ #category : 'accessing' } +RSChartExtents >> maxValueY [ + ^ maxValueY +] + +{ #category : 'accessing' } +RSChartExtents >> maxValueY: aNumber [ + maxValueY := aNumber +] + +{ #category : 'accessing' } +RSChartExtents >> minValueX [ + ^ minValueX +] + +{ #category : 'accessing' } +RSChartExtents >> minValueX: aNumber [ + minValueX := aNumber +] + +{ #category : 'accessing' } +RSChartExtents >> minValueY [ + ^ minValueY +] + +{ #category : 'accessing' } +RSChartExtents >> minValueY: aNumber [ + minValueY := aNumber +] + +{ #category : 'accessing' } +RSChartExtents >> padding [ + ^ padding ifNil: [ padding := 0@0 ] +] + +{ #category : 'accessing' } +RSChartExtents >> padding: aPoint [ + padding := aPoint +] diff --git a/src/Roassal-Chart/RSChartPopupDecoration.class.st b/src/Roassal-Chart/RSChartPopupDecoration.class.st index 903fe4f3..995f9f5b 100644 --- a/src/Roassal-Chart/RSChartPopupDecoration.class.st +++ b/src/Roassal-Chart/RSChartPopupDecoration.class.st @@ -1,93 +1,93 @@ -" -Specific popup, used by RSPopupDecoration -" -Class { - #name : 'RSChartPopupDecoration', - #superclass : 'RSPopup', - #instVars : [ - 'chart', - 'chartPopupBuilder', - 'markersPopupBuilder' - ], - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'accessing' } -RSChartPopupDecoration >> chart [ - ^ chart -] - -{ #category : 'accessing' } -RSChartPopupDecoration >> chart: aRSChart [ - chart := aRSChart -] - -{ #category : 'accessing' } -RSChartPopupDecoration >> chartPopupBuilder [ - ^ chartPopupBuilder -] - -{ #category : 'accessing' } -RSChartPopupDecoration >> chartPopupBuilder: aShapeBuilder [ - chartPopupBuilder := aShapeBuilder -] - -{ #category : 'initialization' } -RSChartPopupDecoration >> initialize [ - super initialize. - "this composite is created once when the mouse enter into the box shape" - self shapeBuilder: [ :obj | RSComposite new ] -] - -{ #category : 'accessing' } -RSChartPopupDecoration >> markersPopupBuilder [ - ^ markersPopupBuilder -] - -{ #category : 'accessing' } -RSChartPopupDecoration >> markersPopupBuilder: aRSAbstractChartPopupBuilder [ - markersPopupBuilder := aRSAbstractChartPopupBuilder -] - -{ #category : 'hooks' } -RSChartPopupDecoration >> releasePopup: popup [ - popup remove. - chart container propertyAt: #popupMarkers ifPresent: [ :old | old remove ] -] - -{ #category : 'hooks' } -RSChartPopupDecoration >> translatePopup: popup event: evt [ - self - updatePopup: popup event: evt; - updateMarkers: evt. - super translatePopup: popup event: evt -] - -{ #category : 'hooks' } -RSChartPopupDecoration >> updateMarkers: evt [ - | markers key container builder | - builder := self markersPopupBuilder. - builder position: evt position. - markers := builder shapeFor: self chart. - container := self chart container canvas. - key := #popupMarkers. - container propertyAt: key ifPresent: [ :old | old remove ]. - container addShape: markers. - container propertyAt: key put: markers. - ^ builder -] - -{ #category : 'hooks' } -RSChartPopupDecoration >> updatePopup: popup event: evt [ - "we recreate the popup content each time, since the popup should show the nodes change" - | builder rect | - popup children do: #remove. "we recreate the popup content each time, since the popup should show the nodes change" - builder := self chartPopupBuilder. - rect := evt shape globalEncompassingRectangle. - builder position: evt position - rect origin. - - popup addShape: (builder shapeFor: self chart). - popup adjustToChildren -] +" +Specific popup, used by RSPopupDecoration +" +Class { + #name : 'RSChartPopupDecoration', + #superclass : 'RSPopup', + #instVars : [ + 'chart', + 'chartPopupBuilder', + 'markersPopupBuilder' + ], + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'accessing' } +RSChartPopupDecoration >> chart [ + ^ chart +] + +{ #category : 'accessing' } +RSChartPopupDecoration >> chart: aRSChart [ + chart := aRSChart +] + +{ #category : 'accessing' } +RSChartPopupDecoration >> chartPopupBuilder [ + ^ chartPopupBuilder +] + +{ #category : 'accessing' } +RSChartPopupDecoration >> chartPopupBuilder: aShapeBuilder [ + chartPopupBuilder := aShapeBuilder +] + +{ #category : 'initialization' } +RSChartPopupDecoration >> initialize [ + super initialize. + "this composite is created once when the mouse enter into the box shape" + self shapeBuilder: [ :obj | RSComposite new ] +] + +{ #category : 'accessing' } +RSChartPopupDecoration >> markersPopupBuilder [ + ^ markersPopupBuilder +] + +{ #category : 'accessing' } +RSChartPopupDecoration >> markersPopupBuilder: aRSAbstractChartPopupBuilder [ + markersPopupBuilder := aRSAbstractChartPopupBuilder +] + +{ #category : 'hooks' } +RSChartPopupDecoration >> releasePopup: popup [ + popup remove. + chart container propertyAt: #popupMarkers ifPresent: [ :old | old remove ] +] + +{ #category : 'hooks' } +RSChartPopupDecoration >> translatePopup: popup event: evt [ + self + updatePopup: popup event: evt; + updateMarkers: evt. + super translatePopup: popup event: evt +] + +{ #category : 'hooks' } +RSChartPopupDecoration >> updateMarkers: evt [ + | markers key container builder | + builder := self markersPopupBuilder. + builder position: evt position. + markers := builder shapeFor: self chart. + container := self chart container canvas. + key := #popupMarkers. + container propertyAt: key ifPresent: [ :old | old remove ]. + container addShape: markers. + container propertyAt: key put: markers. + ^ builder +] + +{ #category : 'hooks' } +RSChartPopupDecoration >> updatePopup: popup event: evt [ + "we recreate the popup content each time, since the popup should show the nodes change" + | builder rect | + popup children do: #remove. "we recreate the popup content each time, since the popup should show the nodes change" + builder := self chartPopupBuilder. + rect := evt shape globalEncompassingRectangle. + builder position: evt position - rect origin. + + popup addShape: (builder shapeFor: self chart). + popup adjustToChildren +] diff --git a/src/Roassal-Chart/RSChartSpineDecoration.class.st b/src/Roassal-Chart/RSChartSpineDecoration.class.st index 072604c5..295e7d32 100644 --- a/src/Roassal-Chart/RSChartSpineDecoration.class.st +++ b/src/Roassal-Chart/RSChartSpineDecoration.class.st @@ -1,61 +1,61 @@ -" -The PLTSpineDecoration class represent the spine of a matplot visualization. It represents the surrounding box that contains plots and scatters. - -The class has the responsibilty to draw a usually black box around a visualization. - -The class collaborates with PLT and PLTTitleDecoration. The PLTTitleDecoration class requires the shape in order to put a title above. - -The method is not meant to be used by a normal user as it represents a functionality to be used directly by PLT, and does not offer much configuration. - - Instance Variables - shape: The box that surrounds the visualization - -" -Class { - #name : 'RSChartSpineDecoration', - #superclass : 'RSAbstractChartDecoration', - #instVars : [ - 'box' - ], - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'accessing' } -RSChartSpineDecoration >> box [ - ^ box -] - -{ #category : 'accessing' } -RSChartSpineDecoration >> createdShapes [ - ^ { box } -] - -{ #category : 'initialization' } -RSChartSpineDecoration >> defaultShape [ - ^ RSBox new - noPaint; - border: (RSBorder new joinMiter); - yourself -] - -{ #category : 'testing' } -RSChartSpineDecoration >> isSpineDecoration [ - ^ true -] - -{ #category : 'rendering' } -RSChartSpineDecoration >> renderIn: canvas [ - - box := self shape copy - borderColor: self styler spineColor; - extent: chart extent. - box translateTo: chart extent x / 2 @ (chart extent y negated / 2). - canvas add: box -] - -{ #category : 'accessing' } -RSChartSpineDecoration >> zeroPoint [ - ^ 0 @ self chart extent y -] +" +The PLTSpineDecoration class represent the spine of a matplot visualization. It represents the surrounding box that contains plots and scatters. + +The class has the responsibilty to draw a usually black box around a visualization. + +The class collaborates with PLT and PLTTitleDecoration. The PLTTitleDecoration class requires the shape in order to put a title above. + +The method is not meant to be used by a normal user as it represents a functionality to be used directly by PLT, and does not offer much configuration. + + Instance Variables + shape: The box that surrounds the visualization + +" +Class { + #name : 'RSChartSpineDecoration', + #superclass : 'RSAbstractChartDecoration', + #instVars : [ + 'box' + ], + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'accessing' } +RSChartSpineDecoration >> box [ + ^ box +] + +{ #category : 'accessing' } +RSChartSpineDecoration >> createdShapes [ + ^ { box } +] + +{ #category : 'initialization' } +RSChartSpineDecoration >> defaultShape [ + ^ RSBox new + noPaint; + border: (RSBorder new joinMiter); + yourself +] + +{ #category : 'testing' } +RSChartSpineDecoration >> isSpineDecoration [ + ^ true +] + +{ #category : 'rendering' } +RSChartSpineDecoration >> renderIn: canvas [ + + box := self shape copy + borderColor: self styler spineColor; + extent: chart extent. + box translateTo: chart extent x / 2 @ (chart extent y negated / 2). + canvas add: box +] + +{ #category : 'accessing' } +RSChartSpineDecoration >> zeroPoint [ + ^ 0 @ self chart extent y +] diff --git a/src/Roassal-Chart/RSChartStyler.class.st b/src/Roassal-Chart/RSChartStyler.class.st index a8928fc5..71d14a5f 100644 --- a/src/Roassal-Chart/RSChartStyler.class.st +++ b/src/Roassal-Chart/RSChartStyler.class.st @@ -1,86 +1,86 @@ -" -A chart styler is responsible to styler a chart and its element, in the future it would be connected to css description -" -Class { - #name : 'RSChartStyler', - #superclass : 'RSObject', - #instVars : [ - 'textColor', - 'textFont', - 'textSize', - 'tickColor', - 'spineColor' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'initialization' } -RSChartStyler >> initialize [ - super initialize. - self - textColor: Color black; - tickColor: Color black; - spineColor: Color black -] - -{ #category : 'accessing' } -RSChartStyler >> spineColor [ - - ^ spineColor -] - -{ #category : 'accessing' } -RSChartStyler >> spineColor: anObject [ - - spineColor := anObject -] - -{ #category : 'accessing' } -RSChartStyler >> textColor [ - - ^ textColor -] - -{ #category : 'accessing' } -RSChartStyler >> textColor: anObject [ - - textColor := anObject -] - -{ #category : 'accessing' } -RSChartStyler >> textFont [ - - ^ textFont -] - -{ #category : 'accessing' } -RSChartStyler >> textFont: anObject [ - - textFont := anObject -] - -{ #category : 'accessing' } -RSChartStyler >> textSize [ - - ^ textSize -] - -{ #category : 'accessing' } -RSChartStyler >> textSize: anObject [ - - textSize := anObject -] - -{ #category : 'accessing' } -RSChartStyler >> tickColor [ - - ^ tickColor -] - -{ #category : 'accessing' } -RSChartStyler >> tickColor: anObject [ - - tickColor := anObject -] +" +A chart styler is responsible to styler a chart and its element, in the future it would be connected to css description +" +Class { + #name : 'RSChartStyler', + #superclass : 'RSObject', + #instVars : [ + 'textColor', + 'textFont', + 'textSize', + 'tickColor', + 'spineColor' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'initialization' } +RSChartStyler >> initialize [ + super initialize. + self + textColor: Color black; + tickColor: Color black; + spineColor: Color black +] + +{ #category : 'accessing' } +RSChartStyler >> spineColor [ + + ^ spineColor +] + +{ #category : 'accessing' } +RSChartStyler >> spineColor: anObject [ + + spineColor := anObject +] + +{ #category : 'accessing' } +RSChartStyler >> textColor [ + + ^ textColor +] + +{ #category : 'accessing' } +RSChartStyler >> textColor: anObject [ + + textColor := anObject +] + +{ #category : 'accessing' } +RSChartStyler >> textFont [ + + ^ textFont +] + +{ #category : 'accessing' } +RSChartStyler >> textFont: anObject [ + + textFont := anObject +] + +{ #category : 'accessing' } +RSChartStyler >> textSize [ + + ^ textSize +] + +{ #category : 'accessing' } +RSChartStyler >> textSize: anObject [ + + textSize := anObject +] + +{ #category : 'accessing' } +RSChartStyler >> tickColor [ + + ^ tickColor +] + +{ #category : 'accessing' } +RSChartStyler >> tickColor: anObject [ + + tickColor := anObject +] diff --git a/src/Roassal-Chart/RSChartTitleDecoration.class.st b/src/Roassal-Chart/RSChartTitleDecoration.class.st index 6d433df8..65166f5b 100644 --- a/src/Roassal-Chart/RSChartTitleDecoration.class.st +++ b/src/Roassal-Chart/RSChartTitleDecoration.class.st @@ -1,46 +1,46 @@ -" - -`RSChartTitleDecoration` is a `RSAbstractChartDecoration` that adds a title to a chart. - -*Responsibility*: To display a title on top of a chart per default - -*Collaborators*: must be added to `RSChart` - -*Example*: -```Smalltalk -p := RSLinePlot new x: (1 to: 200) y: (1 to: 200) sqrt. -p addDecoration: (RSChartTitleDecoration new title: 'Square root'). - -""The line above can also be writen as: -p title: 'Square root'. -"" -p open -``` -" -Class { - #name : 'RSChartTitleDecoration', - #superclass : 'RSAbstractLabelDecoration', - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'rendering' } -RSChartTitleDecoration >> defaultFontSize [ - ^ 15 -] - -{ #category : 'initialization' } -RSChartTitleDecoration >> initialize [ - super initialize. - self fontSize: 15. - location - offset: 0@ -2; - above; center -] - -{ #category : 'initialization' } -RSChartTitleDecoration >> isTitle [ - - ^ true -] +" + +`RSChartTitleDecoration` is a `RSAbstractChartDecoration` that adds a title to a chart. + +*Responsibility*: To display a title on top of a chart per default + +*Collaborators*: must be added to `RSChart` + +*Example*: +```Smalltalk +p := RSLinePlot new x: (1 to: 200) y: (1 to: 200) sqrt. +p addDecoration: (RSChartTitleDecoration new title: 'Square root'). + +""The line above can also be writen as: +p title: 'Square root'. +"" +p open +``` +" +Class { + #name : 'RSChartTitleDecoration', + #superclass : 'RSAbstractLabelDecoration', + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'rendering' } +RSChartTitleDecoration >> defaultFontSize [ + ^ 15 +] + +{ #category : 'initialization' } +RSChartTitleDecoration >> initialize [ + super initialize. + self fontSize: 15. + location + offset: 0@ -2; + above; center +] + +{ #category : 'initialization' } +RSChartTitleDecoration >> isTitle [ + + ^ true +] diff --git a/src/Roassal-Chart/RSClusterChart.class.st b/src/Roassal-Chart/RSClusterChart.class.st index f6d9e81b..47637a33 100644 --- a/src/Roassal-Chart/RSClusterChart.class.st +++ b/src/Roassal-Chart/RSClusterChart.class.st @@ -1,165 +1,165 @@ -Class { - #name : 'RSClusterChart', - #superclass : 'RSCompositeChart', - #instVars : [ - 'innerClusterScale', - 'clustersScale', - 'clustersMargin', - 'bandsMargin', - 'horizontal', - 'positions' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'adding' } -RSClusterChart >> + aRSPlot [ - self add: aRSPlot. - ^ self -] - -{ #category : 'adding' } -RSClusterChart >> add: aPlot [ - horizontal := aPlot isHorizontal. - super add: aPlot. - ^ aPlot -] - -{ #category : 'accessing' } -RSClusterChart >> bandsMargin [ - ^ bandsMargin -] - -{ #category : 'accessing' } -RSClusterChart >> bandsMargin: floatBetween0And1 [ - bandsMargin := floatBetween0And1. - self computePlotsBands -] - -{ #category : 'adding' } -RSClusterChart >> beforeRenderingIn: aChart [ - yScale := clustersScale. - aChart yScale: aChart -] - -{ #category : 'private' } -RSClusterChart >> clusterBandValues [ - positions := OrderedCollection new. - self plots do: [ :plot | - plot computeXAndYValues. - positions addAll: plot positions. - ]. - positions removeDuplicates. - ^ positions -] - -{ #category : 'accessing' } -RSClusterChart >> clustersMargin [ - ^ clustersMargin -] - -{ #category : 'accessing' } -RSClusterChart >> clustersMargin: floatBetween0And1 [ - clustersMargin := floatBetween0And1. - self computePlotsBands -] - -{ #category : 'accessing' } -RSClusterChart >> clustersScale [ - ^ clustersScale -] - -{ #category : 'private' } -RSClusterChart >> computeClustersScale [ - | range | - range := { self padding x. self extent x - self padding x}. - horizontal ifTrue: [ - range := { self padding y negated. (self extent y - self padding y) negated} ]. - clustersScale := NSScale ordinal - domain: (self clusterBandValues); - rangeBands: range padding: clustersMargin. - ^ clustersScale -] - -{ #category : 'private' } -RSClusterChart >> computeInnerClusterScale [ - innerClusterScale := NSScale ordinal - domain: (1 to: self numberOfPlots); - rangeBands: {0. clustersScale rangeBand} padding: bandsMargin. - ^ innerClusterScale -] - -{ #category : 'private' } -RSClusterChart >> computePlotsBands [ - self computeClustersScale. - self computeInnerClusterScale. - self plots doWithIndex: [ :plot :index | - | positionInInnerClusterScale | - positionInInnerClusterScale := innerClusterScale scale: index. - plot bandScale: clustersScale. - plot bandsWidth: innerClusterScale rangeBand. - plot bandsOffset: - positionInInnerClusterScale - (clustersScale rangeBand / 2) ] -] - -{ #category : 'rendering' } -RSClusterChart >> computeTicks [ - | horizontalTick verticalTick | - horizontal - ifFalse: [ - horizontalTick := self horizontalTick. - horizontalTick ifNotNil: [ - horizontalTick isTicksDataNil ifTrue: [ self xTicks: positions labels: positions ] ]. - ] - ifTrue: [ - verticalTick := self verticalTick. - verticalTick ifNotNil: [ - verticalTick isTicksDataNil ifTrue: [ self yTicks: positions labels: positions ] ]. - ] -] - -{ #category : 'defaults' } -RSClusterChart >> defaultBandsMargin [ - ^ 0.2 -] - -{ #category : 'defaults' } -RSClusterChart >> defaultClustersMargin [ - ^ 0.2 -] - -{ #category : 'adding' } -RSClusterChart >> horizontal [ - self plots do: [ :plot | plot horizontal ]. - horizontal := true -] - -{ #category : 'initialization' } -RSClusterChart >> initialize [ - super initialize. - clustersMargin := self defaultClustersMargin. - bandsMargin := self defaultBandsMargin. - horizontal := false -] - -{ #category : 'accessing' } -RSClusterChart >> innerClusterScale [ - ^ innerClusterScale -] - -{ #category : 'accessing' } -RSClusterChart >> maxNumberOfBandsPerPlot [ - ^ (self plots collect: [ :plot | plot numberOfBands ]) max -] - -{ #category : 'rendering' } -RSClusterChart >> renderIn: aCanvas [ - horizontal - ifTrue: [ self extent: (self extent x)@(self extent y * (1+(self plots size * 0.1))). ] - ifFalse: [ self extent: (self extent x * (1+(self plots size * 0.1)))@(self extent y). ]. - self computePlotsBands. - self computeTicks. - ^ super renderIn: aCanvas -] +Class { + #name : 'RSClusterChart', + #superclass : 'RSCompositeChart', + #instVars : [ + 'innerClusterScale', + 'clustersScale', + 'clustersMargin', + 'bandsMargin', + 'horizontal', + 'positions' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'adding' } +RSClusterChart >> + aRSPlot [ + self add: aRSPlot. + ^ self +] + +{ #category : 'adding' } +RSClusterChart >> add: aPlot [ + horizontal := aPlot isHorizontal. + super add: aPlot. + ^ aPlot +] + +{ #category : 'accessing' } +RSClusterChart >> bandsMargin [ + ^ bandsMargin +] + +{ #category : 'accessing' } +RSClusterChart >> bandsMargin: floatBetween0And1 [ + bandsMargin := floatBetween0And1. + self computePlotsBands +] + +{ #category : 'adding' } +RSClusterChart >> beforeRenderingIn: aChart [ + yScale := clustersScale. + aChart yScale: aChart +] + +{ #category : 'private' } +RSClusterChart >> clusterBandValues [ + positions := OrderedCollection new. + self plots do: [ :plot | + plot computeXAndYValues. + positions addAll: plot positions. + ]. + positions removeDuplicates. + ^ positions +] + +{ #category : 'accessing' } +RSClusterChart >> clustersMargin [ + ^ clustersMargin +] + +{ #category : 'accessing' } +RSClusterChart >> clustersMargin: floatBetween0And1 [ + clustersMargin := floatBetween0And1. + self computePlotsBands +] + +{ #category : 'accessing' } +RSClusterChart >> clustersScale [ + ^ clustersScale +] + +{ #category : 'private' } +RSClusterChart >> computeClustersScale [ + | range | + range := { self padding x. self extent x - self padding x}. + horizontal ifTrue: [ + range := { self padding y negated. (self extent y - self padding y) negated} ]. + clustersScale := NSScale ordinal + domain: (self clusterBandValues); + rangeBands: range padding: clustersMargin. + ^ clustersScale +] + +{ #category : 'private' } +RSClusterChart >> computeInnerClusterScale [ + innerClusterScale := NSScale ordinal + domain: (1 to: self numberOfPlots); + rangeBands: {0. clustersScale rangeBand} padding: bandsMargin. + ^ innerClusterScale +] + +{ #category : 'private' } +RSClusterChart >> computePlotsBands [ + self computeClustersScale. + self computeInnerClusterScale. + self plots doWithIndex: [ :plot :index | + | positionInInnerClusterScale | + positionInInnerClusterScale := innerClusterScale scale: index. + plot bandScale: clustersScale. + plot bandsWidth: innerClusterScale rangeBand. + plot bandsOffset: + positionInInnerClusterScale - (clustersScale rangeBand / 2) ] +] + +{ #category : 'rendering' } +RSClusterChart >> computeTicks [ + | horizontalTick verticalTick | + horizontal + ifFalse: [ + horizontalTick := self horizontalTick. + horizontalTick ifNotNil: [ + horizontalTick isTicksDataNil ifTrue: [ self xTicks: positions labels: positions ] ]. + ] + ifTrue: [ + verticalTick := self verticalTick. + verticalTick ifNotNil: [ + verticalTick isTicksDataNil ifTrue: [ self yTicks: positions labels: positions ] ]. + ] +] + +{ #category : 'defaults' } +RSClusterChart >> defaultBandsMargin [ + ^ 0.2 +] + +{ #category : 'defaults' } +RSClusterChart >> defaultClustersMargin [ + ^ 0.2 +] + +{ #category : 'adding' } +RSClusterChart >> horizontal [ + self plots do: [ :plot | plot horizontal ]. + horizontal := true +] + +{ #category : 'initialization' } +RSClusterChart >> initialize [ + super initialize. + clustersMargin := self defaultClustersMargin. + bandsMargin := self defaultBandsMargin. + horizontal := false +] + +{ #category : 'accessing' } +RSClusterChart >> innerClusterScale [ + ^ innerClusterScale +] + +{ #category : 'accessing' } +RSClusterChart >> maxNumberOfBandsPerPlot [ + ^ (self plots collect: [ :plot | plot numberOfBands ]) max +] + +{ #category : 'rendering' } +RSClusterChart >> renderIn: aCanvas [ + horizontal + ifTrue: [ self extent: (self extent x)@(self extent y * (1+(self plots size * 0.1))). ] + ifFalse: [ self extent: (self extent x * (1+(self plots size * 0.1)))@(self extent y). ]. + self computePlotsBands. + self computeTicks. + ^ super renderIn: aCanvas +] diff --git a/src/Roassal-Chart/RSCompositeChart.class.st b/src/Roassal-Chart/RSCompositeChart.class.st index 0cbfd661..c7e0a8cf 100644 --- a/src/Roassal-Chart/RSCompositeChart.class.st +++ b/src/Roassal-Chart/RSCompositeChart.class.st @@ -1,158 +1,158 @@ -" -Please use RSCompositeChart -" -Class { - #name : 'RSCompositeChart', - #superclass : 'RSAbstractChart', - #instVars : [ - 'plots' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'adding' } -RSCompositeChart >> add: aPlot [ - "Add a plot to the chart. - -For example: -```Smalltalk -x := -3.14 to: 3.14 by: 0.1. -y := x sin. -c := RSChart new. -c addPlot: (RSLinePlot new x: x y: y). -c -``` - " - plots add: aPlot. - aPlot chart: self. - aPlot decorations removeAll. - aPlot padding: self padding. - aPlot extent: self extent. - aPlot styler: self styler. - ^ aPlot -] - -{ #category : 'adding' } -RSCompositeChart >> addAll: aCollection [ - - aCollection do: [ :each | self add: each ] -] - -{ #category : 'accessing' } -RSCompositeChart >> chart [ - - ^ self -] - -{ #category : 'initialization' } -RSCompositeChart >> initialize [ - - super initialize. - plots := OrderedCollection new -] - -{ #category : 'accessing - extension' } -RSCompositeChart >> maxChartValueX [ - "if not set before, returns the maximum x value of all the plots" - - ^ self chartExtents maxValueX - ifNil: [ - | res | - self chartExtents maxValueX: - (res := (self plots collect: #maxValueX) max). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'accessing - extension' } -RSCompositeChart >> maxChartValueY [ - "if not set before, returns the maximum y value of all the plots" - - ^ self chartExtents maxValueY - ifNil: [ - | res | - self chartExtents maxValueY: - (res := (self plots collect: #maxValueY) max). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'accessing - extension' } -RSCompositeChart >> minChartValueX [ - "if not set before, returns the minimum x value of all the plots" - - ^ self chartExtents minValueX - ifNil: [ - | res | - self chartExtents minValueX: - (res := (self plots collect: #minValueX) min). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'accessing - extension' } -RSCompositeChart >> minChartValueY [ - "if not set before, returns the minimum y value of all the plots" - - ^ self chartExtents minValueY - ifNil: [ - | res | - self chartExtents minValueY: - (res := (self plots collect: #minValueY) min). - res ] - ifNotNil: [ :res | res ] -] - -{ #category : 'accessing' } -RSCompositeChart >> numberOfPlots [ - "Return the number of plots contained in the chart" - - ^ self plots size -] - -{ #category : 'public' } -RSCompositeChart >> openOnce [ - - self build. - ^ self canvas openOnce -] - -{ #category : 'accessing - extension' } -RSCompositeChart >> padding: aPoint [ - - super padding: aPoint. - plots ifNil: [ ^ self ]. - plots do: [ :plot | plot padding: aPoint ] -] - -{ #category : 'accessing' } -RSCompositeChart >> plots [ - - ^ plots -] - -{ #category : 'rendering' } -RSCompositeChart >> renderIn: aCanvas [ - - decorations , plots do: [ :element | element beforeRenderingIn: self ]. - decorations , plots do: [ :element | element renderIn: aCanvas ]. - shapes := decorations , plots - flatCollect: [ :element | element createdShapes ] - as: RSGroup -] - -{ #category : 'public - scales' } -RSCompositeChart >> xScale: aScale [ - - super xScale: aScale. - plots do: [ :plot | plot xScale: aScale ] -] - -{ #category : 'public - scales' } -RSCompositeChart >> yScale: aScale [ - - super yScale: aScale. - plots do: [ :e | e yScale: aScale ] -] +" +Please use RSCompositeChart +" +Class { + #name : 'RSCompositeChart', + #superclass : 'RSAbstractChart', + #instVars : [ + 'plots' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'adding' } +RSCompositeChart >> add: aPlot [ + "Add a plot to the chart. + +For example: +```Smalltalk +x := -3.14 to: 3.14 by: 0.1. +y := x sin. +c := RSChart new. +c addPlot: (RSLinePlot new x: x y: y). +c +``` + " + plots add: aPlot. + aPlot chart: self. + aPlot decorations removeAll. + aPlot padding: self padding. + aPlot extent: self extent. + aPlot styler: self styler. + ^ aPlot +] + +{ #category : 'adding' } +RSCompositeChart >> addAll: aCollection [ + + aCollection do: [ :each | self add: each ] +] + +{ #category : 'accessing' } +RSCompositeChart >> chart [ + + ^ self +] + +{ #category : 'initialization' } +RSCompositeChart >> initialize [ + + super initialize. + plots := OrderedCollection new +] + +{ #category : 'accessing - extension' } +RSCompositeChart >> maxChartValueX [ + "if not set before, returns the maximum x value of all the plots" + + ^ self chartExtents maxValueX + ifNil: [ + | res | + self chartExtents maxValueX: + (res := (self plots collect: #maxValueX) max). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'accessing - extension' } +RSCompositeChart >> maxChartValueY [ + "if not set before, returns the maximum y value of all the plots" + + ^ self chartExtents maxValueY + ifNil: [ + | res | + self chartExtents maxValueY: + (res := (self plots collect: #maxValueY) max). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'accessing - extension' } +RSCompositeChart >> minChartValueX [ + "if not set before, returns the minimum x value of all the plots" + + ^ self chartExtents minValueX + ifNil: [ + | res | + self chartExtents minValueX: + (res := (self plots collect: #minValueX) min). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'accessing - extension' } +RSCompositeChart >> minChartValueY [ + "if not set before, returns the minimum y value of all the plots" + + ^ self chartExtents minValueY + ifNil: [ + | res | + self chartExtents minValueY: + (res := (self plots collect: #minValueY) min). + res ] + ifNotNil: [ :res | res ] +] + +{ #category : 'accessing' } +RSCompositeChart >> numberOfPlots [ + "Return the number of plots contained in the chart" + + ^ self plots size +] + +{ #category : 'public' } +RSCompositeChart >> openOnce [ + + self build. + ^ self canvas openOnce +] + +{ #category : 'accessing - extension' } +RSCompositeChart >> padding: aPoint [ + + super padding: aPoint. + plots ifNil: [ ^ self ]. + plots do: [ :plot | plot padding: aPoint ] +] + +{ #category : 'accessing' } +RSCompositeChart >> plots [ + + ^ plots +] + +{ #category : 'rendering' } +RSCompositeChart >> renderIn: aCanvas [ + + decorations , plots do: [ :element | element beforeRenderingIn: self ]. + decorations , plots do: [ :element | element renderIn: aCanvas ]. + shapes := decorations , plots + flatCollect: [ :element | element createdShapes ] + as: RSGroup +] + +{ #category : 'public - scales' } +RSCompositeChart >> xScale: aScale [ + + super xScale: aScale. + plots do: [ :plot | plot xScale: aScale ] +] + +{ #category : 'public - scales' } +RSCompositeChart >> yScale: aScale [ + + super yScale: aScale. + plots do: [ :e | e yScale: aScale ] +] diff --git a/src/Roassal-Chart/RSDefaultBinning.class.st b/src/Roassal-Chart/RSDefaultBinning.class.st index 6b710551..31ee9e6f 100644 --- a/src/Roassal-Chart/RSDefaultBinning.class.st +++ b/src/Roassal-Chart/RSDefaultBinning.class.st @@ -1,39 +1,39 @@ -" -TODO -" -Class { - #name : 'RSDefaultBinning', - #superclass : 'RSAbstractBinning', - #instVars : [ - 'numberOfBins' - ], - #category : 'Roassal-Chart-Strategy', - #package : 'Roassal-Chart', - #tag : 'Strategy' -} - -{ #category : 'hooks' } -RSDefaultBinning >> computeNumberOfBinsFor: aCollection [ - ^ self numberOfBins -] - -{ #category : 'defaults' } -RSDefaultBinning >> defaultNumberOfBins [ - ^ 10 -] - -{ #category : 'initialization' } -RSDefaultBinning >> initialize [ - super initialize. - self numberOfBins: self defaultNumberOfBins -] - -{ #category : 'accessing' } -RSDefaultBinning >> numberOfBins [ - ^ numberOfBins -] - -{ #category : 'accessing' } -RSDefaultBinning >> numberOfBins: aNumber [ - numberOfBins := aNumber -] +" +TODO +" +Class { + #name : 'RSDefaultBinning', + #superclass : 'RSAbstractBinning', + #instVars : [ + 'numberOfBins' + ], + #category : 'Roassal-Chart-Strategy', + #package : 'Roassal-Chart', + #tag : 'Strategy' +} + +{ #category : 'hooks' } +RSDefaultBinning >> computeNumberOfBinsFor: aCollection [ + ^ self numberOfBins +] + +{ #category : 'defaults' } +RSDefaultBinning >> defaultNumberOfBins [ + ^ 10 +] + +{ #category : 'initialization' } +RSDefaultBinning >> initialize [ + super initialize. + self numberOfBins: self defaultNumberOfBins +] + +{ #category : 'accessing' } +RSDefaultBinning >> numberOfBins [ + ^ numberOfBins +] + +{ #category : 'accessing' } +RSDefaultBinning >> numberOfBins: aNumber [ + numberOfBins := aNumber +] diff --git a/src/Roassal-Chart/RSDensityPlot.class.st b/src/Roassal-Chart/RSDensityPlot.class.st index f30fbdfc..7d9e8eff 100644 --- a/src/Roassal-Chart/RSDensityPlot.class.st +++ b/src/Roassal-Chart/RSDensityPlot.class.st @@ -1,255 +1,255 @@ -" -`RSDensityPlot` is a visual representation of the density distribution of a dataset using a kernel density estimation. - -**Responsibility:** plots the density distribution, provides options to customize the plot. - -**Collaborators:** the instance variable kernel density is a `RSKernelDensity` object that calculates the points of the density curve. - -**Public API and Key Messages** -- `data: aCollection` to create instances passing aCollection as argument. -- `kernelDensity aRSKernelDensity`: to create instances passing a `RSKernelDensity` object with set attributes like, bandwidth, kernel, etc. -- `bandwidth: aFloat` to set the bandwith (h) of the kernel in the kernel density estimation function. By `default 1.0`. - -**Instance Variables:** -- `area`: an `RSPolygon` that represents the area under the density curve. -- `curvePoints`: an `OrderedCollection` of `Point`s that store the result of evaluate the Kernel Density Estimation. -- `kernelDensity`: a `RSKernelDensity` object that made the calculation of the Kernel Density Estimation, several parametres are passed to this object to personalize the curve (bandwidth, kernel, etc.). -- `title`: aString that stores the title of the chart. -- `xlabel`: aString that stores the label of x axis. -- `ylabel`: aString that stores the label of y axis. - -**Example:** -```Smalltalk -| densityPlot data | -data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). -densityPlot := RSDensityPlot data: data. -densityPlot bandwidth: 10. -densityPlot xlabel: 'X label'; ylabel: 'Y label'; title: 'Density plot (bandwidth:10)'. -densityPlot color: Color green. -densityPlot open. -``` -" -Class { - #name : 'RSDensityPlot', - #superclass : 'RSAbstractPlot', - #instVars : [ - 'kernelDensity', - 'curvePoints', - 'area' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSDensityPlot class >> data: aCollection [ - | densityPlot | - densityPlot := self new. - densityPlot data: aCollection. - ^ densityPlot -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleBasicDensityPlot [ - | densityPlot data | - data := #(14 15 28 27 32 35). - densityPlot := self data: data. - densityPlot bandwidth: 4. - ^ densityPlot open -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleBasicDensityPlot2 [ - | densityPlot data | - data := #(5 5 5 45 45 45). - densityPlot := self data: data. - densityPlot bandwidth: 4. - ^ densityPlot open -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleBasicDensityPlot3 [ - | densityPlot data | - data := #(10 10 10 10). - densityPlot := self data: data. -" densityPlot bandwidth: 4." - ^ densityPlot open -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleBasicDensityPlot4 [ - | densityPlot data | - data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). - densityPlot := self data: data. - densityPlot bandwidth: 10. - ^ densityPlot open -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleBasicDensityPlot5 [ - | densityPlot data | - data := #(0.1 0.05 0.05 0.08). - densityPlot := self data: data. - densityPlot bandwidth." -0.02545046473618206" -" densityPlot bandwidth: 4." - ^ densityPlot open -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleDensityPlotCreateWithKernelDesity [ - | densityPlot data kernelDensity | - data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). - kernelDensity := RSKernelDensity data: data. - kernelDensity bandwidth: 10. - densityPlot := self kernelDensity: kernelDensity. - densityPlot shape - color: Color blue translucent; - border: (RSBorder new - color: Color red; - width: 2; - dashArray: #(2 4)). - ^ densityPlot open -] - -{ #category : 'examples' } -RSDensityPlot class >> exampleDensityPlotWithLabels [ - | densityPlot data | - data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). - densityPlot := self data: data. - densityPlot bandwidth: 10. - densityPlot xlabel: 'X label'; ylabel: 'Y label'; title: 'Density plot (bandwidth:10)'. - ^ densityPlot open -] - -{ #category : 'accessing' } -RSDensityPlot class >> kernelDensity: anRSKernelDensity [ - | densityPlot | - densityPlot := self new. - densityPlot kernelDensity: anRSKernelDensity. - ^ densityPlot -] - -{ #category : 'accessing' } -RSDensityPlot >> area [ - ^ area -] - -{ #category : 'accessing' } -RSDensityPlot >> areaColor [ - ^ self computeColor -] - -{ #category : 'accessing' } -RSDensityPlot >> bandwidth [ - ^ kernelDensity bandwidth -] - -{ #category : 'accessing' } -RSDensityPlot >> bandwidth: aNumber [ - kernelDensity bandwidth: aNumber. - self computeCurvePoints -] - -{ #category : 'private' } -RSDensityPlot >> computeCurvePoints [ - | ys yMax | - curvePoints := kernelDensity densityCurve. - xValues := { curvePoints first x. curvePoints last x. }. - ys := curvePoints collect: [ :point | point y ]. - yMax := ys max. - yValues := {0. yMax. } -] - -{ #category : 'accessing' } -RSDensityPlot >> createdShapes [ - ^ { area } -] - -{ #category : 'accessing' } -RSDensityPlot >> curvePoints [ - ^ curvePoints -] - -{ #category : 'accessing' } -RSDensityPlot >> data [ - ^ kernelDensity data -] - -{ #category : 'accessing' } -RSDensityPlot >> data: aCollection [ - kernelDensity data: aCollection. - self computeCurvePoints -] - -{ #category : 'defaults' } -RSDensityPlot >> defaultAreaColor [ - ^ area color -] - -{ #category : 'defaults' } -RSDensityPlot >> defaultKernelDensity [ - ^ RSKernelDensity new -] - -{ #category : 'defaults' } -RSDensityPlot >> defaultLineColor [ - ^ Color r:117 g:107 b:177 range: 255 -] - -{ #category : 'accessing - defaults' } -RSDensityPlot >> defaultShape [ - ^ RSPolygon new - noPaint -] - -{ #category : 'initialization' } -RSDensityPlot >> initialize [ - super initialize. - kernelDensity := self defaultKernelDensity -] - -{ #category : 'accessing' } -RSDensityPlot >> kde: aNumber [ - ^ kernelDensity kde: aNumber -] - -{ #category : 'accessing' } -RSDensityPlot >> kernel [ - ^ kernelDensity kernel -] - -{ #category : 'accessing' } -RSDensityPlot >> kernel: anRSKernelFunction [ - kernelDensity kernel: anRSKernelFunction. - self computeCurvePoints -] - -{ #category : 'accessing' } -RSDensityPlot >> kernelDensity [ - ^ kernelDensity -] - -{ #category : 'accessing' } -RSDensityPlot >> kernelDensity: anRSKernelDensity [ - kernelDensity := anRSKernelDensity. - self computeCurvePoints -] - -{ #category : 'rendering' } -RSDensityPlot >> renderIn: canvas [ - | curve firstPoint lastPoint closingPointStart closingPointEnd | - super renderIn: canvas. - area := self shape copy. - area color: self computeColor. - firstPoint := curvePoints first. - lastPoint := curvePoints last. - closingPointStart := firstPoint x @ 0. - closingPointEnd := lastPoint x @ 0. - curve := curvePoints copy. - curve addFirst: closingPointStart. - curve add: closingPointEnd. - lastPoint := curve last. - area points: (curve collect: [ :aPoint | self scalePoint: aPoint ]). - canvas add: area -] +" +`RSDensityPlot` is a visual representation of the density distribution of a dataset using a kernel density estimation. + +**Responsibility:** plots the density distribution, provides options to customize the plot. + +**Collaborators:** the instance variable kernel density is a `RSKernelDensity` object that calculates the points of the density curve. + +**Public API and Key Messages** +- `data: aCollection` to create instances passing aCollection as argument. +- `kernelDensity aRSKernelDensity`: to create instances passing a `RSKernelDensity` object with set attributes like, bandwidth, kernel, etc. +- `bandwidth: aFloat` to set the bandwith (h) of the kernel in the kernel density estimation function. By `default 1.0`. + +**Instance Variables:** +- `area`: an `RSPolygon` that represents the area under the density curve. +- `curvePoints`: an `OrderedCollection` of `Point`s that store the result of evaluate the Kernel Density Estimation. +- `kernelDensity`: a `RSKernelDensity` object that made the calculation of the Kernel Density Estimation, several parametres are passed to this object to personalize the curve (bandwidth, kernel, etc.). +- `title`: aString that stores the title of the chart. +- `xlabel`: aString that stores the label of x axis. +- `ylabel`: aString that stores the label of y axis. + +**Example:** +```Smalltalk +| densityPlot data | +data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). +densityPlot := RSDensityPlot data: data. +densityPlot bandwidth: 10. +densityPlot xlabel: 'X label'; ylabel: 'Y label'; title: 'Density plot (bandwidth:10)'. +densityPlot color: Color green. +densityPlot open. +``` +" +Class { + #name : 'RSDensityPlot', + #superclass : 'RSAbstractPlot', + #instVars : [ + 'kernelDensity', + 'curvePoints', + 'area' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSDensityPlot class >> data: aCollection [ + | densityPlot | + densityPlot := self new. + densityPlot data: aCollection. + ^ densityPlot +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleBasicDensityPlot [ + | densityPlot data | + data := #(14 15 28 27 32 35). + densityPlot := self data: data. + densityPlot bandwidth: 4. + ^ densityPlot open +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleBasicDensityPlot2 [ + | densityPlot data | + data := #(5 5 5 45 45 45). + densityPlot := self data: data. + densityPlot bandwidth: 4. + ^ densityPlot open +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleBasicDensityPlot3 [ + | densityPlot data | + data := #(10 10 10 10). + densityPlot := self data: data. +" densityPlot bandwidth: 4." + ^ densityPlot open +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleBasicDensityPlot4 [ + | densityPlot data | + data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). + densityPlot := self data: data. + densityPlot bandwidth: 10. + ^ densityPlot open +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleBasicDensityPlot5 [ + | densityPlot data | + data := #(0.1 0.05 0.05 0.08). + densityPlot := self data: data. + densityPlot bandwidth." -0.02545046473618206" +" densityPlot bandwidth: 4." + ^ densityPlot open +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleDensityPlotCreateWithKernelDesity [ + | densityPlot data kernelDensity | + data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). + kernelDensity := RSKernelDensity data: data. + kernelDensity bandwidth: 10. + densityPlot := self kernelDensity: kernelDensity. + densityPlot shape + color: Color blue translucent; + border: (RSBorder new + color: Color red; + width: 2; + dashArray: #(2 4)). + ^ densityPlot open +] + +{ #category : 'examples' } +RSDensityPlot class >> exampleDensityPlotWithLabels [ + | densityPlot data | + data := #(-25 -25 -25 -25 10 20 30 40 50 50 50 50 ). + densityPlot := self data: data. + densityPlot bandwidth: 10. + densityPlot xlabel: 'X label'; ylabel: 'Y label'; title: 'Density plot (bandwidth:10)'. + ^ densityPlot open +] + +{ #category : 'accessing' } +RSDensityPlot class >> kernelDensity: anRSKernelDensity [ + | densityPlot | + densityPlot := self new. + densityPlot kernelDensity: anRSKernelDensity. + ^ densityPlot +] + +{ #category : 'accessing' } +RSDensityPlot >> area [ + ^ area +] + +{ #category : 'accessing' } +RSDensityPlot >> areaColor [ + ^ self computeColor +] + +{ #category : 'accessing' } +RSDensityPlot >> bandwidth [ + ^ kernelDensity bandwidth +] + +{ #category : 'accessing' } +RSDensityPlot >> bandwidth: aNumber [ + kernelDensity bandwidth: aNumber. + self computeCurvePoints +] + +{ #category : 'private' } +RSDensityPlot >> computeCurvePoints [ + | ys yMax | + curvePoints := kernelDensity densityCurve. + xValues := { curvePoints first x. curvePoints last x. }. + ys := curvePoints collect: [ :point | point y ]. + yMax := ys max. + yValues := {0. yMax. } +] + +{ #category : 'accessing' } +RSDensityPlot >> createdShapes [ + ^ { area } +] + +{ #category : 'accessing' } +RSDensityPlot >> curvePoints [ + ^ curvePoints +] + +{ #category : 'accessing' } +RSDensityPlot >> data [ + ^ kernelDensity data +] + +{ #category : 'accessing' } +RSDensityPlot >> data: aCollection [ + kernelDensity data: aCollection. + self computeCurvePoints +] + +{ #category : 'defaults' } +RSDensityPlot >> defaultAreaColor [ + ^ area color +] + +{ #category : 'defaults' } +RSDensityPlot >> defaultKernelDensity [ + ^ RSKernelDensity new +] + +{ #category : 'defaults' } +RSDensityPlot >> defaultLineColor [ + ^ Color r:117 g:107 b:177 range: 255 +] + +{ #category : 'accessing - defaults' } +RSDensityPlot >> defaultShape [ + ^ RSPolygon new + noPaint +] + +{ #category : 'initialization' } +RSDensityPlot >> initialize [ + super initialize. + kernelDensity := self defaultKernelDensity +] + +{ #category : 'accessing' } +RSDensityPlot >> kde: aNumber [ + ^ kernelDensity kde: aNumber +] + +{ #category : 'accessing' } +RSDensityPlot >> kernel [ + ^ kernelDensity kernel +] + +{ #category : 'accessing' } +RSDensityPlot >> kernel: anRSKernelFunction [ + kernelDensity kernel: anRSKernelFunction. + self computeCurvePoints +] + +{ #category : 'accessing' } +RSDensityPlot >> kernelDensity [ + ^ kernelDensity +] + +{ #category : 'accessing' } +RSDensityPlot >> kernelDensity: anRSKernelDensity [ + kernelDensity := anRSKernelDensity. + self computeCurvePoints +] + +{ #category : 'rendering' } +RSDensityPlot >> renderIn: canvas [ + | curve firstPoint lastPoint closingPointStart closingPointEnd | + super renderIn: canvas. + area := self shape copy. + area color: self computeColor. + firstPoint := curvePoints first. + lastPoint := curvePoints last. + closingPointStart := firstPoint x @ 0. + closingPointEnd := lastPoint x @ 0. + curve := curvePoints copy. + curve addFirst: closingPointStart. + curve add: closingPointEnd. + lastPoint := curve last. + area points: (curve collect: [ :aPoint | self scalePoint: aPoint ]). + canvas add: area +] diff --git a/src/Roassal-Chart/RSDoubleBarPlot.class.st b/src/Roassal-Chart/RSDoubleBarPlot.class.st index 4d121720..406a7093 100644 --- a/src/Roassal-Chart/RSDoubleBarPlot.class.st +++ b/src/Roassal-Chart/RSDoubleBarPlot.class.st @@ -1,136 +1,136 @@ -" -RSDoubleBarPlot renders 2 different bars -" -Class { - #name : 'RSDoubleBarPlot', - #superclass : 'RSHorizontalBarPlot', - #instVars : [ - 'x2Values', - 'secondColor', - 'x2Scale', - 'bars2', - 'horizontalTopTick' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSDoubleBarPlot >> bars2 [ - ^ bars2 -] - -{ #category : 'accessing - computed' } -RSDoubleBarPlot >> computeSecondColor [ - ^ secondColor ifNil: [ self chart colorFor: x2Values ] -] - -{ #category : 'hooks' } -RSDoubleBarPlot >> computeSecondRectagleFor: aPoint index: index [ - | origin corner sizeOffset offset zero | - zero := 0. - left ifNotNil: [ zero := left at: index ]. - origin := (x2Scale scale: aPoint x) @ (yScale scale: aPoint y). - corner := (x2Scale scale: zero) @ origin y. - sizeOffset := 0@(self barSize * 0.3). - offset := 0@ self barOffset. - ^ Rectangle - origin: origin + offset - sizeOffset - corner: corner + offset + sizeOffset -] - -{ #category : 'rendering' } -RSDoubleBarPlot >> createBar2For: aPoint index: index [ - ^ self shape copy - model: (self modelFor: aPoint); - color: self computeSecondColor; - fromRectangle: (self computeSecondRectagleFor: aPoint index: index); - yourself -] - -{ #category : 'rendering' } -RSDoubleBarPlot >> createXScale [ - | padding | - super createXScale. - x2Scale ifNil: [ x2Scale := NSScale linear ]. - x2Scale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := self padding x. - x2Scale - domain: - {x2Values min min: 0. - x2Values max max: 0}; - range: - {0 + padding. - self extent x - padding} -] - -{ #category : 'accessing' } -RSDoubleBarPlot >> createdShapes [ - ^ bars, bars2 -] - -{ #category : 'accessing' } -RSDoubleBarPlot >> horizontalTopTick [ - - ^ horizontalTopTick -] - -{ #category : 'initialization' } -RSDoubleBarPlot >> initializeDecorations [ - - super initializeDecorations. - horizontalTopTick := RSHorizontalTopTick new. - self addDecoration: horizontalTopTick -] - -{ #category : 'public' } -RSDoubleBarPlot >> rawData: aCollection x1: bloc1 x2: bloc2 y: bloc3 [ - - rawData := aCollection. - self - x1: (rawData collect: bloc1) - x2: (rawData collect: bloc2) - y: (rawData collect: bloc3) -] - -{ #category : 'rendering' } -RSDoubleBarPlot >> renderIn: canvas [ - - | index | - super renderIn: canvas. - index := 1. - bars2 := x2Values - collect: [ :xt | - | yt bar | - yt := yValues at: index. - bar := self createBar2For: xt @ yt index: index. - index := index + 1. - bar ] - as: RSGroup. - canvas addAll: bars2 -] - -{ #category : 'accessing' } -RSDoubleBarPlot >> secondColor [ - ^ secondColor -] - -{ #category : 'public' } -RSDoubleBarPlot >> x1: x1 x2: x2 y: y [ - self assert: x1 size = x2 size description: 'The two collections must have the same size'. - self x: x1 y: y. - x2Values := x2. - horizontalTopTick values: x2Values -] - -{ #category : 'accessing' } -RSDoubleBarPlot >> x2Scale [ - - ^ x2Scale -] - -{ #category : 'accessing' } -RSDoubleBarPlot >> x2Values [ - ^ x2Values -] +" +RSDoubleBarPlot renders 2 different bars +" +Class { + #name : 'RSDoubleBarPlot', + #superclass : 'RSHorizontalBarPlot', + #instVars : [ + 'x2Values', + 'secondColor', + 'x2Scale', + 'bars2', + 'horizontalTopTick' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSDoubleBarPlot >> bars2 [ + ^ bars2 +] + +{ #category : 'accessing - computed' } +RSDoubleBarPlot >> computeSecondColor [ + ^ secondColor ifNil: [ self chart colorFor: x2Values ] +] + +{ #category : 'hooks' } +RSDoubleBarPlot >> computeSecondRectagleFor: aPoint index: index [ + | origin corner sizeOffset offset zero | + zero := 0. + left ifNotNil: [ zero := left at: index ]. + origin := (x2Scale scale: aPoint x) @ (yScale scale: aPoint y). + corner := (x2Scale scale: zero) @ origin y. + sizeOffset := 0@(self barSize * 0.3). + offset := 0@ self barOffset. + ^ Rectangle + origin: origin + offset - sizeOffset + corner: corner + offset + sizeOffset +] + +{ #category : 'rendering' } +RSDoubleBarPlot >> createBar2For: aPoint index: index [ + ^ self shape copy + model: (self modelFor: aPoint); + color: self computeSecondColor; + fromRectangle: (self computeSecondRectagleFor: aPoint index: index); + yourself +] + +{ #category : 'rendering' } +RSDoubleBarPlot >> createXScale [ + | padding | + super createXScale. + x2Scale ifNil: [ x2Scale := NSScale linear ]. + x2Scale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := self padding x. + x2Scale + domain: + {x2Values min min: 0. + x2Values max max: 0}; + range: + {0 + padding. + self extent x - padding} +] + +{ #category : 'accessing' } +RSDoubleBarPlot >> createdShapes [ + ^ bars, bars2 +] + +{ #category : 'accessing' } +RSDoubleBarPlot >> horizontalTopTick [ + + ^ horizontalTopTick +] + +{ #category : 'initialization' } +RSDoubleBarPlot >> initializeDecorations [ + + super initializeDecorations. + horizontalTopTick := RSHorizontalTopTick new. + self addDecoration: horizontalTopTick +] + +{ #category : 'public' } +RSDoubleBarPlot >> rawData: aCollection x1: bloc1 x2: bloc2 y: bloc3 [ + + rawData := aCollection. + self + x1: (rawData collect: bloc1) + x2: (rawData collect: bloc2) + y: (rawData collect: bloc3) +] + +{ #category : 'rendering' } +RSDoubleBarPlot >> renderIn: canvas [ + + | index | + super renderIn: canvas. + index := 1. + bars2 := x2Values + collect: [ :xt | + | yt bar | + yt := yValues at: index. + bar := self createBar2For: xt @ yt index: index. + index := index + 1. + bar ] + as: RSGroup. + canvas addAll: bars2 +] + +{ #category : 'accessing' } +RSDoubleBarPlot >> secondColor [ + ^ secondColor +] + +{ #category : 'public' } +RSDoubleBarPlot >> x1: x1 x2: x2 y: y [ + self assert: x1 size = x2 size description: 'The two collections must have the same size'. + self x: x1 y: y. + x2Values := x2. + horizontalTopTick values: x2Values +] + +{ #category : 'accessing' } +RSDoubleBarPlot >> x2Scale [ + + ^ x2Scale +] + +{ #category : 'accessing' } +RSDoubleBarPlot >> x2Values [ + ^ x2Values +] diff --git a/src/Roassal-Chart/RSFixedBinning.class.st b/src/Roassal-Chart/RSFixedBinning.class.st index 69880e5c..98a55018 100644 --- a/src/Roassal-Chart/RSFixedBinning.class.st +++ b/src/Roassal-Chart/RSFixedBinning.class.st @@ -1,35 +1,35 @@ -" -I am class used to bins: in RSHistogramPlot -" -Class { - #name : 'RSFixedBinning', - #superclass : 'RSAbstractBinning', - #instVars : [ - 'bins' - ], - #category : 'Roassal-Chart-Strategy', - #package : 'Roassal-Chart', - #tag : 'Strategy' -} - -{ #category : 'accessing' } -RSFixedBinning >> bins [ - - ^ bins -] - -{ #category : 'accessing' } -RSFixedBinning >> bins: aCollection [ - - bins := aCollection -] - -{ #category : 'hooks' } -RSFixedBinning >> computeNumberOfBinsFor: aCollection [ - ^ bins size -] - -{ #category : 'hooks' } -RSFixedBinning >> createBinsFor: aCollection [ - ^ bins -] +" +I am class used to bins: in RSHistogramPlot +" +Class { + #name : 'RSFixedBinning', + #superclass : 'RSAbstractBinning', + #instVars : [ + 'bins' + ], + #category : 'Roassal-Chart-Strategy', + #package : 'Roassal-Chart', + #tag : 'Strategy' +} + +{ #category : 'accessing' } +RSFixedBinning >> bins [ + + ^ bins +] + +{ #category : 'accessing' } +RSFixedBinning >> bins: aCollection [ + + bins := aCollection +] + +{ #category : 'hooks' } +RSFixedBinning >> computeNumberOfBinsFor: aCollection [ + ^ bins size +] + +{ #category : 'hooks' } +RSFixedBinning >> createBinsFor: aCollection [ + ^ bins +] diff --git a/src/Roassal-Chart/RSFixedLocator.class.st b/src/Roassal-Chart/RSFixedLocator.class.st index e6c6d6bd..4bb7ec8e 100644 --- a/src/Roassal-Chart/RSFixedLocator.class.st +++ b/src/Roassal-Chart/RSFixedLocator.class.st @@ -1,37 +1,37 @@ -" - -`RSFixedLocator` places ticks at fixed positions according to `ticks`. Only positions that are in the domain of the scale are taken into account. - -*Responsibility*: Places ticks at fixed positions. - -*Collaborators*: `RSFixedLocator` is used when rendering ticks. -" -Class { - #name : 'RSFixedLocator', - #superclass : 'RSTickLocator', - #instVars : [ - 'ticks' - ], - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'generate' } -RSFixedLocator >> generateTicks: aScale with: numberOfTicks [ - - ticks sort. - ^ ticks select: [ :e | e >= aScale domain first and: e <= aScale domain last ] -] - -{ #category : 'generate' } -RSFixedLocator >> ticks [ - - ^ ticks -] - -{ #category : 'generate' } -RSFixedLocator >> ticks: aCollection [ - - ticks := aCollection -] +" + +`RSFixedLocator` places ticks at fixed positions according to `ticks`. Only positions that are in the domain of the scale are taken into account. + +*Responsibility*: Places ticks at fixed positions. + +*Collaborators*: `RSFixedLocator` is used when rendering ticks. +" +Class { + #name : 'RSFixedLocator', + #superclass : 'RSTickLocator', + #instVars : [ + 'ticks' + ], + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'generate' } +RSFixedLocator >> generateTicks: aScale with: numberOfTicks [ + + ticks sort. + ^ ticks select: [ :e | e >= aScale domain first and: e <= aScale domain last ] +] + +{ #category : 'generate' } +RSFixedLocator >> ticks [ + + ^ ticks +] + +{ #category : 'generate' } +RSFixedLocator >> ticks: aCollection [ + + ticks := aCollection +] diff --git a/src/Roassal-Chart/RSHistogramPlot.class.st b/src/Roassal-Chart/RSHistogramPlot.class.st index 654c8237..fcd69eb5 100644 --- a/src/Roassal-Chart/RSHistogramPlot.class.st +++ b/src/Roassal-Chart/RSHistogramPlot.class.st @@ -1,243 +1,243 @@ -" -Histogram Plot for Roassal -``` -| values c plot | -values := #(0.5 0.5 0.3 -0.2 1.6 0 0.1 0.1 0.6 0.4). -c := RSChart new. -plot := RSHistogramPlot new x: values; bins: #(-0.5 0 0.5 1 0.5 1 1.5 2). -c addPlot: plot. -c horizontalTick doNotUseNiceLabel. -^ c -``` - -- Bins, can be a number or array of sorted numbers. -- By default bins is 10. -- For the previous example bins are `#(-0.5 0 0.5 1 0.5 1 1.5 2)`. -- First bin is #(-0.5 0), second bin is #(0 0.5), etc. -- All bins but last use this expression to calculate the number of values in each bin: `start <= n and: [n < end]` -- Last bin uses this expression `n between: start and: end` -- `binningStrategy:` allows to the user define different strategies to calculate the size for bins. Check `RSBinningStrategy` - - -A histogram can also be generated from a collection and a bloc using `RSHistogramPlot of: aCollection on: aBloc`. It can be useful when the data is associated with something, like the number of lines of code of a class. -The histogram will `collect:` on the collection with the bloc on its own to get the data, then the plot will show the distribution of the elements of the collection according to their corresponding value from the data. -Here is an example for the number of lines of code of `Collection` and its subclasses: - -``` -classes := Collection withAllSubclasses. -plot := RSHistogramPlot of: classes on: #linesOfCode. -plot bins: (0 to: 1000 by: 20) , {1000. 2000. 3000 }. - -p show -``` -" -Class { - #name : 'RSHistogramPlot', - #superclass : 'RSAbstractPlot', - #instVars : [ - 'map', - 'x', - 'bins', - 'bars', - 'binningStrategy', - 'isRelativeVerticalTicks' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'instance creation' } -RSHistogramPlot class >> of: aCollection on: aBloc [ - - ^ self new collectDataOf: aCollection on: aBloc -] - -{ #category : 'accessing' } -RSHistogramPlot >> absoluteVerticalTicks [ - - isRelativeVerticalTicks := false. - self verticalTick labelConversion: [ :labelValue | labelValue ] -] - -{ #category : 'accessing' } -RSHistogramPlot >> bars [ - ^ bars -] - -{ #category : 'accessing' } -RSHistogramPlot >> binSize: aNumber [ - self binningStrategy: (RSBinSizeBinning new - size: aNumber; - yourself) -] - -{ #category : 'accessing' } -RSHistogramPlot >> binningStrategy [ - - ^ binningStrategy -] - -{ #category : 'accessing' } -RSHistogramPlot >> binningStrategy: aBinningStrategy [ - binningStrategy := aBinningStrategy. - self computeXYValues -] - -{ #category : 'accessing' } -RSHistogramPlot >> bins [ - ^ bins -] - -{ #category : 'accessing' } -RSHistogramPlot >> bins: aCollection [ - self assert: aCollection isCollection description: 'you must provide a collection'. - self binningStrategy: (RSFixedBinning new - bins: aCollection; - yourself) -] - -{ #category : 'enumerating' } -RSHistogramPlot >> collectDataOf: aCollection on: aBloc [ - - x := aCollection collect: aBloc. - map := Dictionary newFromKeys: aCollection andValues: x. - x := x sorted. - self computeXYValues -] - -{ #category : 'private' } -RSHistogramPlot >> computeBins [ - ^ binningStrategy createBinsFor: x -] - -{ #category : 'private' } -RSHistogramPlot >> computeXYValues [ - | prev | - x ifNil: [ ^ self ]. - bins := self computeBins. - xValues := bins. - prev := bins first. - yValues := bins allButLast collectWithIndex: [ :val1 :index | - | val2 condition | - val2 := bins at: index + 1. - condition := index + 1 = bins size - ifTrue: [ [ :value | value between: val1 and: val2 ] ] - ifFalse: [ [ :value | val1 <= value and: [ value < val2 ] ] ]. - - (x select: condition ) size. - ] -] - -{ #category : 'accessing' } -RSHistogramPlot >> createdShapes [ - ^ bars -] - -{ #category : 'accessing - defaults' } -RSHistogramPlot >> defaultShape [ - ^ RSBox new noPaint -] - -{ #category : 'rendering' } -RSHistogramPlot >> definedValuesY [ - "Return the list Y values that are defined" - ^ yValues, {0} -] - -{ #category : 'initialization' } -RSHistogramPlot >> initialize [ - - super initialize. - self binningStrategy: RSDefaultBinning new. - isRelativeVerticalTicks := false -] - -{ #category : 'accessing' } -RSHistogramPlot >> map [ - - ^ map -] - -{ #category : 'accessing' } -RSHistogramPlot >> map: aMap [ - - map := aMap -] - -{ #category : 'accessing' } -RSHistogramPlot >> model: anArray [ - - | toShow xVal xVal2 yVal | - xVal := anArray first. - xVal2 := anArray second. - yVal := anArray last. - - map ifNil: [ ^ xVal -> xVal2 -> yVal ]. - ^ toShow := map keys select: [ :e | - (map at: e) between: xVal and: xVal2 ] -] - -{ #category : 'accessing' } -RSHistogramPlot >> numberOfBins [ - ^ self binningStrategy numberOfBins -] - -{ #category : 'accessing' } -RSHistogramPlot >> numberOfBins: aNumber [ - self binningStrategy numberOfBins: aNumber. - self computeXYValues -] - -{ #category : 'accessing' } -RSHistogramPlot >> relativeVerticalTicks [ - - "Adjust the vertical ticks label to show the relative labels. E.g. instead of showing that a bin appeared 17 times it will show 50%" - - isRelativeVerticalTicks := true. - self verticalTick - labelConversion: [ :labelValue | (labelValue / x size) * 100 asInteger ] -] - -{ #category : 'rendering' } -RSHistogramPlot >> renderIn: canvas [ - - super renderIn: canvas. - bars := yValues collectWithIndex: [ :yVal :index | - | rect xVal xVal2 | - xVal := xValues at: index. - xVal2 := xValues at: index + 1. - rect := Rectangle - origin: (self scalePoint: xVal @ yVal) - corner: (self scalePoint: xVal2 @ 0). - self shape copy - model: (self model: { xVal. xVal2. yVal }); - color: self computeColor; - fromRectangle: rect; - yourself ]. - bars := bars asGroup. - canvas addAll: bars -] - -{ #category : 'accessing' } -RSHistogramPlot >> x [ - ^ x -] - -{ #category : 'accessing' } -RSHistogramPlot >> x: aCollection [ - x := aCollection sorted. - self computeXYValues -] - -{ #category : 'accessing' } -RSHistogramPlot >> yTicksRangeMax: max numberOfTicks: numberOfTicks [ - - | relativeMax | - relativeMax := isRelativeVerticalTicks - ifFalse: [ max ] - ifTrue: [ x size * (max / 100) ]. - - self verticalTick ticksData: - ((0 to: relativeMax count: numberOfTicks) collect: #asInteger) -] +" +Histogram Plot for Roassal +``` +| values c plot | +values := #(0.5 0.5 0.3 -0.2 1.6 0 0.1 0.1 0.6 0.4). +c := RSChart new. +plot := RSHistogramPlot new x: values; bins: #(-0.5 0 0.5 1 0.5 1 1.5 2). +c addPlot: plot. +c horizontalTick doNotUseNiceLabel. +^ c +``` + +- Bins, can be a number or array of sorted numbers. +- By default bins is 10. +- For the previous example bins are `#(-0.5 0 0.5 1 0.5 1 1.5 2)`. +- First bin is #(-0.5 0), second bin is #(0 0.5), etc. +- All bins but last use this expression to calculate the number of values in each bin: `start <= n and: [n < end]` +- Last bin uses this expression `n between: start and: end` +- `binningStrategy:` allows to the user define different strategies to calculate the size for bins. Check `RSBinningStrategy` + + +A histogram can also be generated from a collection and a bloc using `RSHistogramPlot of: aCollection on: aBloc`. It can be useful when the data is associated with something, like the number of lines of code of a class. +The histogram will `collect:` on the collection with the bloc on its own to get the data, then the plot will show the distribution of the elements of the collection according to their corresponding value from the data. +Here is an example for the number of lines of code of `Collection` and its subclasses: + +``` +classes := Collection withAllSubclasses. +plot := RSHistogramPlot of: classes on: #linesOfCode. +plot bins: (0 to: 1000 by: 20) , {1000. 2000. 3000 }. + +p show +``` +" +Class { + #name : 'RSHistogramPlot', + #superclass : 'RSAbstractPlot', + #instVars : [ + 'map', + 'x', + 'bins', + 'bars', + 'binningStrategy', + 'isRelativeVerticalTicks' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'instance creation' } +RSHistogramPlot class >> of: aCollection on: aBloc [ + + ^ self new collectDataOf: aCollection on: aBloc +] + +{ #category : 'accessing' } +RSHistogramPlot >> absoluteVerticalTicks [ + + isRelativeVerticalTicks := false. + self verticalTick labelConversion: [ :labelValue | labelValue ] +] + +{ #category : 'accessing' } +RSHistogramPlot >> bars [ + ^ bars +] + +{ #category : 'accessing' } +RSHistogramPlot >> binSize: aNumber [ + self binningStrategy: (RSBinSizeBinning new + size: aNumber; + yourself) +] + +{ #category : 'accessing' } +RSHistogramPlot >> binningStrategy [ + + ^ binningStrategy +] + +{ #category : 'accessing' } +RSHistogramPlot >> binningStrategy: aBinningStrategy [ + binningStrategy := aBinningStrategy. + self computeXYValues +] + +{ #category : 'accessing' } +RSHistogramPlot >> bins [ + ^ bins +] + +{ #category : 'accessing' } +RSHistogramPlot >> bins: aCollection [ + self assert: aCollection isCollection description: 'you must provide a collection'. + self binningStrategy: (RSFixedBinning new + bins: aCollection; + yourself) +] + +{ #category : 'enumerating' } +RSHistogramPlot >> collectDataOf: aCollection on: aBloc [ + + x := aCollection collect: aBloc. + map := Dictionary newFromKeys: aCollection andValues: x. + x := x sorted. + self computeXYValues +] + +{ #category : 'private' } +RSHistogramPlot >> computeBins [ + ^ binningStrategy createBinsFor: x +] + +{ #category : 'private' } +RSHistogramPlot >> computeXYValues [ + | prev | + x ifNil: [ ^ self ]. + bins := self computeBins. + xValues := bins. + prev := bins first. + yValues := bins allButLast collectWithIndex: [ :val1 :index | + | val2 condition | + val2 := bins at: index + 1. + condition := index + 1 = bins size + ifTrue: [ [ :value | value between: val1 and: val2 ] ] + ifFalse: [ [ :value | val1 <= value and: [ value < val2 ] ] ]. + + (x select: condition ) size. + ] +] + +{ #category : 'accessing' } +RSHistogramPlot >> createdShapes [ + ^ bars +] + +{ #category : 'accessing - defaults' } +RSHistogramPlot >> defaultShape [ + ^ RSBox new noPaint +] + +{ #category : 'rendering' } +RSHistogramPlot >> definedValuesY [ + "Return the list Y values that are defined" + ^ yValues, {0} +] + +{ #category : 'initialization' } +RSHistogramPlot >> initialize [ + + super initialize. + self binningStrategy: RSDefaultBinning new. + isRelativeVerticalTicks := false +] + +{ #category : 'accessing' } +RSHistogramPlot >> map [ + + ^ map +] + +{ #category : 'accessing' } +RSHistogramPlot >> map: aMap [ + + map := aMap +] + +{ #category : 'accessing' } +RSHistogramPlot >> model: anArray [ + + | toShow xVal xVal2 yVal | + xVal := anArray first. + xVal2 := anArray second. + yVal := anArray last. + + map ifNil: [ ^ xVal -> xVal2 -> yVal ]. + ^ toShow := map keys select: [ :e | + (map at: e) between: xVal and: xVal2 ] +] + +{ #category : 'accessing' } +RSHistogramPlot >> numberOfBins [ + ^ self binningStrategy numberOfBins +] + +{ #category : 'accessing' } +RSHistogramPlot >> numberOfBins: aNumber [ + self binningStrategy numberOfBins: aNumber. + self computeXYValues +] + +{ #category : 'accessing' } +RSHistogramPlot >> relativeVerticalTicks [ + + "Adjust the vertical ticks label to show the relative labels. E.g. instead of showing that a bin appeared 17 times it will show 50%" + + isRelativeVerticalTicks := true. + self verticalTick + labelConversion: [ :labelValue | (labelValue / x size) * 100 asInteger ] +] + +{ #category : 'rendering' } +RSHistogramPlot >> renderIn: canvas [ + + super renderIn: canvas. + bars := yValues collectWithIndex: [ :yVal :index | + | rect xVal xVal2 | + xVal := xValues at: index. + xVal2 := xValues at: index + 1. + rect := Rectangle + origin: (self scalePoint: xVal @ yVal) + corner: (self scalePoint: xVal2 @ 0). + self shape copy + model: (self model: { xVal. xVal2. yVal }); + color: self computeColor; + fromRectangle: rect; + yourself ]. + bars := bars asGroup. + canvas addAll: bars +] + +{ #category : 'accessing' } +RSHistogramPlot >> x [ + ^ x +] + +{ #category : 'accessing' } +RSHistogramPlot >> x: aCollection [ + x := aCollection sorted. + self computeXYValues +] + +{ #category : 'accessing' } +RSHistogramPlot >> yTicksRangeMax: max numberOfTicks: numberOfTicks [ + + | relativeMax | + relativeMax := isRelativeVerticalTicks + ifFalse: [ max ] + ifTrue: [ x size * (max / 100) ]. + + self verticalTick ticksData: + ((0 to: relativeMax count: numberOfTicks) collect: #asInteger) +] diff --git a/src/Roassal-Chart/RSHorizontalBarPlot.class.st b/src/Roassal-Chart/RSHorizontalBarPlot.class.st index a3c6e9d8..0717d313 100644 --- a/src/Roassal-Chart/RSHorizontalBarPlot.class.st +++ b/src/Roassal-Chart/RSHorizontalBarPlot.class.st @@ -1,77 +1,77 @@ -" -The class for horizontal bar plots -" -Class { - #name : 'RSHorizontalBarPlot', - #superclass : 'RSAbstractBarPlot', - #instVars : [ - 'left' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSHorizontalBarPlot >> barScale [ - ^ yScale -] - -{ #category : 'rendering' } -RSHorizontalBarPlot >> beforeRenderingIn: aChart [ - - | barScale | - super beforeRenderingIn: aChart. - yScale class = NSOrdinalScale ifTrue: [ ^ self ]. - barScale := NSOrdinalScale new - domain: yValues; - rangeBands: yScale range padding: gapRatio. - aChart yScale: barScale -] - -{ #category : 'hooks' } -RSHorizontalBarPlot >> computeRectagleFor: aPoint index: index [ - - | origin corner sizeOffset offset zero | - zero := 0. - left ifNotNil: [ zero := left at: index ]. - origin := self scalePoint: aPoint + (zero @ 0). - corner := (xScale scale: zero) @ origin y. - sizeOffset := 0 @ (self barSize / 2.0). - offset := 0 @ self barOffset. - ^ Rectangle - origin: origin + offset - sizeOffset - corner: corner + offset + sizeOffset -] - -{ #category : 'rendering' } -RSHorizontalBarPlot >> definedValuesX [ - "Return the list Y values that are defined" - | res | - res := xValues. - left ifNotNil: [ res := xValues + left ]. - ^ res, {0} -] - -{ #category : 'testing' } -RSHorizontalBarPlot >> isHorizontalBarPlot [ - ^ true -] - -{ #category : 'accessing' } -RSHorizontalBarPlot >> left [ - ^ left -] - -{ #category : 'accessing' } -RSHorizontalBarPlot >> left: aCollection [ - self - assert: yValues size = aCollection size - description: 'Invalid size'. - left := aCollection -] - -{ #category : 'hooks' } -RSHorizontalBarPlot >> modelFor: aPoint [ - ^ aPoint x -] +" +The class for horizontal bar plots +" +Class { + #name : 'RSHorizontalBarPlot', + #superclass : 'RSAbstractBarPlot', + #instVars : [ + 'left' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSHorizontalBarPlot >> barScale [ + ^ yScale +] + +{ #category : 'rendering' } +RSHorizontalBarPlot >> beforeRenderingIn: aChart [ + + | barScale | + super beforeRenderingIn: aChart. + yScale class = NSOrdinalScale ifTrue: [ ^ self ]. + barScale := NSOrdinalScale new + domain: yValues; + rangeBands: yScale range padding: gapRatio. + aChart yScale: barScale +] + +{ #category : 'hooks' } +RSHorizontalBarPlot >> computeRectagleFor: aPoint index: index [ + + | origin corner sizeOffset offset zero | + zero := 0. + left ifNotNil: [ zero := left at: index ]. + origin := self scalePoint: aPoint + (zero @ 0). + corner := (xScale scale: zero) @ origin y. + sizeOffset := 0 @ (self barSize / 2.0). + offset := 0 @ self barOffset. + ^ Rectangle + origin: origin + offset - sizeOffset + corner: corner + offset + sizeOffset +] + +{ #category : 'rendering' } +RSHorizontalBarPlot >> definedValuesX [ + "Return the list Y values that are defined" + | res | + res := xValues. + left ifNotNil: [ res := xValues + left ]. + ^ res, {0} +] + +{ #category : 'testing' } +RSHorizontalBarPlot >> isHorizontalBarPlot [ + ^ true +] + +{ #category : 'accessing' } +RSHorizontalBarPlot >> left [ + ^ left +] + +{ #category : 'accessing' } +RSHorizontalBarPlot >> left: aCollection [ + self + assert: yValues size = aCollection size + description: 'Invalid size'. + left := aCollection +] + +{ #category : 'hooks' } +RSHorizontalBarPlot >> modelFor: aPoint [ + ^ aPoint x +] diff --git a/src/Roassal-Chart/RSHorizontalTick.class.st b/src/Roassal-Chart/RSHorizontalTick.class.st index 01768678..77e3c307 100644 --- a/src/Roassal-Chart/RSHorizontalTick.class.st +++ b/src/Roassal-Chart/RSHorizontalTick.class.st @@ -1,75 +1,75 @@ -" - -`RSHorizontalTick` defines ticks for the horizontal axis. It is a decoration that can be added to a `RSChart`. - -*Responsibility*: define, customize, and render ticks - -*Collaborators*: `RSHorizontalTick` is added to `RSChart` - -*Example*: -```Smalltalk - x := -3.14 to: 3.14 by: 0.01. - p := RSLinePlot new. - p x: x y: x sin * 0.22 + 0.5. - p verticalTick asFloat. - p -``` -" -Class { - #name : 'RSHorizontalTick', - #superclass : 'RSAbstractTick', - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'rendering' } -RSHorizontalTick >> createTickLineFor: aNumber [ - | value zeroY | - value := xScale scale: aNumber. - zeroY := self chart spineDecoration zeroPoint y. - zeroY := zeroY - self chart extent y. - ^ self newLineTick - startPoint: value @ zeroY; - endPoint: value @ (zeroY + self configuration tickSize); - yourself -] - -{ #category : 'accessing - defaults' } -RSHorizontalTick >> defaultLabelLocation [ - ^ RSLocation new below offset: 0@3 -] - -{ #category : 'testing' } -RSHorizontalTick >> isHorizontalTick [ - ^ true -] - -{ #category : 'accessing' } -RSHorizontalTick >> max [ - ^ chart maxValueX -] - -{ #category : 'accessing' } -RSHorizontalTick >> min [ - ^ chart minChartValueX -] - -{ #category : 'rendering' } -RSHorizontalTick >> updateChartMaxAndMinValues: aChart [ - - aChart - minChartValueX: self ticksData first; - maxChartValueX: self ticksData last -] - -{ #category : 'public' } -RSHorizontalTick >> useDiagonalLabel [ - self labelRotation: -45. - self labelLocation outer; bottom; left; offset: (self configuration fontSize * 0.5 @ 0) -] - -{ #category : 'public' } -RSHorizontalTick >> useVerticalLabel [ - self labelRotation: -90 -] +" + +`RSHorizontalTick` defines ticks for the horizontal axis. It is a decoration that can be added to a `RSChart`. + +*Responsibility*: define, customize, and render ticks + +*Collaborators*: `RSHorizontalTick` is added to `RSChart` + +*Example*: +```Smalltalk + x := -3.14 to: 3.14 by: 0.01. + p := RSLinePlot new. + p x: x y: x sin * 0.22 + 0.5. + p verticalTick asFloat. + p +``` +" +Class { + #name : 'RSHorizontalTick', + #superclass : 'RSAbstractTick', + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'rendering' } +RSHorizontalTick >> createTickLineFor: aNumber [ + | value zeroY | + value := xScale scale: aNumber. + zeroY := self chart spineDecoration zeroPoint y. + zeroY := zeroY - self chart extent y. + ^ self newLineTick + startPoint: value @ zeroY; + endPoint: value @ (zeroY + self configuration tickSize); + yourself +] + +{ #category : 'accessing - defaults' } +RSHorizontalTick >> defaultLabelLocation [ + ^ RSLocation new below offset: 0@3 +] + +{ #category : 'testing' } +RSHorizontalTick >> isHorizontalTick [ + ^ true +] + +{ #category : 'accessing' } +RSHorizontalTick >> max [ + ^ chart maxValueX +] + +{ #category : 'accessing' } +RSHorizontalTick >> min [ + ^ chart minChartValueX +] + +{ #category : 'rendering' } +RSHorizontalTick >> updateChartMaxAndMinValues: aChart [ + + aChart + minChartValueX: self ticksData first; + maxChartValueX: self ticksData last +] + +{ #category : 'public' } +RSHorizontalTick >> useDiagonalLabel [ + self labelRotation: -45. + self labelLocation outer; bottom; left; offset: (self configuration fontSize * 0.5 @ 0) +] + +{ #category : 'public' } +RSHorizontalTick >> useVerticalLabel [ + self labelRotation: -90 +] diff --git a/src/Roassal-Chart/RSHorizontalTopTick.class.st b/src/Roassal-Chart/RSHorizontalTopTick.class.st index a31c5236..ec36edd7 100644 --- a/src/Roassal-Chart/RSHorizontalTopTick.class.st +++ b/src/Roassal-Chart/RSHorizontalTopTick.class.st @@ -1,83 +1,83 @@ -" -A horizontal tick but in top of the chart, takes values from RSChart, this tick does not use the default scale from the chart. Also this tick decoration does not modify the max and min values from the chart - -" -Class { - #name : 'RSHorizontalTopTick', - #superclass : 'RSAbstractTick', - #instVars : [ - 'values' - ], - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'rendering' } -RSHorizontalTopTick >> beforeRenderingIn: aChart [ - - self createXScale. - self createYScale -] - -{ #category : 'rendering' } -RSHorizontalTopTick >> createTickLineFor: aNumber [ - | value y | - value := xScale scale: aNumber. - y := chart extent y negated. - ^ self newLineTick - startPoint: value @ y; - endPoint: value @ (y - self configuration tickSize); - yourself -] - -{ #category : 'rendering' } -RSHorizontalTopTick >> createXScale [ - - | padding | - xScale ifNil: [ xScale := NSScale linear ]. - xScale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := chart padding x. - xScale - domain: { - self min. - self max }; - range: { - (0 + padding). - (chart extent x - padding) } -] - -{ #category : 'accessing - defaults' } -RSHorizontalTopTick >> defaultLabelLocation [ - ^ RSLocation new above offset: 0@ -3 -] - -{ #category : 'testing' } -RSHorizontalTopTick >> isHorizontalTick [ - - ^ true -] - -{ #category : 'accessing' } -RSHorizontalTopTick >> max [ - ^ values max max: 0 -] - -{ #category : 'accessing' } -RSHorizontalTopTick >> min [ - ^ values min min: 0 -] - -{ #category : 'rendering' } -RSHorizontalTopTick >> updateChartMaxAndMinValues: aChart [ -] - -{ #category : 'accessing' } -RSHorizontalTopTick >> values [ - ^ values -] - -{ #category : 'accessing' } -RSHorizontalTopTick >> values: aCollection [ - values := aCollection -] +" +A horizontal tick but in top of the chart, takes values from RSChart, this tick does not use the default scale from the chart. Also this tick decoration does not modify the max and min values from the chart + +" +Class { + #name : 'RSHorizontalTopTick', + #superclass : 'RSAbstractTick', + #instVars : [ + 'values' + ], + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'rendering' } +RSHorizontalTopTick >> beforeRenderingIn: aChart [ + + self createXScale. + self createYScale +] + +{ #category : 'rendering' } +RSHorizontalTopTick >> createTickLineFor: aNumber [ + | value y | + value := xScale scale: aNumber. + y := chart extent y negated. + ^ self newLineTick + startPoint: value @ y; + endPoint: value @ (y - self configuration tickSize); + yourself +] + +{ #category : 'rendering' } +RSHorizontalTopTick >> createXScale [ + + | padding | + xScale ifNil: [ xScale := NSScale linear ]. + xScale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := chart padding x. + xScale + domain: { + self min. + self max }; + range: { + (0 + padding). + (chart extent x - padding) } +] + +{ #category : 'accessing - defaults' } +RSHorizontalTopTick >> defaultLabelLocation [ + ^ RSLocation new above offset: 0@ -3 +] + +{ #category : 'testing' } +RSHorizontalTopTick >> isHorizontalTick [ + + ^ true +] + +{ #category : 'accessing' } +RSHorizontalTopTick >> max [ + ^ values max max: 0 +] + +{ #category : 'accessing' } +RSHorizontalTopTick >> min [ + ^ values min min: 0 +] + +{ #category : 'rendering' } +RSHorizontalTopTick >> updateChartMaxAndMinValues: aChart [ +] + +{ #category : 'accessing' } +RSHorizontalTopTick >> values [ + ^ values +] + +{ #category : 'accessing' } +RSHorizontalTopTick >> values: aCollection [ + values := aCollection +] diff --git a/src/Roassal-Chart/RSInvertedCDF.class.st b/src/Roassal-Chart/RSInvertedCDF.class.st index 72d54914..4f7ce11b 100644 --- a/src/Roassal-Chart/RSInvertedCDF.class.st +++ b/src/Roassal-Chart/RSInvertedCDF.class.st @@ -1,30 +1,30 @@ -Class { - #name : 'RSInvertedCDF', - #superclass : 'RSQuantile', - #category : 'Roassal-Chart-Statistics', - #package : 'Roassal-Chart', - #tag : 'Statistics' -} - -{ #category : 'initialize' } -RSInvertedCDF >> compute: populationPercentage [ - | m j g n| - n := data size. - (n = 1) ifTrue: [ ^ data at: 1 ]. - m := 0. - j := ((populationPercentage * n) + m). - j := j//1. - j = 0 ifTrue: [ j := 1 ]. - g := (populationPercentage * n) + m - j. - ^ ((1 - (self gama: g)) * (data at: j)) + ((self gama: g)*(data at:(j+1))) -] - -{ #category : 'initialize' } -RSInvertedCDF >> gama: g [ - | ans | - ans := nil. - (g > 0) ifTrue: [ ans := 1 ]. - (g = 0) ifTrue: [ ans := 0 ]. - (g < 0) ifTrue: [ ans := 0 ]. - ^ ans -] +Class { + #name : 'RSInvertedCDF', + #superclass : 'RSQuantile', + #category : 'Roassal-Chart-Statistics', + #package : 'Roassal-Chart', + #tag : 'Statistics' +} + +{ #category : 'initialize' } +RSInvertedCDF >> compute: populationPercentage [ + | m j g n| + n := data size. + (n = 1) ifTrue: [ ^ data at: 1 ]. + m := 0. + j := ((populationPercentage * n) + m). + j := j//1. + j = 0 ifTrue: [ j := 1 ]. + g := (populationPercentage * n) + m - j. + ^ ((1 - (self gama: g)) * (data at: j)) + ((self gama: g)*(data at:(j+1))) +] + +{ #category : 'initialize' } +RSInvertedCDF >> gama: g [ + | ans | + ans := nil. + (g > 0) ifTrue: [ ans := 1 ]. + (g = 0) ifTrue: [ ans := 0 ]. + (g < 0) ifTrue: [ ans := 0 ]. + ^ ans +] diff --git a/src/Roassal-Chart/RSKernelDensity.class.st b/src/Roassal-Chart/RSKernelDensity.class.st index d34deba0..66d8fe34 100644 --- a/src/Roassal-Chart/RSKernelDensity.class.st +++ b/src/Roassal-Chart/RSKernelDensity.class.st @@ -1,250 +1,250 @@ -" -`RSKernelDensity` is a class that performs calculations related to density distribution plots (`RSDensityPlot`,`RSViolinPlot`) using the **kernel density estimation (KDE) function**. -Resources to understand the calculations and concepts related: -- https://youtu.be/qc9elACH8LA -- https://mathisonian.github.io/kde/ - -**Responsibility:** calculate density distribution of a dataset. - -**Collaborators:** `RSKernelFunction` represents the Kernel function (by default a Gaussian function) evaluated in the KDE function. - -**Public API and Key Messages** -- `data: aCollection` to create instances passing aCollection (dataset) as argument. -- `evaluateKernel: aNumber` to get the kernel function evaluation of aNumber K(x). -- `kde: aNumber` to get the corresponding density (probability) of an x value. (it sum all the kernels applied over the dataset values). -- `densityCurve`: to apply the `kde` function over a ""continuos"" set of x values and get the y values, for the domain of the dataset passed. - -**Instance Variables:** - -- `data`: aCollection, the dataset for wich the probability density is estimated. -- `bandwidth`: a Float that stores the bandwidth used for the kernel function. -- `kernel`: a `RSKernelFunction` that calculates the corresponding kernel (a `RSKernelGaussianFunction` by default) and has an `evaluate: aNumber` message. - -**Example:** -```Smalltalk -| kernelDensity anXValue data | -data := #(14 15 28 27 32 35). -kernelDensity := RSKernelDensity data: data. -kernelDensity bandwidth: 4. -anXValue := 30. -self assert: (kernelDensity kde: anXValue) closeTo: 0.0495. -``` -" -Class { - #name : 'RSKernelDensity', - #superclass : 'RSObject', - #instVars : [ - 'data', - 'bandwidth', - 'kernel', - 'curveStep', - 'errorMargin' - ], - #category : 'Roassal-Chart-Statistics', - #package : 'Roassal-Chart', - #tag : 'Statistics' -} - -{ #category : 'instance creation' } -RSKernelDensity class >> bandwidth: aNumber [ - ^ self gaussian bandwidth: aNumber -] - -{ #category : 'instance creation' } -RSKernelDensity class >> data: aCollection [ - | kernelDensity | - kernelDensity := self gaussian. - kernelDensity data: aCollection. - ^ kernelDensity -] - -{ #category : 'instance creation' } -RSKernelDensity class >> gaussian [ - | kernelDensity | - kernelDensity := self new. - kernelDensity kernel: RSKernelGaussianFunction new. - ^ kernelDensity -] - -{ #category : 'instance creation' } -RSKernelDensity class >> kernel: anRSKernelFunction [ - | kernelDensity | - kernelDensity := self new. - kernelDensity kernel: anRSKernelFunction. - ^ kernelDensity -] - -{ #category : 'instance creation' } -RSKernelDensity class >> kernel: anRSKernelFunction data: aCollection [ - | kernelDensity | - kernelDensity := self new. - kernelDensity kernel: anRSKernelFunction. - kernelDensity data: aCollection. - ^ kernelDensity -] - -{ #category : 'accessing' } -RSKernelDensity >> bandwidth [ - ^ bandwidth -] - -{ #category : 'accessing' } -RSKernelDensity >> bandwidth: aNumber [ - bandwidth := aNumber abs -] - -{ #category : 'private' } -RSKernelDensity >> computeBandwidth [ - "Based on https://en.wikipedia.org/wiki/Kernel_density_estimation" - | n standardDeviation iqr | - n := data size. - standardDeviation := data standardDeviation. - iqr := data interQuartileRange. - self bandwidth: (0.9 * (standardDeviation min: (iqr/1.34)) * (n raisedTo: (-1/5))) -] - -{ #category : 'private' } -RSKernelDensity >> computeCurveStep [ - | min max diff | - min := data min. - max := data max. - diff := max - min. - diff isZero ifTrue: [ diff := 1 ]. - self curveStep: diff * 0.01 -] - -{ #category : 'accessing' } -RSKernelDensity >> curveStep [ - ^ curveStep -] - -{ #category : 'accessing' } -RSKernelDensity >> curveStep: aNumber [ - curveStep := aNumber abs -] - -{ #category : 'accessing' } -RSKernelDensity >> data [ - ^ data -] - -{ #category : 'accessing' } -RSKernelDensity >> data: aCollection [ - self assert: (aCollection allSatisfy: [ :each | each isNumber ]) - description: 'data: should receive only a collection of numbers'. - data := aCollection. - self computeBandwidth. - self computeCurveStep -] - -{ #category : 'defaults' } -RSKernelDensity >> defaultErrorMargin [ - ^ 0.0001 -] - -{ #category : 'accessing' } -RSKernelDensity >> defaultKernel [ - ^ RSKernelGaussianFunction new -] - -{ #category : 'rendering' } -RSKernelDensity >> densityCurve [ - | curvePoints xi yi maxData minData epsilon step minXDomain maxXDomain maxY | - maxData := data max. - minData := data min. - curvePoints := OrderedCollection new. - epsilon := self errorMargin. - step := self curveStep. - maxY := 0. - - "Calculate left side" - xi := minData. - yi := self kde: xi. - curvePoints add: xi@yi. - maxY := yi max: maxY. - [ yi > epsilon ] whileTrue: [ - xi := xi - step. - yi := self kde: xi. - curvePoints addFirst: xi@yi. - maxY := yi max: maxY. - ]. - minXDomain := xi. - - "Calculate middle" - xi := minData + step. - yi := self kde: xi. - maxY := yi max: maxY. - curvePoints add: xi@yi. - [ xi < maxData ] whileTrue: [ - xi := xi + step. - yi := self kde: xi. - curvePoints add: xi@yi. - maxY := yi max: maxY. - ]. - - "Calculate right side" - [ yi > epsilon ] whileTrue: [ - xi := xi + step. - yi := self kde: xi. - curvePoints add: xi@yi. - maxY := yi max: maxY. - ]. - maxXDomain := xi. - maxY := yi max: maxY. - - ^ curvePoints -] - -{ #category : 'accessing' } -RSKernelDensity >> densityCurveSample: aCollection [ - ^ aCollection collect: [ :value | (self kde: value) ] -] - -{ #category : 'private' } -RSKernelDensity >> errorMargin [ - ^ errorMargin -] - -{ #category : 'private' } -RSKernelDensity >> errorMargin: floatCloseToZero [ - "The error margin will be use to calculate when the curve will be truncated, - how close to zero is needed to stop the iteration" - errorMargin := floatCloseToZero -] - -{ #category : 'accessing' } -RSKernelDensity >> evaluateKernel: aNumber [ - ^ kernel evaluate: aNumber -] - -{ #category : 'initialization' } -RSKernelDensity >> initialize [ - super initialize. - self errorMargin: self defaultErrorMargin. - self kernel: self defaultKernel -] - -{ #category : 'accessing' } -RSKernelDensity >> kde: aNumber [ - | sum h | - sum := 0. - h := bandwidth. - h isZero ifTrue: [ h := 1 ]. - data - do: [ :xi | - sum := sum + (self evaluateKernel: ((aNumber - xi)/h)) - ]. - ^ sum / ((data size) * h) -] - -{ #category : 'accessing' } -RSKernelDensity >> kernel [ - ^ kernel -] - -{ #category : 'accessing' } -RSKernelDensity >> kernel: anRSKernelFunction [ - self assert: (anRSKernelFunction isKindOf: RSKernelFunction) - description: 'Error: The argument passed to kernel is not an RSKernelFunction.'. - kernel := anRSKernelFunction -] +" +`RSKernelDensity` is a class that performs calculations related to density distribution plots (`RSDensityPlot`,`RSViolinPlot`) using the **kernel density estimation (KDE) function**. +Resources to understand the calculations and concepts related: +- https://youtu.be/qc9elACH8LA +- https://mathisonian.github.io/kde/ + +**Responsibility:** calculate density distribution of a dataset. + +**Collaborators:** `RSKernelFunction` represents the Kernel function (by default a Gaussian function) evaluated in the KDE function. + +**Public API and Key Messages** +- `data: aCollection` to create instances passing aCollection (dataset) as argument. +- `evaluateKernel: aNumber` to get the kernel function evaluation of aNumber K(x). +- `kde: aNumber` to get the corresponding density (probability) of an x value. (it sum all the kernels applied over the dataset values). +- `densityCurve`: to apply the `kde` function over a ""continuos"" set of x values and get the y values, for the domain of the dataset passed. + +**Instance Variables:** + +- `data`: aCollection, the dataset for wich the probability density is estimated. +- `bandwidth`: a Float that stores the bandwidth used for the kernel function. +- `kernel`: a `RSKernelFunction` that calculates the corresponding kernel (a `RSKernelGaussianFunction` by default) and has an `evaluate: aNumber` message. + +**Example:** +```Smalltalk +| kernelDensity anXValue data | +data := #(14 15 28 27 32 35). +kernelDensity := RSKernelDensity data: data. +kernelDensity bandwidth: 4. +anXValue := 30. +self assert: (kernelDensity kde: anXValue) closeTo: 0.0495. +``` +" +Class { + #name : 'RSKernelDensity', + #superclass : 'RSObject', + #instVars : [ + 'data', + 'bandwidth', + 'kernel', + 'curveStep', + 'errorMargin' + ], + #category : 'Roassal-Chart-Statistics', + #package : 'Roassal-Chart', + #tag : 'Statistics' +} + +{ #category : 'instance creation' } +RSKernelDensity class >> bandwidth: aNumber [ + ^ self gaussian bandwidth: aNumber +] + +{ #category : 'instance creation' } +RSKernelDensity class >> data: aCollection [ + | kernelDensity | + kernelDensity := self gaussian. + kernelDensity data: aCollection. + ^ kernelDensity +] + +{ #category : 'instance creation' } +RSKernelDensity class >> gaussian [ + | kernelDensity | + kernelDensity := self new. + kernelDensity kernel: RSKernelGaussianFunction new. + ^ kernelDensity +] + +{ #category : 'instance creation' } +RSKernelDensity class >> kernel: anRSKernelFunction [ + | kernelDensity | + kernelDensity := self new. + kernelDensity kernel: anRSKernelFunction. + ^ kernelDensity +] + +{ #category : 'instance creation' } +RSKernelDensity class >> kernel: anRSKernelFunction data: aCollection [ + | kernelDensity | + kernelDensity := self new. + kernelDensity kernel: anRSKernelFunction. + kernelDensity data: aCollection. + ^ kernelDensity +] + +{ #category : 'accessing' } +RSKernelDensity >> bandwidth [ + ^ bandwidth +] + +{ #category : 'accessing' } +RSKernelDensity >> bandwidth: aNumber [ + bandwidth := aNumber abs +] + +{ #category : 'private' } +RSKernelDensity >> computeBandwidth [ + "Based on https://en.wikipedia.org/wiki/Kernel_density_estimation" + | n standardDeviation iqr | + n := data size. + standardDeviation := data standardDeviation. + iqr := data interQuartileRange. + self bandwidth: (0.9 * (standardDeviation min: (iqr/1.34)) * (n raisedTo: (-1/5))) +] + +{ #category : 'private' } +RSKernelDensity >> computeCurveStep [ + | min max diff | + min := data min. + max := data max. + diff := max - min. + diff isZero ifTrue: [ diff := 1 ]. + self curveStep: diff * 0.01 +] + +{ #category : 'accessing' } +RSKernelDensity >> curveStep [ + ^ curveStep +] + +{ #category : 'accessing' } +RSKernelDensity >> curveStep: aNumber [ + curveStep := aNumber abs +] + +{ #category : 'accessing' } +RSKernelDensity >> data [ + ^ data +] + +{ #category : 'accessing' } +RSKernelDensity >> data: aCollection [ + self assert: (aCollection allSatisfy: [ :each | each isNumber ]) + description: 'data: should receive only a collection of numbers'. + data := aCollection. + self computeBandwidth. + self computeCurveStep +] + +{ #category : 'defaults' } +RSKernelDensity >> defaultErrorMargin [ + ^ 0.0001 +] + +{ #category : 'accessing' } +RSKernelDensity >> defaultKernel [ + ^ RSKernelGaussianFunction new +] + +{ #category : 'rendering' } +RSKernelDensity >> densityCurve [ + | curvePoints xi yi maxData minData epsilon step minXDomain maxXDomain maxY | + maxData := data max. + minData := data min. + curvePoints := OrderedCollection new. + epsilon := self errorMargin. + step := self curveStep. + maxY := 0. + + "Calculate left side" + xi := minData. + yi := self kde: xi. + curvePoints add: xi@yi. + maxY := yi max: maxY. + [ yi > epsilon ] whileTrue: [ + xi := xi - step. + yi := self kde: xi. + curvePoints addFirst: xi@yi. + maxY := yi max: maxY. + ]. + minXDomain := xi. + + "Calculate middle" + xi := minData + step. + yi := self kde: xi. + maxY := yi max: maxY. + curvePoints add: xi@yi. + [ xi < maxData ] whileTrue: [ + xi := xi + step. + yi := self kde: xi. + curvePoints add: xi@yi. + maxY := yi max: maxY. + ]. + + "Calculate right side" + [ yi > epsilon ] whileTrue: [ + xi := xi + step. + yi := self kde: xi. + curvePoints add: xi@yi. + maxY := yi max: maxY. + ]. + maxXDomain := xi. + maxY := yi max: maxY. + + ^ curvePoints +] + +{ #category : 'accessing' } +RSKernelDensity >> densityCurveSample: aCollection [ + ^ aCollection collect: [ :value | (self kde: value) ] +] + +{ #category : 'private' } +RSKernelDensity >> errorMargin [ + ^ errorMargin +] + +{ #category : 'private' } +RSKernelDensity >> errorMargin: floatCloseToZero [ + "The error margin will be use to calculate when the curve will be truncated, + how close to zero is needed to stop the iteration" + errorMargin := floatCloseToZero +] + +{ #category : 'accessing' } +RSKernelDensity >> evaluateKernel: aNumber [ + ^ kernel evaluate: aNumber +] + +{ #category : 'initialization' } +RSKernelDensity >> initialize [ + super initialize. + self errorMargin: self defaultErrorMargin. + self kernel: self defaultKernel +] + +{ #category : 'accessing' } +RSKernelDensity >> kde: aNumber [ + | sum h | + sum := 0. + h := bandwidth. + h isZero ifTrue: [ h := 1 ]. + data + do: [ :xi | + sum := sum + (self evaluateKernel: ((aNumber - xi)/h)) + ]. + ^ sum / ((data size) * h) +] + +{ #category : 'accessing' } +RSKernelDensity >> kernel [ + ^ kernel +] + +{ #category : 'accessing' } +RSKernelDensity >> kernel: anRSKernelFunction [ + self assert: (anRSKernelFunction isKindOf: RSKernelFunction) + description: 'Error: The argument passed to kernel is not an RSKernelFunction.'. + kernel := anRSKernelFunction +] diff --git a/src/Roassal-Chart/RSKernelFunction.class.st b/src/Roassal-Chart/RSKernelFunction.class.st index 260ef409..15d2c296 100644 --- a/src/Roassal-Chart/RSKernelFunction.class.st +++ b/src/Roassal-Chart/RSKernelFunction.class.st @@ -1,17 +1,17 @@ -Class { - #name : 'RSKernelFunction', - #superclass : 'RSObject', - #category : 'Roassal-Chart-Statistics', - #package : 'Roassal-Chart', - #tag : 'Statistics' -} - -{ #category : 'instance creation' } -RSKernelFunction class >> gaussian [ - ^ RSKernelGaussianFunction new -] - -{ #category : 'accessing' } -RSKernelFunction >> evaluate: aNumber [ - ^ self subclassResponsibility -] +Class { + #name : 'RSKernelFunction', + #superclass : 'RSObject', + #category : 'Roassal-Chart-Statistics', + #package : 'Roassal-Chart', + #tag : 'Statistics' +} + +{ #category : 'instance creation' } +RSKernelFunction class >> gaussian [ + ^ RSKernelGaussianFunction new +] + +{ #category : 'accessing' } +RSKernelFunction >> evaluate: aNumber [ + ^ self subclassResponsibility +] diff --git a/src/Roassal-Chart/RSKernelGaussianFunction.class.st b/src/Roassal-Chart/RSKernelGaussianFunction.class.st index 973b1bce..b687662c 100644 --- a/src/Roassal-Chart/RSKernelGaussianFunction.class.st +++ b/src/Roassal-Chart/RSKernelGaussianFunction.class.st @@ -1,12 +1,12 @@ -Class { - #name : 'RSKernelGaussianFunction', - #superclass : 'RSKernelFunction', - #category : 'Roassal-Chart-Statistics', - #package : 'Roassal-Chart', - #tag : 'Statistics' -} - -{ #category : 'accessing' } -RSKernelGaussianFunction >> evaluate: aNumber [ - ^ ((0-((aNumber squared)/2))) exp / ((2 * (Float pi)) sqrt) -] +Class { + #name : 'RSKernelGaussianFunction', + #superclass : 'RSKernelFunction', + #category : 'Roassal-Chart-Statistics', + #package : 'Roassal-Chart', + #tag : 'Statistics' +} + +{ #category : 'accessing' } +RSKernelGaussianFunction >> evaluate: aNumber [ + ^ ((0-((aNumber squared)/2))) exp / ((2 * (Float pi)) sqrt) +] diff --git a/src/Roassal-Chart/RSKiviat.class.st b/src/Roassal-Chart/RSKiviat.class.st index 8ff53964..026215f1 100644 --- a/src/Roassal-Chart/RSKiviat.class.st +++ b/src/Roassal-Chart/RSKiviat.class.st @@ -1,410 +1,426 @@ -" -this class is related to radar visualization -" -Class { - #name : 'RSKiviat', - #superclass : 'RSBuilder', - #instVars : [ - 'values', - 'axisNames', - 'radius', - 'shouldUseEllipse', - 'labelGapSize', - 'scales', - 'polygonShape', - 'palette', - 'shouldUsePolygonBorder', - 'shouldUseDots', - 'shouldUsePolygonFillColor', - 'labels', - 'axis', - 'backgroundShapes', - 'polygonShapes', - 'maxValue', - 'minValue' - ], - #category : 'Roassal-Chart-Radar', - #package : 'Roassal-Chart', - #tag : 'Radar' -} - -{ #category : 'accessing' } -RSKiviat >> addRow: aList [ - values add: aList -] - -{ #category : 'adding' } -RSKiviat >> addRows: aCollection [ - values addAll: aCollection -] - -{ #category : 'accessing' } -RSKiviat >> axis [ - ^ axis -] - -{ #category : 'accessing' } -RSKiviat >> axisNames [ - ^ axisNames -] - -{ #category : 'accessing' } -RSKiviat >> axisNames: aCollection [ - axisNames := aCollection -] - -{ #category : 'accessing' } -RSKiviat >> backgroundColor [ - ^ '#e5ebf6' -] - -{ #category : 'accessing' } -RSKiviat >> backgroundShapes [ - ^ backgroundShapes -] - -{ #category : 'accessing' } -RSKiviat >> border [ - ^ RSBorder new - color: Color white; - yourself -] - -{ #category : 'accessing - computed' } -RSKiviat >> computePointsFor: aCollection [ - | delta current divisor | - divisor := axisNames ifEmpty: [ 1 ] ifNotEmpty: [ axisNames size ]. - delta := Float twoPi / divisor. - current := Float halfPi negated. - ^ aCollection collectWithIndex: [ :value :index | | indexScale point newRadius | - indexScale := scales scale: index. - newRadius := (indexScale scale: value). - point := current cos @ current sin * newRadius. - current := current + delta. - point ] -] - -{ #category : 'private' } -RSKiviat >> computeScaleFor: array [ - | min max | - min := minValue - ifNotNil: [ minValue ] - ifNil: [ (array ifEmpty: 0 ifNotEmpty: [ values first min ]) min: 0 ]. - max := maxValue - ifNotNil: [ maxValue ] - ifNil: [ array ifEmpty: 1 ifNotEmpty: [ values first max ] ]. - - ^ NSScale linear domain: { min. max }; range: { 0. radius } -] - -{ #category : 'private' } -RSKiviat >> computeScales [ - scales := NSScale ordinal. - values size = 1 ifTrue: [ - scales range: { self computeScaleFor: values first } - ] ifFalse: [ - scales range: (axisNames collectWithIndex: [ :each :index | - | column | - column := values collect: [ :array | array at: index ]. - self computeScaleFor: column. - ]). - ] -] - -{ #category : 'creation' } -RSKiviat >> createAxisLine: name angle: angle [ - ^ RSLine new - endPoint: angle cos @ angle sin * radius; - border: self border; - yourself -] - -{ #category : 'creation' } -RSKiviat >> createAxisName: aString [ - ^ RSLabel new - text: aString; - yourself -] - -{ #category : 'creation' } -RSKiviat >> createBorderFor: aRSPolygon [ - ^ RSBorder new - color: aRSPolygon color muchDarker; - yourself -] - -{ #category : 'creation' } -RSKiviat >> createEllipseFor: each [ - ^ RSEllipse new - noPaint; - size: each*2; - border: self border; - yourself -] - -{ #category : 'creation' } -RSKiviat >> createPolygonFor: each [ - ^ RSPolygon new - noPaint; - points: (RSPolygon - generateUnitNgonPoints: axisNames size - rotation: 0)* each; - border: self border; - yourself -] - -{ #category : 'creation' } -RSKiviat >> createShapePointFor: aPoint in: aRSPolygon [ - ^ RSEllipse new - color: aRSPolygon color muchDarker; - position: aPoint; - size: 10; - yourself -] - -{ #category : 'creation' } -RSKiviat >> createValuePolygon: array [ - | polygon points shapePoints | - points := self computePointsFor: array. - polygon := self polygonShape copy - points: points; - color: (palette scale: array); - yourself. - self shouldUsePolygonBorder - ifTrue: [ polygon border: (self createBorderFor: polygon) ]. - - self shouldUseDots ifTrue: [ - shapePoints := points collect: [:point | self createShapePointFor: point in: polygon ]. - shapePoints with: array do: [ :s :value | s model: value ]. - self shouldUsePolygonFillColor ifFalse: [ polygon noPaint ]. - ^ ({ polygon }, shapePoints) asShape ]. - self shouldUsePolygonFillColor ifFalse: [ polygon noPaint ]. - ^ polygon -] - -{ #category : 'accessing - defaults' } -RSKiviat >> defaultContainer [ - ^ RSCanvas new @ RSCanvasController -] - -{ #category : 'defaults' } -RSKiviat >> defaultPalette [ - ^ NSScale ordinal range: (NSScale category20 range collect: [:color| color translucent ]) -] - -{ #category : 'utilities' } -RSKiviat >> fixLabelPosition: label angle: angle [ - | positions gap | - label position: angle cos @ angle sin * radius. - gap := self labelGapSize. - positions := Dictionary newFromPairs: - { 0. 0.5@0. - Float halfPi negated. 0@ -0.5. - Float pi. -0.5@ 0. - Float pi + Float halfPi. 0@ 0.5}. - positions at: angle - ifPresent: [:fix | label translateBy: label extent * fix+ (gap * fix sign) ] - ifAbsent: [ - gap := angle cos @ angle sin * gap. - (angle between: Float halfPi negated and: 0) - ifTrue: [ ^ label translateBy: label baseRectangle bottomLeft negated+gap ]. - (angle between: 0 and: Float halfPi) - ifTrue: [ ^ label translateBy: label baseRectangle topLeft negated+gap ]. - (angle between: Float halfPi and: Float pi) - ifTrue: [ ^ label translateBy: label baseRectangle topRight negated+gap ]. - label translateBy: label baseRectangle bottomRight negated + gap ] -] - -{ #category : 'initialization' } -RSKiviat >> initialize [ - super initialize. - values := OrderedCollection new. - polygonShape := RSPolygon new. - self - palette: self defaultPalette; - radius: 200; - labelGapSize: 5; - noPolygonBorder; - useEllipse; - usePolygonFillColor; - noDots -] - -{ #category : 'accessing' } -RSKiviat >> labelGapSize [ - ^ labelGapSize -] - -{ #category : 'accessing' } -RSKiviat >> labelGapSize: aNumber [ - labelGapSize := aNumber -] - -{ #category : 'accessing' } -RSKiviat >> labels [ - ^ labels -] - -{ #category : 'accessing' } -RSKiviat >> maxValue [ - ^ maxValue -] - -{ #category : 'accessing' } -RSKiviat >> maxValue: aNumber [ - maxValue := aNumber -] - -{ #category : 'accessing' } -RSKiviat >> minValue [ - ^ minValue -] - -{ #category : 'accessing' } -RSKiviat >> minValue: aNumber [ - minValue := aNumber -] - -{ #category : 'public' } -RSKiviat >> noDots [ - shouldUseDots := false -] - -{ #category : 'public' } -RSKiviat >> noPolygonBorder [ - shouldUsePolygonBorder := false -] - -{ #category : 'public' } -RSKiviat >> noPolygonFillColor [ - shouldUsePolygonFillColor := false -] - -{ #category : 'accessing' } -RSKiviat >> palette [ - ^ palette -] - -{ #category : 'accessing' } -RSKiviat >> palette: aNSOrdinalScale [ - palette := aNSOrdinalScale -] - -{ #category : 'accessing' } -RSKiviat >> polygonShape [ - ^ polygonShape -] - -{ #category : 'accessing' } -RSKiviat >> polygonShapes [ - ^ polygonShapes -] - -{ #category : 'accessing' } -RSKiviat >> radius [ - ^ radius -] - -{ #category : 'accessing' } -RSKiviat >> radius: aNumber [ - radius := aNumber -] - -{ #category : 'hooks' } -RSKiviat >> renderAxisIn: aCanvas [ - | delta current deltaRadius divisor | - divisor := axisNames ifEmpty: [ 1 ] ifNotEmpty: [ axisNames size ]. - delta := Float twoPi / divisor. - current := Float halfPi negated. - axis := RSGroup new. - labels := axisNames collect: [ :name | | label | - label := self createAxisName: name. - self fixLabelPosition: label angle: current. - axis add: (self createAxisLine: name angle: current). - current := current + delta. - label ]. - deltaRadius := self tickStep. "should we use nice label generator for ticks?" - backgroundShapes := (deltaRadius to: radius by: deltaRadius) - collect: [ :each | shouldUseEllipse - ifTrue: [ self createEllipseFor: each ] - ifFalse: [ self createPolygonFor: each ] ]. - - aCanvas addAll: backgroundShapes. - backgroundShapes last - color: self backgroundColor; "should I draw rings insteadof one one ellipse" - pushBack. - aCanvas addAll: axis. - aCanvas addAll: labels -] - -{ #category : 'rendering' } -RSKiviat >> renderIn: aCanvas [ - self assert: values isNotEmpty description: 'Use addRow: first'. - axisNames ifNil: [ axisNames := values first collectWithIndex: [ :val :i | i asString ] ]. - self assert: values first size = axisNames size description: 'can not be different'. - self renderAxisIn: aCanvas. - self renderValuesIn: aCanvas. - shapes := backgroundShapes, axis, labels, polygonShapes -] - -{ #category : 'hooks' } -RSKiviat >> renderValuesIn: aCanvas [ - self computeScales. - polygonShapes := values collect: [ :array | self createValuePolygon: array ]. - aCanvas addAll: polygonShapes -] - -{ #category : 'testing' } -RSKiviat >> shouldUseDots [ - ^ shouldUseDots -] - -{ #category : 'testing' } -RSKiviat >> shouldUseEllipse [ - ^ shouldUseEllipse -] - -{ #category : 'testing' } -RSKiviat >> shouldUsePolygonBorder [ - ^ shouldUsePolygonBorder -] - -{ #category : 'testing' } -RSKiviat >> shouldUsePolygonFillColor [ - ^ shouldUsePolygonFillColor -] - -{ #category : 'accessing - computed' } -RSKiviat >> tickStep [ - ^ radius / 5 -] - -{ #category : 'public' } -RSKiviat >> useDots [ - shouldUseDots := true -] - -{ #category : 'public' } -RSKiviat >> useEllipse [ - shouldUseEllipse := true -] - -{ #category : 'public' } -RSKiviat >> usePolygon [ - shouldUseEllipse := false -] - -{ #category : 'public' } -RSKiviat >> usePolygonBorder [ - shouldUsePolygonBorder := true -] - -{ #category : 'public' } -RSKiviat >> usePolygonFillColor [ - shouldUsePolygonFillColor := true -] - -{ #category : 'accessing' } -RSKiviat >> values [ - ^ values -] +" +this class is related to radar visualization +" +Class { + #name : 'RSKiviat', + #superclass : 'RSBuilder', + #instVars : [ + 'values', + 'axisNames', + 'radius', + 'shouldUseEllipse', + 'labelGapSize', + 'scales', + 'polygonShape', + 'palette', + 'shouldUsePolygonBorder', + 'shouldUseDots', + 'shouldUsePolygonFillColor', + 'labels', + 'axis', + 'backgroundShapes', + 'polygonShapes', + 'maxValue', + 'minValue' + ], + #category : 'Roassal-Chart-Radar', + #package : 'Roassal-Chart', + #tag : 'Radar' +} + +{ #category : 'accessing' } +RSKiviat >> addRow: aList [ + values add: aList +] + +{ #category : 'adding' } +RSKiviat >> addRows: aCollection [ + values addAll: aCollection +] + +{ #category : 'accessing' } +RSKiviat >> axis [ + ^ axis +] + +{ #category : 'accessing' } +RSKiviat >> axisNames [ + ^ axisNames +] + +{ #category : 'accessing' } +RSKiviat >> axisNames: aCollection [ + axisNames := aCollection +] + +{ #category : 'accessing' } +RSKiviat >> backgroundColor [ + ^ '#e5ebf6' +] + +{ #category : 'accessing' } +RSKiviat >> backgroundShapes [ + ^ backgroundShapes +] + +{ #category : 'accessing' } +RSKiviat >> border [ + ^ RSBorder new + color: Color white; + yourself +] + +{ #category : 'accessing - computed' } +RSKiviat >> computePointsFor: aCollection [ + | delta current divisor | + divisor := axisNames ifEmpty: [ 1 ] ifNotEmpty: [ axisNames size ]. + delta := Float twoPi / divisor. + current := Float halfPi negated. + ^ aCollection collectWithIndex: [ :value :index | | indexScale point newRadius | + indexScale := scales scale: index. + newRadius := (indexScale scale: value). + point := current cos @ current sin * newRadius. + current := current + delta. + point ] +] + +{ #category : 'private' } +RSKiviat >> computeScaleFor: array [ + | min max | + min := minValue + ifNotNil: [ minValue ] + ifNil: [ (array ifEmpty: 0 ifNotEmpty: [ values first min ]) min: 0 ]. + max := maxValue + ifNotNil: [ maxValue ] + ifNil: [ array ifEmpty: 1 ifNotEmpty: [ values first max ] ]. + + ^ NSScale linear domain: { min. max }; range: { 0. radius } +] + +{ #category : 'private' } +RSKiviat >> computeScales [ + scales := NSScale ordinal. + values size = 1 ifTrue: [ + scales range: { self computeScaleFor: values first } + ] ifFalse: [ + scales range: (axisNames collectWithIndex: [ :each :index | + | column | + column := values collect: [ :array | array at: index ]. + self computeScaleFor: column. + ]). + ] +] + +{ #category : 'creation' } +RSKiviat >> createAxisLine: name angle: angle [ + ^ RSLine new + endPoint: angle cos @ angle sin * radius; + border: self border; + yourself +] + +{ #category : 'creation' } +RSKiviat >> createAxisName: aString [ + ^ RSLabel new + text: aString; + yourself +] + +{ #category : 'creation' } +RSKiviat >> createBorderFor: aRSPolygon [ + ^ RSBorder new + color: aRSPolygon color muchDarker; + yourself +] + +{ #category : 'creation' } +RSKiviat >> createEllipseFor: each [ + ^ RSEllipse new + noPaint; + size: each*2; + border: self border; + yourself +] + +{ #category : 'creation' } +RSKiviat >> createPolygonFor: each [ + "create the background polygon shape if the variable #shouldUseEllipse equals False. + + The polygon must be created with a rotation if the number of axis is even." + | rotation | + rotation := 0. + axisNames size even ifTrue: [ rotation := Float pi / axisNames size ]. + + ^ RSPolygon new + noPaint; + points: + (RSPolygon generateUnitNgonPoints: axisNames size rotation: rotation) + * each; + border: self border; + yourself +] + +{ #category : 'creation' } +RSKiviat >> createShapePointFor: aPoint in: aRSPolygon [ + ^ RSEllipse new + color: aRSPolygon color muchDarker; + position: aPoint; + size: 10; + yourself +] + +{ #category : 'creation' } +RSKiviat >> createValuePolygon: array [ + | polygon points shapePoints | + points := self computePointsFor: array. + polygon := self polygonShape copy + points: points; + color: (palette scale: array); + yourself. + self shouldUsePolygonBorder + ifTrue: [ polygon border: (self createBorderFor: polygon) ]. + + self shouldUseDots ifTrue: [ + shapePoints := points collect: [:point | self createShapePointFor: point in: polygon ]. + shapePoints with: array do: [ :s :value | s model: value ]. + self shouldUsePolygonFillColor ifFalse: [ polygon noPaint ]. + ^ ({ polygon }, shapePoints) asShape ]. + self shouldUsePolygonFillColor ifFalse: [ polygon noPaint ]. + ^ polygon +] + +{ #category : 'accessing - defaults' } +RSKiviat >> defaultContainer [ + ^ RSCanvas new @ RSCanvasController +] + +{ #category : 'defaults' } +RSKiviat >> defaultPalette [ + ^ NSScale ordinal range: (NSScale category20 range collect: [:color| color translucent ]) +] + +{ #category : 'utilities' } +RSKiviat >> fixLabelPosition: label angle: angle [ + + | positions gap | + label position: angle cos @ angle sin * radius. + gap := self labelGapSize. + positions := Dictionary newFromPairs: { + 0. + (0.5 @ 0). + Float halfPi. + (0 @ 0.5). + Float halfPi negated. + (0 @ -0.5). + Float pi. + (-0.5 @ 0). + (Float pi + Float halfPi). + (0 @ 0.5) }. + positions + at: angle + ifPresent: [ :fix | + label translateBy: label extent * fix + (gap * fix sign) ] + ifAbsent: [ + gap := angle cos @ angle sin * gap. + (angle between: Float halfPi negated and: 0) ifTrue: [ + ^ label translateBy: label baseRectangle bottomLeft negated + gap ]. + (angle between: 0 and: Float halfPi) ifTrue: [ + ^ label translateBy: label baseRectangle topLeft negated + gap ]. + (angle between: Float halfPi and: Float pi) ifTrue: [ + ^ label translateBy: label baseRectangle topRight negated + gap ]. + label translateBy: label baseRectangle bottomRight negated + gap ] +] + +{ #category : 'initialization' } +RSKiviat >> initialize [ + super initialize. + values := OrderedCollection new. + polygonShape := RSPolygon new. + self + palette: self defaultPalette; + radius: 200; + labelGapSize: 5; + noPolygonBorder; + useEllipse; + usePolygonFillColor; + noDots +] + +{ #category : 'accessing' } +RSKiviat >> labelGapSize [ + ^ labelGapSize +] + +{ #category : 'accessing' } +RSKiviat >> labelGapSize: aNumber [ + labelGapSize := aNumber +] + +{ #category : 'accessing' } +RSKiviat >> labels [ + ^ labels +] + +{ #category : 'accessing' } +RSKiviat >> maxValue [ + ^ maxValue +] + +{ #category : 'accessing' } +RSKiviat >> maxValue: aNumber [ + maxValue := aNumber +] + +{ #category : 'accessing' } +RSKiviat >> minValue [ + ^ minValue +] + +{ #category : 'accessing' } +RSKiviat >> minValue: aNumber [ + minValue := aNumber +] + +{ #category : 'public' } +RSKiviat >> noDots [ + shouldUseDots := false +] + +{ #category : 'public' } +RSKiviat >> noPolygonBorder [ + shouldUsePolygonBorder := false +] + +{ #category : 'public' } +RSKiviat >> noPolygonFillColor [ + shouldUsePolygonFillColor := false +] + +{ #category : 'accessing' } +RSKiviat >> palette [ + ^ palette +] + +{ #category : 'accessing' } +RSKiviat >> palette: aNSOrdinalScale [ + palette := aNSOrdinalScale +] + +{ #category : 'accessing' } +RSKiviat >> polygonShape [ + ^ polygonShape +] + +{ #category : 'accessing' } +RSKiviat >> polygonShapes [ + ^ polygonShapes +] + +{ #category : 'accessing' } +RSKiviat >> radius [ + ^ radius +] + +{ #category : 'accessing' } +RSKiviat >> radius: aNumber [ + radius := aNumber +] + +{ #category : 'hooks' } +RSKiviat >> renderAxisIn: aCanvas [ + | delta current deltaRadius divisor | + divisor := axisNames ifEmpty: [ 1 ] ifNotEmpty: [ axisNames size ]. + delta := Float twoPi / divisor. + current := Float halfPi negated. + axis := RSGroup new. + labels := axisNames collect: [ :name | | label | + label := self createAxisName: name. + self fixLabelPosition: label angle: current. + axis add: (self createAxisLine: name angle: current). + current := current + delta. + label ]. + deltaRadius := self tickStep. "should we use nice label generator for ticks?" + backgroundShapes := (deltaRadius to: radius by: deltaRadius) + collect: [ :each | shouldUseEllipse + ifTrue: [ self createEllipseFor: each ] + ifFalse: [ self createPolygonFor: each ] ]. + + aCanvas addAll: backgroundShapes. + backgroundShapes last + color: self backgroundColor; "should I draw rings insteadof one one ellipse" + pushBack. + aCanvas addAll: axis. + aCanvas addAll: labels +] + +{ #category : 'rendering' } +RSKiviat >> renderIn: aCanvas [ + self assert: values isNotEmpty description: 'Use addRow: first'. + axisNames ifNil: [ axisNames := values first collectWithIndex: [ :val :i | i asString ] ]. + self assert: values first size = axisNames size description: 'can not be different'. + self renderAxisIn: aCanvas. + self renderValuesIn: aCanvas. + shapes := backgroundShapes, axis, labels, polygonShapes +] + +{ #category : 'hooks' } +RSKiviat >> renderValuesIn: aCanvas [ + self computeScales. + polygonShapes := values collect: [ :array | self createValuePolygon: array ]. + aCanvas addAll: polygonShapes +] + +{ #category : 'testing' } +RSKiviat >> shouldUseDots [ + ^ shouldUseDots +] + +{ #category : 'testing' } +RSKiviat >> shouldUseEllipse [ + ^ shouldUseEllipse +] + +{ #category : 'testing' } +RSKiviat >> shouldUsePolygonBorder [ + ^ shouldUsePolygonBorder +] + +{ #category : 'testing' } +RSKiviat >> shouldUsePolygonFillColor [ + ^ shouldUsePolygonFillColor +] + +{ #category : 'accessing - computed' } +RSKiviat >> tickStep [ + ^ radius / 5 +] + +{ #category : 'public' } +RSKiviat >> useDots [ + shouldUseDots := true +] + +{ #category : 'public' } +RSKiviat >> useEllipse [ + shouldUseEllipse := true +] + +{ #category : 'public' } +RSKiviat >> usePolygon [ + shouldUseEllipse := false +] + +{ #category : 'public' } +RSKiviat >> usePolygonBorder [ + shouldUsePolygonBorder := true +] + +{ #category : 'public' } +RSKiviat >> usePolygonFillColor [ + shouldUsePolygonFillColor := true +] + +{ #category : 'accessing' } +RSKiviat >> values [ + ^ values +] diff --git a/src/Roassal-Chart/RSLineChartPopupBuilder.class.st b/src/Roassal-Chart/RSLineChartPopupBuilder.class.st index 5c5a9b35..3a4f12a7 100644 --- a/src/Roassal-Chart/RSLineChartPopupBuilder.class.st +++ b/src/Roassal-Chart/RSLineChartPopupBuilder.class.st @@ -1,52 +1,52 @@ -" -TODO -" -Class { - #name : 'RSLineChartPopupBuilder', - #superclass : 'RSAbstractChartPopupBuilder', - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'hooks' } -RSLineChartPopupBuilder >> rowShapeFor: aRSLinePlot [ - | valuePoint | - valuePoint := self valuePointFor: aRSLinePlot. - valuePoint ifNil: [ ^ nil ]. - ^ self rowShapeFor: aRSLinePlot point: valuePoint -] - -{ #category : 'hooks' } -RSLineChartPopupBuilder >> rowShapeFor: aRSLinePlot point: aPoint [ - ^ self subclassResponsibility -] - -{ #category : 'hooks' } -RSLineChartPopupBuilder >> shapeFor: aRSChart [ - | plots | - plots := aRSChart plots select: #isLinePlot. - plots := plots - collect: [ :plot | self rowShapeFor: plot ] - as: RSGroup. - plots := plots reject: #isNil. - plots ifEmpty: [ ^ RSBox new size: 0 ]. - RSVerticalLineLayout new gapSize: 0; on: plots. - ^ plots asShape - padding: 10; - color: (Color white alpha: 0.8); - withBorder; - yourself -] - -{ #category : 'hooks' } -RSLineChartPopupBuilder >> valuePointFor: aRSLinePlot [ - | xValue yValue index | - xValue := (aRSLinePlot xScale invert: position x) roundTo: 1. - index := aRSLinePlot xValues indexOf: xValue. - index = 0 ifFalse: [ xValue := index ]. - (xValue between: 1 and: aRSLinePlot yValues size) - ifFalse: [ ^ nil ]. - yValue := aRSLinePlot yValues at: xValue. - ^ xValue @ yValue -] +" +TODO +" +Class { + #name : 'RSLineChartPopupBuilder', + #superclass : 'RSAbstractChartPopupBuilder', + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'hooks' } +RSLineChartPopupBuilder >> rowShapeFor: aRSLinePlot [ + | valuePoint | + valuePoint := self valuePointFor: aRSLinePlot. + valuePoint ifNil: [ ^ nil ]. + ^ self rowShapeFor: aRSLinePlot point: valuePoint +] + +{ #category : 'hooks' } +RSLineChartPopupBuilder >> rowShapeFor: aRSLinePlot point: aPoint [ + ^ self subclassResponsibility +] + +{ #category : 'hooks' } +RSLineChartPopupBuilder >> shapeFor: aRSChart [ + | plots | + plots := aRSChart plots select: #isLinePlot. + plots := plots + collect: [ :plot | self rowShapeFor: plot ] + as: RSGroup. + plots := plots reject: #isNil. + plots ifEmpty: [ ^ RSBox new size: 0 ]. + RSVerticalLineLayout new gapSize: 0; on: plots. + ^ plots asShape + padding: 10; + color: (Color white alpha: 0.8); + withBorder; + yourself +] + +{ #category : 'hooks' } +RSLineChartPopupBuilder >> valuePointFor: aRSLinePlot [ + | xValue yValue index | + xValue := (aRSLinePlot xScale invert: position x) roundTo: 1. + index := aRSLinePlot xValues indexOf: xValue. + index = 0 ifFalse: [ xValue := index ]. + (xValue between: 1 and: aRSLinePlot yValues size) + ifFalse: [ ^ nil ]. + yValue := aRSLinePlot yValues at: xValue. + ^ xValue @ yValue +] diff --git a/src/Roassal-Chart/RSLinePlot.class.st b/src/Roassal-Chart/RSLinePlot.class.st index ad0cf005..3791b326 100644 --- a/src/Roassal-Chart/RSLinePlot.class.st +++ b/src/Roassal-Chart/RSLinePlot.class.st @@ -1,73 +1,73 @@ -" -RSLinePlot is a class to create an y vs x plot. -" -Class { - #name : 'RSLinePlot', - #superclass : 'RSAbstractPlot', - #traits : 'RSTLine', - #classTraits : 'RSTLine classTrait', - #instVars : [ - 'line' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'instance creation' } -RSLinePlot class >> example1 [ - | p | - p := (self y: #(1 2 3 4)) format: 'or--'. - ^ p open -] - -{ #category : 'instance creation' } -RSLinePlot class >> y: aCollection [ - | linePlot | - linePlot := self new. - linePlot y: aCollection. - ^ linePlot -] - -{ #category : 'accessing' } -RSLinePlot >> createdShapes [ - ^ { line } -] - -{ #category : 'accessing - defaults' } -RSLinePlot >> defaultShape [ - ^ RSPolyline new color: nil -] - -{ #category : 'testing' } -RSLinePlot >> isLinePlot [ - ^ true -] - -{ #category : 'accessing' } -RSLinePlot >> line [ - ^ line -] - -{ #category : 'constants' } -RSLinePlot >> marker [ - ^ self shape markers at: 1 -] - -{ #category : 'rendering' } -RSLinePlot >> renderIn: canvas [ - | controlPoints | - super renderIn: canvas. - self checkAssertion. - controlPoints := OrderedCollection new. - (1 to: xValues size) do: [ :i | - | xt yt | - xt := xValues at: i. - yt := yValues at: i. - (self isPointWellDefined: xt @ yt) - ifTrue: [ controlPoints add: (self scalePoint: xt @ yt) ] ]. - canvas add: (line := self shape copy - color: self computeColor; - controlPoints: controlPoints; - yourself) -] +" +RSLinePlot is a class to create an y vs x plot. +" +Class { + #name : 'RSLinePlot', + #superclass : 'RSAbstractPlot', + #traits : 'RSTLine', + #classTraits : 'RSTLine classTrait', + #instVars : [ + 'line' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'instance creation' } +RSLinePlot class >> example1 [ + | p | + p := (self y: #(1 2 3 4)) format: 'or--'. + ^ p open +] + +{ #category : 'instance creation' } +RSLinePlot class >> y: aCollection [ + | linePlot | + linePlot := self new. + linePlot y: aCollection. + ^ linePlot +] + +{ #category : 'accessing' } +RSLinePlot >> createdShapes [ + ^ { line } +] + +{ #category : 'accessing - defaults' } +RSLinePlot >> defaultShape [ + ^ RSPolyline new color: nil +] + +{ #category : 'testing' } +RSLinePlot >> isLinePlot [ + ^ true +] + +{ #category : 'accessing' } +RSLinePlot >> line [ + ^ line +] + +{ #category : 'constants' } +RSLinePlot >> marker [ + ^ self shape markers at: 1 +] + +{ #category : 'rendering' } +RSLinePlot >> renderIn: canvas [ + | controlPoints | + super renderIn: canvas. + self checkAssertion. + controlPoints := OrderedCollection new. + (1 to: xValues size) do: [ :i | + | xt yt | + xt := xValues at: i. + yt := yValues at: i. + (self isPointWellDefined: xt @ yt) + ifTrue: [ controlPoints add: (self scalePoint: xt @ yt) ] ]. + canvas add: (line := self shape copy + color: self computeColor; + controlPoints: controlPoints; + yourself) +] diff --git a/src/Roassal-Chart/RSLineSpineDecoration.class.st b/src/Roassal-Chart/RSLineSpineDecoration.class.st index 5e840bdf..e14f1215 100644 --- a/src/Roassal-Chart/RSLineSpineDecoration.class.st +++ b/src/Roassal-Chart/RSLineSpineDecoration.class.st @@ -1,50 +1,50 @@ -" -Used to draw line axis -" -Class { - #name : 'RSLineSpineDecoration', - #superclass : 'RSChartSpineDecoration', - #traits : 'RSTLine', - #classTraits : 'RSTLine classTrait', - #instVars : [ - 'axisX', - 'axisY' - ], - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'initialization' } -RSLineSpineDecoration >> defaultShape [ - ^ RSLine new -] - -{ #category : 'rendering' } -RSLineSpineDecoration >> renderIn: canvas [ - | x y zeroPoint | - axisX := self shape copy. - zeroPoint := self zeroPoint. - x := zeroPoint x. - y := zeroPoint y. - axisX startPoint: 0@ y; endPoint: chart extent x @ y. - - axisY := self shape copy. - axisY startPoint: x @ chart extent y; endPoint: x @ 0. - - box := { axisX. axisY } asShape. - box translateTo: (chart extent x / 2) @ (chart extent y negated / 2). - canvas add: box -] - -{ #category : 'accessing' } -RSLineSpineDecoration >> zeroPoint [ - | x y | - y := (0 between: chart minChartValueY and: chart maxChartValueY) - ifTrue: [ (chart yScale scale: 0) + chart extent y ] - ifFalse: [ chart extent y ]. - x := (0 between: chart minChartValueX and: chart maxChartValueX) - ifTrue: [ (chart xScale scale: 0) ] - ifFalse: [ 0 ]. - ^ x @ y -] +" +Used to draw line axis +" +Class { + #name : 'RSLineSpineDecoration', + #superclass : 'RSChartSpineDecoration', + #traits : 'RSTLine', + #classTraits : 'RSTLine classTrait', + #instVars : [ + 'axisX', + 'axisY' + ], + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'initialization' } +RSLineSpineDecoration >> defaultShape [ + ^ RSLine new +] + +{ #category : 'rendering' } +RSLineSpineDecoration >> renderIn: canvas [ + | x y zeroPoint | + axisX := self shape copy. + zeroPoint := self zeroPoint. + x := zeroPoint x. + y := zeroPoint y. + axisX startPoint: 0@ y; endPoint: chart extent x @ y. + + axisY := self shape copy. + axisY startPoint: x @ chart extent y; endPoint: x @ 0. + + box := { axisX. axisY } asShape. + box translateTo: (chart extent x / 2) @ (chart extent y negated / 2). + canvas add: box +] + +{ #category : 'accessing' } +RSLineSpineDecoration >> zeroPoint [ + | x y | + y := (0 between: chart minChartValueY and: chart maxChartValueY) + ifTrue: [ (chart yScale scale: 0) + chart extent y ] + ifFalse: [ chart extent y ]. + x := (0 between: chart minChartValueX and: chart maxChartValueX) + ifTrue: [ (chart xScale scale: 0) ] + ifFalse: [ 0 ]. + ^ x @ y +] diff --git a/src/Roassal-Chart/RSLinearLocator.class.st b/src/Roassal-Chart/RSLinearLocator.class.st index a435fe1d..b8552314 100644 --- a/src/Roassal-Chart/RSLinearLocator.class.st +++ b/src/Roassal-Chart/RSLinearLocator.class.st @@ -1,21 +1,21 @@ -" - -`RSLinearLocator` places ticks at regular intervals between the minimum and the maximum data value, even if the value of the ticks are not ""round"". - -*Responsibility*: Places ticks linearly. - -*Collaborators*: `RSLinearLocator` is used when rendering ticks. -" -Class { - #name : 'RSLinearLocator', - #superclass : 'RSTickLocator', - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'generate' } -RSLinearLocator >> generateTicks: aScale with: numberOfTicks [ - - ^ aScale ticks: numberOfTicks -] +" + +`RSLinearLocator` places ticks at regular intervals between the minimum and the maximum data value, even if the value of the ticks are not ""round"". + +*Responsibility*: Places ticks linearly. + +*Collaborators*: `RSLinearLocator` is used when rendering ticks. +" +Class { + #name : 'RSLinearLocator', + #superclass : 'RSTickLocator', + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'generate' } +RSLinearLocator >> generateTicks: aScale with: numberOfTicks [ + + ^ aScale ticks: numberOfTicks +] diff --git a/src/Roassal-Chart/RSLogLocator.class.st b/src/Roassal-Chart/RSLogLocator.class.st index 7395810d..7fa1079f 100644 --- a/src/Roassal-Chart/RSLogLocator.class.st +++ b/src/Roassal-Chart/RSLogLocator.class.st @@ -1,91 +1,91 @@ -" - -`RSLogLocator` places ticks in a logarithmic way. A `RSLogLocator` has a base, by default 10, and places a tick at each power of the base (for ex: 0.1, 1, 10, 100, etc). -However it doesn't rescale the axis. - -*Responsibility*: Places logarithmic ticks. - -*Collaborators*: `RSLogLocator` is used when rendering ticks. -" -Class { - #name : 'RSLogLocator', - #superclass : 'RSTickLocator', - #instVars : [ - 'base', - 'smallestIndex', - 'smallestPower' - ], - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'generate' } -RSLogLocator >> base [ - - ^ base -] - -{ #category : 'generate' } -RSLogLocator >> base: aBase [ - - base := aBase. - smallestIndex := aBase**(smallestPower) -] - -{ #category : 'generate' } -RSLogLocator >> generateTicks: aScale with: numberOfTicks [ - - | first last power collection tick | - first := aScale domain first. - last := aScale domain last. - - last < first ifTrue: [ - | temp | - temp := last. - last := first. - first := temp ]. - - last <= 0 ifTrue: [ - ^ DomainError signal: 'Data has no positive value' ]. - - first <= 0 ifTrue: [ first := smallestIndex ]. - power := (first log: base) rounded. - collection := OrderedCollection new. - tick := base ** power. - - [ tick <= last ] whileTrue: [ - collection add: tick. - power := power + 1. - tick := base ** power ]. - - ^ collection -] - -{ #category : 'initialization' } -RSLogLocator >> initialize [ - - super initialize. - base := 10. - smallestPower := -3. - smallestIndex := base**smallestPower -] - -{ #category : 'stepping' } -RSLogLocator >> smallestIndex [ - - ^ smallestIndex -] - -{ #category : 'stepping' } -RSLogLocator >> smallestPower [ - - ^ smallestPower -] - -{ #category : 'stepping' } -RSLogLocator >> smallestPower: aPower [ - - smallestPower := aPower. - smallestIndex := base**aPower -] +" + +`RSLogLocator` places ticks in a logarithmic way. A `RSLogLocator` has a base, by default 10, and places a tick at each power of the base (for ex: 0.1, 1, 10, 100, etc). +However it doesn't rescale the axis. + +*Responsibility*: Places logarithmic ticks. + +*Collaborators*: `RSLogLocator` is used when rendering ticks. +" +Class { + #name : 'RSLogLocator', + #superclass : 'RSTickLocator', + #instVars : [ + 'base', + 'smallestIndex', + 'smallestPower' + ], + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'generate' } +RSLogLocator >> base [ + + ^ base +] + +{ #category : 'generate' } +RSLogLocator >> base: aBase [ + + base := aBase. + smallestIndex := aBase**(smallestPower) +] + +{ #category : 'generate' } +RSLogLocator >> generateTicks: aScale with: numberOfTicks [ + + | first last power collection tick | + first := aScale domain first. + last := aScale domain last. + + last < first ifTrue: [ + | temp | + temp := last. + last := first. + first := temp ]. + + last <= 0 ifTrue: [ + ^ DomainError signal: 'Data has no positive value' ]. + + first <= 0 ifTrue: [ first := smallestIndex ]. + power := (first log: base) rounded. + collection := OrderedCollection new. + tick := base ** power. + + [ tick <= last ] whileTrue: [ + collection add: tick. + power := power + 1. + tick := base ** power ]. + + ^ collection +] + +{ #category : 'initialization' } +RSLogLocator >> initialize [ + + super initialize. + base := 10. + smallestPower := -3. + smallestIndex := base**smallestPower +] + +{ #category : 'stepping' } +RSLogLocator >> smallestIndex [ + + ^ smallestIndex +] + +{ #category : 'stepping' } +RSLogLocator >> smallestPower [ + + ^ smallestPower +] + +{ #category : 'stepping' } +RSLogLocator >> smallestPower: aPower [ + + smallestPower := aPower. + smallestIndex := base**aPower +] diff --git a/src/Roassal-Chart/RSPopupDecoration.class.st b/src/Roassal-Chart/RSPopupDecoration.class.st index 3b80c0c7..7412c5e2 100644 --- a/src/Roassal-Chart/RSPopupDecoration.class.st +++ b/src/Roassal-Chart/RSPopupDecoration.class.st @@ -1,60 +1,60 @@ -" -I am a popup decoration for instances of line Plots in RSChart by default but you can customize this popup decoration with differents shapebuilders, or popups -" -Class { - #name : 'RSPopupDecoration', - #superclass : 'RSAbstractChartDecoration', - #instVars : [ - 'popup' - ], - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'accessing' } -RSPopupDecoration >> chartPopupBuilder [ - ^ self popup chartPopupBuilder -] - -{ #category : 'accessing' } -RSPopupDecoration >> chartPopupBuilder: aRSAbstractChartPopupBuilder [ - self popup chartPopupBuilder: aRSAbstractChartPopupBuilder -] - -{ #category : 'accessing' } -RSPopupDecoration >> createdShapes [ - ^ #() -] - -{ #category : 'initialization' } -RSPopupDecoration >> defaultShape [ - ^ nil -] - -{ #category : 'initialization' } -RSPopupDecoration >> initialize [ - super initialize. - popup := RSChartPopupDecoration new - chartPopupBuilder: RSSimpleChartPopupBuilder new; - markersPopupBuilder: RSSimpleMarkerPopupBuilder new; - yourself -] - -{ #category : 'accessing' } -RSPopupDecoration >> popup [ - ^ popup -] - -{ #category : 'accessing' } -RSPopupDecoration >> popup: aRSPopup [ - popup := aRSPopup -] - -{ #category : 'rendering' } -RSPopupDecoration >> renderIn: canvas [ - | box | - box := chart spine. - popup chart: chart. - box addInteraction: popup -] +" +I am a popup decoration for instances of line Plots in RSChart by default but you can customize this popup decoration with differents shapebuilders, or popups +" +Class { + #name : 'RSPopupDecoration', + #superclass : 'RSAbstractChartDecoration', + #instVars : [ + 'popup' + ], + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'accessing' } +RSPopupDecoration >> chartPopupBuilder [ + ^ self popup chartPopupBuilder +] + +{ #category : 'accessing' } +RSPopupDecoration >> chartPopupBuilder: aRSAbstractChartPopupBuilder [ + self popup chartPopupBuilder: aRSAbstractChartPopupBuilder +] + +{ #category : 'accessing' } +RSPopupDecoration >> createdShapes [ + ^ #() +] + +{ #category : 'initialization' } +RSPopupDecoration >> defaultShape [ + ^ nil +] + +{ #category : 'initialization' } +RSPopupDecoration >> initialize [ + super initialize. + popup := RSChartPopupDecoration new + chartPopupBuilder: RSSimpleChartPopupBuilder new; + markersPopupBuilder: RSSimpleMarkerPopupBuilder new; + yourself +] + +{ #category : 'accessing' } +RSPopupDecoration >> popup [ + ^ popup +] + +{ #category : 'accessing' } +RSPopupDecoration >> popup: aRSPopup [ + popup := aRSPopup +] + +{ #category : 'rendering' } +RSPopupDecoration >> renderIn: canvas [ + | box | + box := chart spine. + popup chart: chart. + box addInteraction: popup +] diff --git a/src/Roassal-Chart/RSQuantile.class.st b/src/Roassal-Chart/RSQuantile.class.st index 8201f298..ad9af421 100644 --- a/src/Roassal-Chart/RSQuantile.class.st +++ b/src/Roassal-Chart/RSQuantile.class.st @@ -1,28 +1,28 @@ -Class { - #name : 'RSQuantile', - #superclass : 'RSObject', - #instVars : [ - 'data' - ], - #category : 'Roassal-Chart-Statistics', - #package : 'Roassal-Chart', - #tag : 'Statistics' -} - -{ #category : 'accessing' } -RSQuantile class >> data: aCollection [ - | aRSQuantile | - aRSQuantile := self new. - aRSQuantile data: aCollection. - ^ aRSQuantile -] - -{ #category : 'initialize' } -RSQuantile >> compute: populationPercentage [ - ^ self subclassResponsibility -] - -{ #category : 'initialize' } -RSQuantile >> data: aCollection [ - data := aCollection -] +Class { + #name : 'RSQuantile', + #superclass : 'RSObject', + #instVars : [ + 'data' + ], + #category : 'Roassal-Chart-Statistics', + #package : 'Roassal-Chart', + #tag : 'Statistics' +} + +{ #category : 'accessing' } +RSQuantile class >> data: aCollection [ + | aRSQuantile | + aRSQuantile := self new. + aRSQuantile data: aCollection. + ^ aRSQuantile +] + +{ #category : 'initialize' } +RSQuantile >> compute: populationPercentage [ + ^ self subclassResponsibility +] + +{ #category : 'initialize' } +RSQuantile >> data: aCollection [ + data := aCollection +] diff --git a/src/Roassal-Chart/RSRiceBinning.class.st b/src/Roassal-Chart/RSRiceBinning.class.st index b307c156..3ea0e79e 100644 --- a/src/Roassal-Chart/RSRiceBinning.class.st +++ b/src/Roassal-Chart/RSRiceBinning.class.st @@ -1,15 +1,15 @@ -" -TODO -" -Class { - #name : 'RSRiceBinning', - #superclass : 'RSAbstractBinning', - #category : 'Roassal-Chart-Strategy', - #package : 'Roassal-Chart', - #tag : 'Strategy' -} - -{ #category : 'hooks' } -RSRiceBinning >> computeNumberOfBinsFor: aCollection [ - ^ 2 * (aCollection size raisedTo: 1/3) -] +" +TODO +" +Class { + #name : 'RSRiceBinning', + #superclass : 'RSAbstractBinning', + #category : 'Roassal-Chart-Strategy', + #package : 'Roassal-Chart', + #tag : 'Strategy' +} + +{ #category : 'hooks' } +RSRiceBinning >> computeNumberOfBinsFor: aCollection [ + ^ 2 * (aCollection size raisedTo: 1/3) +] diff --git a/src/Roassal-Chart/RSScatterPlot.class.st b/src/Roassal-Chart/RSScatterPlot.class.st index 185f26f4..4146a9d7 100644 --- a/src/Roassal-Chart/RSScatterPlot.class.st +++ b/src/Roassal-Chart/RSScatterPlot.class.st @@ -1,98 +1,98 @@ -" - -`RSScatterPlot` is a scatter plot. Shapes are defined using two components, X and Y. - -*Responsibility*: create shapes that represent a scatter plot. - -*Collaborators*: a plot closely interacts with decorations and can be added in a `RSCompositeChart`. - -*Variables*: -- `processBlock`: is either `nil` or refer to a one argument block. This block is used to particularize the shapes. - -*Example*: -```Smalltalk -x := OrderedCollection new. -y := OrderedCollection new. -z := OrderedCollection new. -r := Random seed: 42. -1 to: 100 do: [ :i | - x add: i + (r nextInteger: 10). - y add: i + (r nextInteger: 10). - z add: i + (r nextInteger: 10). -]. - -p := RSScatterPlot new x: x y: y. -p color: Color blue translucent. - -p horizontalTick doNotUseNiceLabel asFloat: 3. - -p build. -shapes := p ellipses. -shapes models: z. -RSNormalizer size - shapes: shapes; - from: 2; - to: 10; - normalize: #yourself. -RSNormalizer color - shapes: shapes; - normalize: #yourself. -p canvas -``` - - - -" -Class { - #name : 'RSScatterPlot', - #superclass : 'RSAbstractPlot', - #instVars : [ - 'ellipses' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSScatterPlot >> createdShapes [ - ^ ellipses -] - -{ #category : 'accessing - defaults' } -RSScatterPlot >> defaultShape [ - ^ RSEllipse new noPaint -] - -{ #category : 'accessing' } -RSScatterPlot >> ellipses [ - ^ ellipses -] - -{ #category : 'testing' } -RSScatterPlot >> isScatterPlot [ - ^ true -] - -{ #category : 'rendering' } -RSScatterPlot >> renderIn: canvas [ - | newPoint color | - super renderIn: canvas. - color := self computeColor. - self checkAssertion. - ellipses := (1 to: xValues size) - collect: [ :notUsed | - self shape copy - radius: 2; - color: color ] as: RSGroup. - xValues doWithIndex: [ :xt :i | - | yt et | - et := ellipses at: i. - yt := yValues at: i. - newPoint := self scalePoint: xt @ yt. - (self isPointWellDefined: newPoint) - ifTrue: [ - et model: xt @ yt. - et translateTo: newPoint. - canvas add: et ] ] -] +" + +`RSScatterPlot` is a scatter plot. Shapes are defined using two components, X and Y. + +*Responsibility*: create shapes that represent a scatter plot. + +*Collaborators*: a plot closely interacts with decorations and can be added in a `RSCompositeChart`. + +*Variables*: +- `processBlock`: is either `nil` or refer to a one argument block. This block is used to particularize the shapes. + +*Example*: +```Smalltalk +x := OrderedCollection new. +y := OrderedCollection new. +z := OrderedCollection new. +r := Random seed: 42. +1 to: 100 do: [ :i | + x add: i + (r nextInteger: 10). + y add: i + (r nextInteger: 10). + z add: i + (r nextInteger: 10). +]. + +p := RSScatterPlot new x: x y: y. +p color: Color blue translucent. + +p horizontalTick doNotUseNiceLabel asFloat: 3. + +p build. +shapes := p ellipses. +shapes models: z. +RSNormalizer size + shapes: shapes; + from: 2; + to: 10; + normalize: #yourself. +RSNormalizer color + shapes: shapes; + normalize: #yourself. +p canvas +``` + + + +" +Class { + #name : 'RSScatterPlot', + #superclass : 'RSAbstractPlot', + #instVars : [ + 'ellipses' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSScatterPlot >> createdShapes [ + ^ ellipses +] + +{ #category : 'accessing - defaults' } +RSScatterPlot >> defaultShape [ + ^ RSEllipse new noPaint +] + +{ #category : 'accessing' } +RSScatterPlot >> ellipses [ + ^ ellipses +] + +{ #category : 'testing' } +RSScatterPlot >> isScatterPlot [ + ^ true +] + +{ #category : 'rendering' } +RSScatterPlot >> renderIn: canvas [ + | newPoint color | + super renderIn: canvas. + color := self computeColor. + self checkAssertion. + ellipses := (1 to: xValues size) + collect: [ :notUsed | + self shape copy + radius: 2; + color: color ] as: RSGroup. + xValues doWithIndex: [ :xt :i | + | yt et | + et := ellipses at: i. + yt := yValues at: i. + newPoint := self scalePoint: xt @ yt. + (self isPointWellDefined: newPoint) + ifTrue: [ + et model: xt @ yt. + et translateTo: newPoint. + canvas add: et ] ] +] diff --git a/src/Roassal-Chart/RSSimpleChartPopupBuilder.class.st b/src/Roassal-Chart/RSSimpleChartPopupBuilder.class.st index a854a581..cc0a3bf1 100644 --- a/src/Roassal-Chart/RSSimpleChartPopupBuilder.class.st +++ b/src/Roassal-Chart/RSSimpleChartPopupBuilder.class.st @@ -1,105 +1,105 @@ -" -I am a simple chart popup builder. I create a row for each line bar plot. -Then I apply a vertical line layout to each row. - -Users can create a subclass and override `rowShapefor:` - -This class is a simple demo of how to create a basic popup for a RSChart with lines plots. - -It could be different, in that case feel free extend classes like `RSPopupDecoration` -or `RSMarkerPopupChart` - -```Smalltalk -| x cumsum c y error popup | -x := 1 to: 100. -cumsum := [:arr | | sum | - sum := 0. - arr collect: [ :v | sum := sum + v. sum ] ]. - -c := RSChart new. -c extent: 800@400. - -popup := RSPopupDecoration new. -c addDecoration: popup. - -#( -series1 red -series2 blue) pairsDo: [ :label :color | - | col plot | - y := (x collect: [ :i | 50 atRandom - 25 ]). - y := cumsum value: y. - error := x. - col := color value: Color. - - c addPlot: (RSAreaPlot new - x: x y1: y + error y2: y - error; - color: col translucent). - c addPlot: (plot := RSLinePlot new x: x y: y; fmt: 'o'; - color: col; - yourself). - popup chartPopupBuilder - for: plot text: label color: col. - ]. - -c build. - -^ c canvas. -``` -" -Class { - #name : 'RSSimpleChartPopupBuilder', - #superclass : 'RSLineChartPopupBuilder', - #instVars : [ - 'labels', - 'icons' - ], - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'public' } -RSSimpleChartPopupBuilder >> for: aRSLinePlot color: aColor [ - self for: aRSLinePlot icon: (RSEllipse new - size: 10; - color: aColor; - yourself) -] - -{ #category : 'public' } -RSSimpleChartPopupBuilder >> for: aRSLinePlot icon: aRSShape [ - icons at: aRSLinePlot put: aRSShape -] - -{ #category : 'public' } -RSSimpleChartPopupBuilder >> for: aRSLinePlot text: aString [ - labels at: aRSLinePlot put: aString -] - -{ #category : 'public' } -RSSimpleChartPopupBuilder >> for: aRSLinePlot text: aString color: aColor [ - self - for: aRSLinePlot text: aString; - for: aRSLinePlot color: aColor -] - -{ #category : 'initialization' } -RSSimpleChartPopupBuilder >> initialize [ - super initialize. - labels := Dictionary new. - icons := Dictionary new -] - -{ #category : 'hooks' } -RSSimpleChartPopupBuilder >> rowShapeFor: aRSLinePlot point: aPoint [ - | group | - group := RSGroup new. - - icons at: aRSLinePlot ifPresent: [ :icon | - group add: icon copy ]. - labels at: aRSLinePlot ifPresent: [ :label | - group add: (RSLabel new text: label) ]. - group add: (RSLabel new text: aPoint y). - RSHorizontalLineLayout new gapSize: 5; alignMiddle; on: group. - ^ group asShape -] +" +I am a simple chart popup builder. I create a row for each line bar plot. +Then I apply a vertical line layout to each row. + +Users can create a subclass and override `rowShapefor:` + +This class is a simple demo of how to create a basic popup for a RSChart with lines plots. + +It could be different, in that case feel free extend classes like `RSPopupDecoration` +or `RSMarkerPopupChart` + +```Smalltalk +| x cumsum c y error popup | +x := 1 to: 100. +cumsum := [:arr | | sum | + sum := 0. + arr collect: [ :v | sum := sum + v. sum ] ]. + +c := RSChart new. +c extent: 800@400. + +popup := RSPopupDecoration new. +c addDecoration: popup. + +#( +series1 red +series2 blue) pairsDo: [ :label :color | + | col plot | + y := (x collect: [ :i | 50 atRandom - 25 ]). + y := cumsum value: y. + error := x. + col := color value: Color. + + c addPlot: (RSAreaPlot new + x: x y1: y + error y2: y - error; + color: col translucent). + c addPlot: (plot := RSLinePlot new x: x y: y; fmt: 'o'; + color: col; + yourself). + popup chartPopupBuilder + for: plot text: label color: col. + ]. + +c build. + +^ c canvas. +``` +" +Class { + #name : 'RSSimpleChartPopupBuilder', + #superclass : 'RSLineChartPopupBuilder', + #instVars : [ + 'labels', + 'icons' + ], + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'public' } +RSSimpleChartPopupBuilder >> for: aRSLinePlot color: aColor [ + self for: aRSLinePlot icon: (RSEllipse new + size: 10; + color: aColor; + yourself) +] + +{ #category : 'public' } +RSSimpleChartPopupBuilder >> for: aRSLinePlot icon: aRSShape [ + icons at: aRSLinePlot put: aRSShape +] + +{ #category : 'public' } +RSSimpleChartPopupBuilder >> for: aRSLinePlot text: aString [ + labels at: aRSLinePlot put: aString +] + +{ #category : 'public' } +RSSimpleChartPopupBuilder >> for: aRSLinePlot text: aString color: aColor [ + self + for: aRSLinePlot text: aString; + for: aRSLinePlot color: aColor +] + +{ #category : 'initialization' } +RSSimpleChartPopupBuilder >> initialize [ + super initialize. + labels := Dictionary new. + icons := Dictionary new +] + +{ #category : 'hooks' } +RSSimpleChartPopupBuilder >> rowShapeFor: aRSLinePlot point: aPoint [ + | group | + group := RSGroup new. + + icons at: aRSLinePlot ifPresent: [ :icon | + group add: icon copy ]. + labels at: aRSLinePlot ifPresent: [ :label | + group add: (RSLabel new text: label) ]. + group add: (RSLabel new text: aPoint y). + RSHorizontalLineLayout new gapSize: 5; alignMiddle; on: group. + ^ group asShape +] diff --git a/src/Roassal-Chart/RSSimpleMarkerPopupBuilder.class.st b/src/Roassal-Chart/RSSimpleMarkerPopupBuilder.class.st index 9f8014f7..02d92f44 100644 --- a/src/Roassal-Chart/RSSimpleMarkerPopupBuilder.class.st +++ b/src/Roassal-Chart/RSSimpleMarkerPopupBuilder.class.st @@ -1,22 +1,22 @@ -" -I create markers in the visualization -" -Class { - #name : 'RSSimpleMarkerPopupBuilder', - #superclass : 'RSAbstractChartPopupBuilder', - #category : 'Roassal-Chart-Popup', - #package : 'Roassal-Chart', - #tag : 'Popup' -} - -{ #category : 'hooks' } -RSSimpleMarkerPopupBuilder >> shapeFor: aRSChart [ - | line box x | - line := RSLine new dashArray: #(3). - box := aRSChart spine globalEncompassingRectangle. - x := position x. - line - startPoint: x @ box top; - endPoint: x @ box bottom. - ^ line -] +" +I create markers in the visualization +" +Class { + #name : 'RSSimpleMarkerPopupBuilder', + #superclass : 'RSAbstractChartPopupBuilder', + #category : 'Roassal-Chart-Popup', + #package : 'Roassal-Chart', + #tag : 'Popup' +} + +{ #category : 'hooks' } +RSSimpleMarkerPopupBuilder >> shapeFor: aRSChart [ + | line box x | + line := RSLine new dashArray: #(3). + box := aRSChart spine globalEncompassingRectangle. + x := position x. + line + startPoint: x @ box top; + endPoint: x @ box bottom. + ^ line +] diff --git a/src/Roassal-Chart/RSStatisticalMeasures.class.st b/src/Roassal-Chart/RSStatisticalMeasures.class.st index 18efda5b..c81c3094 100644 --- a/src/Roassal-Chart/RSStatisticalMeasures.class.st +++ b/src/Roassal-Chart/RSStatisticalMeasures.class.st @@ -1,166 +1,166 @@ -Class { - #name : 'RSStatisticalMeasures', - #superclass : 'RSObject', - #instVars : [ - 'data', - 'quantile', - 'confidencePercentage' - ], - #category : 'Roassal-Chart-Statistics', - #package : 'Roassal-Chart', - #tag : 'Statistics' -} - -{ #category : 'accessing' } -RSStatisticalMeasures class >> data: aDataset [ - | statisticalMeasures | - statisticalMeasures := self new. - statisticalMeasures data: aDataset. - statisticalMeasures quantile: statisticalMeasures defaultQuantile. - statisticalMeasures confidencePercentage: 95. - ^ statisticalMeasures -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> confidenceInterval [ - | confidenceProbability | - confidenceProbability := confidencePercentage / 100. - ^ { quantile compute: (1-confidenceProbability). quantile compute: confidenceProbability. } -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> confidencePercentage [ - ^ confidencePercentage -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> confidencePercentage: aPercentage [ - - confidencePercentage := aPercentage -] - -{ #category : 'accessing' } -RSStatisticalMeasures >> data [ - ^ data -] - -{ #category : 'accessing' } -RSStatisticalMeasures >> data: aDataset [ - data := aDataset copy sort -] - -{ #category : 'accessing' } -RSStatisticalMeasures >> defaultQuantile [ - ^ (RSInvertedCDF data: data) -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> interQuartileRange [ - | quartiles q1 q3 | - quartiles := self quartiles. - q1 := quartiles at: 1. - q3 := quartiles at: 3. - ^ q3 - q1 -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> iqr [ - | q1 q3 | - q1 := quantile compute: 0.25. - q3 := quantile compute: 0.75. - ^ (q3 - q1) -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> lowerLimit [ - | quartiles q1 q3 iqr iqr15 | - quartiles := self quartiles. - q1 := quartiles at: 1. - q3 := quartiles at: 3. - iqr := self interQuartileRange. - iqr15 := q1 - (1.5 * iqr). - ^ (data select: [ :d | d >= iqr15 ]) min -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> mean [ - | sum | - sum := 0. - data do: [ :xi | sum := sum + xi ]. - ^ (sum / (data size)) -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> median [ - ^ self median: data -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> median: aDataset [ - ^ quantile compute: 0.5 -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> medianConfidenceInterval [ - | notchFactors delta median iqr | - notchFactors := Dictionary newFrom: { - (90 -> 1.28). - (95 -> 1.57). - (99 -> 1.81) }. - iqr := self iqr. - delta := (notchFactors at: confidencePercentage) * iqr / (data size sqrt). - median := self median. - ^ { median - delta. median + delta} -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> outliers [ - | outlierValues lowerLimit upperLimit iterator | - outlierValues := OrderedCollection new. - lowerLimit := self lowerLimit. - upperLimit := self upperLimit. - iterator := 1. - [(data at:iterator)upperLimit] whileTrue: [ - outlierValues add: (data at:iterator). - iterator := iterator - 1]. - ^ outlierValues -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> quantile: aRSQuantileSubclass [ - quantile := aRSQuantileSubclass -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> quartiles [ - | q1 q2 q3 | - q1 := quantile compute: 0.25. - q2 := quantile compute: 0.50. - q3 := quantile compute: 0.75. - ^ { q1. q2. q3 } -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> standardDeviation [ - | standardDeviation sum mean | - sum := 0. - mean := self mean. - data do: [ :observationX | sum := sum + ((observationX - mean) squared) ]. - standardDeviation := (mean/(data size)) sqrt. - ^ standardDeviation -] - -{ #category : 'accessing - computed' } -RSStatisticalMeasures >> upperLimit [ - | quartiles q1 q3 iqr iqr15 | - quartiles := self quartiles. - q1 := quartiles at: 1. - q3 := quartiles at: 3. - iqr := self interQuartileRange. - iqr15 := q3 + (1.5 * iqr). - ^ (data select: [ :d | d <= iqr15 ]) max -] +Class { + #name : 'RSStatisticalMeasures', + #superclass : 'RSObject', + #instVars : [ + 'data', + 'quantile', + 'confidencePercentage' + ], + #category : 'Roassal-Chart-Statistics', + #package : 'Roassal-Chart', + #tag : 'Statistics' +} + +{ #category : 'accessing' } +RSStatisticalMeasures class >> data: aDataset [ + | statisticalMeasures | + statisticalMeasures := self new. + statisticalMeasures data: aDataset. + statisticalMeasures quantile: statisticalMeasures defaultQuantile. + statisticalMeasures confidencePercentage: 95. + ^ statisticalMeasures +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> confidenceInterval [ + | confidenceProbability | + confidenceProbability := confidencePercentage / 100. + ^ { quantile compute: (1-confidenceProbability). quantile compute: confidenceProbability. } +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> confidencePercentage [ + ^ confidencePercentage +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> confidencePercentage: aPercentage [ + + confidencePercentage := aPercentage +] + +{ #category : 'accessing' } +RSStatisticalMeasures >> data [ + ^ data +] + +{ #category : 'accessing' } +RSStatisticalMeasures >> data: aDataset [ + data := aDataset copy sort +] + +{ #category : 'accessing' } +RSStatisticalMeasures >> defaultQuantile [ + ^ (RSInvertedCDF data: data) +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> interQuartileRange [ + | quartiles q1 q3 | + quartiles := self quartiles. + q1 := quartiles at: 1. + q3 := quartiles at: 3. + ^ q3 - q1 +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> iqr [ + | q1 q3 | + q1 := quantile compute: 0.25. + q3 := quantile compute: 0.75. + ^ (q3 - q1) +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> lowerLimit [ + | quartiles q1 q3 iqr iqr15 | + quartiles := self quartiles. + q1 := quartiles at: 1. + q3 := quartiles at: 3. + iqr := self interQuartileRange. + iqr15 := q1 - (1.5 * iqr). + ^ (data select: [ :d | d >= iqr15 ]) min +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> mean [ + | sum | + sum := 0. + data do: [ :xi | sum := sum + xi ]. + ^ (sum / (data size)) +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> median [ + ^ self median: data +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> median: aDataset [ + ^ quantile compute: 0.5 +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> medianConfidenceInterval [ + | notchFactors delta median iqr | + notchFactors := Dictionary newFrom: { + (90 -> 1.28). + (95 -> 1.57). + (99 -> 1.81) }. + iqr := self iqr. + delta := (notchFactors at: confidencePercentage) * iqr / (data size sqrt). + median := self median. + ^ { median - delta. median + delta} +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> outliers [ + | outlierValues lowerLimit upperLimit iterator | + outlierValues := OrderedCollection new. + lowerLimit := self lowerLimit. + upperLimit := self upperLimit. + iterator := 1. + [(data at:iterator)upperLimit] whileTrue: [ + outlierValues add: (data at:iterator). + iterator := iterator - 1]. + ^ outlierValues +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> quantile: aRSQuantileSubclass [ + quantile := aRSQuantileSubclass +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> quartiles [ + | q1 q2 q3 | + q1 := quantile compute: 0.25. + q2 := quantile compute: 0.50. + q3 := quantile compute: 0.75. + ^ { q1. q2. q3 } +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> standardDeviation [ + | standardDeviation sum mean | + sum := 0. + mean := self mean. + data do: [ :observationX | sum := sum + ((observationX - mean) squared) ]. + standardDeviation := (mean/(data size)) sqrt. + ^ standardDeviation +] + +{ #category : 'accessing - computed' } +RSStatisticalMeasures >> upperLimit [ + | quartiles q1 q3 iqr iqr15 | + quartiles := self quartiles. + q1 := quartiles at: 1. + q3 := quartiles at: 3. + iqr := self interQuartileRange. + iqr15 := q3 + (1.5 * iqr). + ^ (data select: [ :d | d <= iqr15 ]) max +] diff --git a/src/Roassal-Chart/RSSturgesBinning.class.st b/src/Roassal-Chart/RSSturgesBinning.class.st index aa1c5ab9..4b5f6f98 100644 --- a/src/Roassal-Chart/RSSturgesBinning.class.st +++ b/src/Roassal-Chart/RSSturgesBinning.class.st @@ -1,15 +1,15 @@ -" -TODO -" -Class { - #name : 'RSSturgesBinning', - #superclass : 'RSAbstractBinning', - #category : 'Roassal-Chart-Strategy', - #package : 'Roassal-Chart', - #tag : 'Strategy' -} - -{ #category : 'hooks' } -RSSturgesBinning >> computeNumberOfBinsFor: aCollection [ - ^ (aCollection size log / 2 log) ceiling + 1 -] +" +TODO +" +Class { + #name : 'RSSturgesBinning', + #superclass : 'RSAbstractBinning', + #category : 'Roassal-Chart-Strategy', + #package : 'Roassal-Chart', + #tag : 'Strategy' +} + +{ #category : 'hooks' } +RSSturgesBinning >> computeNumberOfBinsFor: aCollection [ + ^ (aCollection size log / 2 log) ceiling + 1 +] diff --git a/src/Roassal-Chart/RSTickConfiguration.class.st b/src/Roassal-Chart/RSTickConfiguration.class.st index 4ceeaeb1..bf31f128 100644 --- a/src/Roassal-Chart/RSTickConfiguration.class.st +++ b/src/Roassal-Chart/RSTickConfiguration.class.st @@ -1,163 +1,163 @@ -" - -`RSTickConfiguration` describes a configuration for a vertical or horizontal tick. Note that the same configuration can be shared for the two tick objects. - -*Responsibility*: maintain the different configurable attributes for vertical or horizontal tickis. - -*Collaborators*: used by `RSAbstractTick` - -*Variables*: -- `numberOfTicks`: number of ticks the tick decoration should have -- `shouldHaveLabels`: a boolean indicating whether a the tick should have labels or not -- `labelConversion`: one arg block to transform a value. E.g., `[ :p | 'V', p asString ]` -- `shouldUseNiceLabel`: a boolean indicating whether the tick should use the nice tick label generator (e.g., `RSNiceLabel`) -- `labelRotation`: rotation degree of the label -- `fontSize`: size of the label font -- `tickSize`: size of the tick, in pixels - -*Example*: -```Smalltalk -classes := RSObject withAllSubclasses. -x := classes collect: [ :c | c numberOfMethods ]. -y := classes collect: [ :c | c linesOfCode ]. - -c := RSChart new. - -d := RSScatterPlot new. -d color: Color blue translucent. -d x: x y: y. -c addPlot: d. - -tickConfiguration := RSTickConfiguration new - numberOfTicks: 5; - fontSize: 5; - labelRotation: 0; - shouldHaveLabels: true; - shouldUseNiceLabel: true; - labelConversion: [ :value | value ] ; - tickSize: 3; - yourself. -c addDecoration: (RSHorizontalTick new configuration: tickConfiguration). -c addDecoration: (RSVerticalTick new configuration: tickConfiguration). -c -``` - -Another similar example: -```Smalltalk -classes := RSObject withAllSubclasses. -x := classes collect: [ :c | c numberOfMethods ]. -y := classes collect: [ :c | c linesOfCode ]. - -d := RSScatterPlot new. -d color: Color blue translucent. -d x: x y: y. - -tickConfiguration := RSTickConfiguration new. -tickConfiguration labelConversion: [ :p | 'V', p asString ]. - -d horizontalTick configuration: tickConfiguration. -d -``` -" -Class { - #name : 'RSTickConfiguration', - #superclass : 'Object', - #instVars : [ - 'numberOfTicks', - 'shouldHaveLabels', - 'labelConversion', - 'labelRotation', - 'fontSize', - 'tickSize' - ], - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'public' } -RSTickConfiguration >> createLabelFor: aValue [ - ^ RSLabel new - fontSize: self fontSize; - text: (self labelConversion rsValue: aValue); - rotateByDegrees: self labelRotation; - yourself -] - -{ #category : 'accessing' } -RSTickConfiguration >> fontSize [ - ^ fontSize -] - -{ #category : 'accessing' } -RSTickConfiguration >> fontSize: aNumber [ - fontSize := aNumber -] - -{ #category : 'initialization' } -RSTickConfiguration >> initialize [ - super initialize. - self numberOfTicks: 5; - fontSize: RSLabel defaultFontSize; - labelRotation: 0; - shouldHaveLabels: true; - labelConversion: [ :value | value asFloat round: 3 ]; - tickSize: 3 -] - -{ #category : 'accessing' } -RSTickConfiguration >> labelConversion [ - ^ labelConversion -] - -{ #category : 'accessing' } -RSTickConfiguration >> labelConversion: aOneArgBlock [ - labelConversion := aOneArgBlock -] - -{ #category : 'accessing' } -RSTickConfiguration >> labelRotation [ - ^ labelRotation -] - -{ #category : 'accessing' } -RSTickConfiguration >> labelRotation: degreesAsNumber [ - labelRotation := degreesAsNumber -] - -{ #category : 'accessing' } -RSTickConfiguration >> numberOfTicks [ - ^ numberOfTicks -] - -{ #category : 'accessing' } -RSTickConfiguration >> numberOfTicks: aNumber [ - numberOfTicks := aNumber -] - -{ #category : 'accessing' } -RSTickConfiguration >> shouldHaveLabels [ - ^ shouldHaveLabels -] - -{ #category : 'accessing' } -RSTickConfiguration >> shouldHaveLabels: aBoolean [ - shouldHaveLabels := aBoolean -] - -{ #category : 'accessing' } -RSTickConfiguration >> tickSize [ - ^ tickSize -] - -{ #category : 'accessing' } -RSTickConfiguration >> tickSize: aNumber [ - tickSize := aNumber -] - -{ #category : 'accessing' } -RSTickConfiguration >> updateTickConfiguration: anEvent [ - | canvas | - canvas := anEvent canvas. - self fontSize: (canvas extent) / (canvas defaultExtent) -] +" + +`RSTickConfiguration` describes a configuration for a vertical or horizontal tick. Note that the same configuration can be shared for the two tick objects. + +*Responsibility*: maintain the different configurable attributes for vertical or horizontal tickis. + +*Collaborators*: used by `RSAbstractTick` + +*Variables*: +- `numberOfTicks`: number of ticks the tick decoration should have +- `shouldHaveLabels`: a boolean indicating whether a the tick should have labels or not +- `labelConversion`: one arg block to transform a value. E.g., `[ :p | 'V', p asString ]` +- `shouldUseNiceLabel`: a boolean indicating whether the tick should use the nice tick label generator (e.g., `RSNiceLabel`) +- `labelRotation`: rotation degree of the label +- `fontSize`: size of the label font +- `tickSize`: size of the tick, in pixels + +*Example*: +```Smalltalk +classes := RSObject withAllSubclasses. +x := classes collect: [ :c | c numberOfMethods ]. +y := classes collect: [ :c | c linesOfCode ]. + +c := RSChart new. + +d := RSScatterPlot new. +d color: Color blue translucent. +d x: x y: y. +c addPlot: d. + +tickConfiguration := RSTickConfiguration new + numberOfTicks: 5; + fontSize: 5; + labelRotation: 0; + shouldHaveLabels: true; + shouldUseNiceLabel: true; + labelConversion: [ :value | value ] ; + tickSize: 3; + yourself. +c addDecoration: (RSHorizontalTick new configuration: tickConfiguration). +c addDecoration: (RSVerticalTick new configuration: tickConfiguration). +c +``` + +Another similar example: +```Smalltalk +classes := RSObject withAllSubclasses. +x := classes collect: [ :c | c numberOfMethods ]. +y := classes collect: [ :c | c linesOfCode ]. + +d := RSScatterPlot new. +d color: Color blue translucent. +d x: x y: y. + +tickConfiguration := RSTickConfiguration new. +tickConfiguration labelConversion: [ :p | 'V', p asString ]. + +d horizontalTick configuration: tickConfiguration. +d +``` +" +Class { + #name : 'RSTickConfiguration', + #superclass : 'Object', + #instVars : [ + 'numberOfTicks', + 'shouldHaveLabels', + 'labelConversion', + 'labelRotation', + 'fontSize', + 'tickSize' + ], + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'public' } +RSTickConfiguration >> createLabelFor: aValue [ + ^ RSLabel new + fontSize: self fontSize; + text: (self labelConversion rsValue: aValue); + rotateByDegrees: self labelRotation; + yourself +] + +{ #category : 'accessing' } +RSTickConfiguration >> fontSize [ + ^ fontSize +] + +{ #category : 'accessing' } +RSTickConfiguration >> fontSize: aNumber [ + fontSize := aNumber +] + +{ #category : 'initialization' } +RSTickConfiguration >> initialize [ + super initialize. + self numberOfTicks: 5; + fontSize: RSLabel defaultFontSize; + labelRotation: 0; + shouldHaveLabels: true; + labelConversion: [ :value | value asFloat round: 3 ]; + tickSize: 3 +] + +{ #category : 'accessing' } +RSTickConfiguration >> labelConversion [ + ^ labelConversion +] + +{ #category : 'accessing' } +RSTickConfiguration >> labelConversion: aOneArgBlock [ + labelConversion := aOneArgBlock +] + +{ #category : 'accessing' } +RSTickConfiguration >> labelRotation [ + ^ labelRotation +] + +{ #category : 'accessing' } +RSTickConfiguration >> labelRotation: degreesAsNumber [ + labelRotation := degreesAsNumber +] + +{ #category : 'accessing' } +RSTickConfiguration >> numberOfTicks [ + ^ numberOfTicks +] + +{ #category : 'accessing' } +RSTickConfiguration >> numberOfTicks: aNumber [ + numberOfTicks := aNumber +] + +{ #category : 'accessing' } +RSTickConfiguration >> shouldHaveLabels [ + ^ shouldHaveLabels +] + +{ #category : 'accessing' } +RSTickConfiguration >> shouldHaveLabels: aBoolean [ + shouldHaveLabels := aBoolean +] + +{ #category : 'accessing' } +RSTickConfiguration >> tickSize [ + ^ tickSize +] + +{ #category : 'accessing' } +RSTickConfiguration >> tickSize: aNumber [ + tickSize := aNumber +] + +{ #category : 'accessing' } +RSTickConfiguration >> updateTickConfiguration: anEvent [ + | canvas | + canvas := anEvent canvas. + self fontSize: (canvas extent) / (canvas defaultExtent) +] diff --git a/src/Roassal-Chart/RSTickLocator.class.st b/src/Roassal-Chart/RSTickLocator.class.st index e4141a76..43272709 100644 --- a/src/Roassal-Chart/RSTickLocator.class.st +++ b/src/Roassal-Chart/RSTickLocator.class.st @@ -1,16 +1,16 @@ -" -Abstract class for all tick locators. A tick locator defines where the ticks of a chart should be. -" -Class { - #name : 'RSTickLocator', - #superclass : 'Object', - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'generate' } -RSTickLocator >> generateTicks: aScale with: numberOfTicks [ - - ^ self subclassResponsibility -] +" +Abstract class for all tick locators. A tick locator defines where the ticks of a chart should be. +" +Class { + #name : 'RSTickLocator', + #superclass : 'Object', + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'generate' } +RSTickLocator >> generateTicks: aScale with: numberOfTicks [ + + ^ self subclassResponsibility +] diff --git a/src/Roassal-Chart/RSTimeLinePlot.class.st b/src/Roassal-Chart/RSTimeLinePlot.class.st index 7994b8d6..9c85650b 100644 --- a/src/Roassal-Chart/RSTimeLinePlot.class.st +++ b/src/Roassal-Chart/RSTimeLinePlot.class.st @@ -1,115 +1,115 @@ -" -Define a time line, a bit like a Gantt diagram. -" -Class { - #name : 'RSTimeLinePlot', - #superclass : 'RSAbstractPlot', - #instVars : [ - 'gapRatio', - 'bars', - 'barSize' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSTimeLinePlot >> barScale [ - ^ yScale -] - -{ #category : 'accessing' } -RSTimeLinePlot >> barSize [ - "Return the width of each bar" - ^ barSize ifNil: [ self barScale rangeBand ] -] - -{ #category : 'accessing' } -RSTimeLinePlot >> barSize: aBarWidth [ - "Set the width of the bar" - barSize := aBarWidth -] - -{ #category : 'accessing' } -RSTimeLinePlot >> bars [ - ^ bars -] - -{ #category : 'rendering' } -RSTimeLinePlot >> beforeRenderingIn: aChart [ - - | barScale | - super beforeRenderingIn: aChart. - yScale class = NSOrdinalScale ifTrue: [ ^ self ]. - barScale := NSOrdinalScale new - domain: - (aChart minChartValueY to: aChart maxChartValueY); - rangeBands: yScale range padding: gapRatio. - aChart yScale: barScale -] - -{ #category : 'accessing' } -RSTimeLinePlot >> createdShapes [ - ^ bars -] - -{ #category : 'accessing - defaults' } -RSTimeLinePlot >> defaultShape [ - ^ RSBox new noPaint -] - -{ #category : 'accessing' } -RSTimeLinePlot >> entries: aCollection at: aNumber [ - "example: - self entries: { 0 2 4 8}. - " - self - assert: aCollection size even - description: 'You only can use collection of even sizes'. - self - x: aCollection - y: (Array new: aCollection size withAll: aNumber) -] - -{ #category : 'accessing' } -RSTimeLinePlot >> gapRatio [ - ^ gapRatio -] - -{ #category : 'accessing' } -RSTimeLinePlot >> gapRatio: anObject [ - gapRatio := anObject -] - -{ #category : 'initialization' } -RSTimeLinePlot >> initialize [ - super initialize. - self gapRatio: 0.1 -] - -{ #category : 'rendering' } -RSTimeLinePlot >> renderIn: canvas [ - - super renderIn: canvas. - bars := RSGroup new. - 1 to: xValues size by: 2 do: [ :index | - | x1 y1 x2 y2 origin corner rectangle sizeOffset | - x1 := xValues at: index. - y1 := yValues at: index. - x2 := xValues at: index+1. - y2 := yValues at: index+1. - origin := self scalePoint: x1 @ y1. - corner := self scalePoint: x2 @ y2. - sizeOffset := 0@ self barSize / 2.0. - rectangle := Rectangle - origin: origin-sizeOffset - corner: corner+sizeOffset. - bars add: (self shape copy - model: (x1 -> x2); - color: self computeColor; - fromRectangle: rectangle; - yourself) - ]. - canvas addAll: bars -] +" +Define a time line, a bit like a Gantt diagram. +" +Class { + #name : 'RSTimeLinePlot', + #superclass : 'RSAbstractPlot', + #instVars : [ + 'gapRatio', + 'bars', + 'barSize' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSTimeLinePlot >> barScale [ + ^ yScale +] + +{ #category : 'accessing' } +RSTimeLinePlot >> barSize [ + "Return the width of each bar" + ^ barSize ifNil: [ self barScale rangeBand ] +] + +{ #category : 'accessing' } +RSTimeLinePlot >> barSize: aBarWidth [ + "Set the width of the bar" + barSize := aBarWidth +] + +{ #category : 'accessing' } +RSTimeLinePlot >> bars [ + ^ bars +] + +{ #category : 'rendering' } +RSTimeLinePlot >> beforeRenderingIn: aChart [ + + | barScale | + super beforeRenderingIn: aChart. + yScale class = NSOrdinalScale ifTrue: [ ^ self ]. + barScale := NSOrdinalScale new + domain: + (aChart minChartValueY to: aChart maxChartValueY); + rangeBands: yScale range padding: gapRatio. + aChart yScale: barScale +] + +{ #category : 'accessing' } +RSTimeLinePlot >> createdShapes [ + ^ bars +] + +{ #category : 'accessing - defaults' } +RSTimeLinePlot >> defaultShape [ + ^ RSBox new noPaint +] + +{ #category : 'accessing' } +RSTimeLinePlot >> entries: aCollection at: aNumber [ + "example: + self entries: { 0 2 4 8}. + " + self + assert: aCollection size even + description: 'You only can use collection of even sizes'. + self + x: aCollection + y: (Array new: aCollection size withAll: aNumber) +] + +{ #category : 'accessing' } +RSTimeLinePlot >> gapRatio [ + ^ gapRatio +] + +{ #category : 'accessing' } +RSTimeLinePlot >> gapRatio: anObject [ + gapRatio := anObject +] + +{ #category : 'initialization' } +RSTimeLinePlot >> initialize [ + super initialize. + self gapRatio: 0.1 +] + +{ #category : 'rendering' } +RSTimeLinePlot >> renderIn: canvas [ + + super renderIn: canvas. + bars := RSGroup new. + 1 to: xValues size by: 2 do: [ :index | + | x1 y1 x2 y2 origin corner rectangle sizeOffset | + x1 := xValues at: index. + y1 := yValues at: index. + x2 := xValues at: index+1. + y2 := yValues at: index+1. + origin := self scalePoint: x1 @ y1. + corner := self scalePoint: x2 @ y2. + sizeOffset := 0@ self barSize / 2.0. + rectangle := Rectangle + origin: origin-sizeOffset + corner: corner+sizeOffset. + bars add: (self shape copy + model: (x1 -> x2); + color: self computeColor; + fromRectangle: rectangle; + yourself) + ]. + canvas addAll: bars +] diff --git a/src/Roassal-Chart/RSVerticalRightTick.class.st b/src/Roassal-Chart/RSVerticalRightTick.class.st index 8ae6f55f..0e6a46ea 100644 --- a/src/Roassal-Chart/RSVerticalRightTick.class.st +++ b/src/Roassal-Chart/RSVerticalRightTick.class.st @@ -1,84 +1,84 @@ -" -Similar to vertical tick and hortizonal top tick -" -Class { - #name : 'RSVerticalRightTick', - #superclass : 'RSAbstractTick', - #instVars : [ - 'values' - ], - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'rendering' } -RSVerticalRightTick >> beforeRenderingIn: aChart [ - - self createXScale. - self createYScale -] - -{ #category : 'rendering' } -RSVerticalRightTick >> createTickLineFor: aNumber [ - | scaledNumber x | - scaledNumber := yScale scale: aNumber. - x := chart extent x. - ^ self newLineTick - startPoint: x @ scaledNumber; - endPoint: (x + self configuration tickSize) @ scaledNumber; - yourself -] - -{ #category : 'rendering' } -RSVerticalRightTick >> createYScale [ - - | padding | - yScale ifNil: [ yScale := NSScale linear ]. - yScale class = NSOrdinalScale ifTrue: [ ^ self ]. - padding := chart padding y. - yScale - domain: { - self min. - self max }; - range: { - (0 - padding). - (chart extent y negated + padding) } -] - -{ #category : 'accessing - defaults' } -RSVerticalRightTick >> defaultLabelLocation [ - ^ RSLocation new outer; right; offset: 2@0 -] - -{ #category : 'testing' } -RSVerticalRightTick >> isVerticalTick [ - - ^ true -] - -{ #category : 'accessing' } -RSVerticalRightTick >> max [ - ^ values max -] - -{ #category : 'accessing' } -RSVerticalRightTick >> min [ - ^ values min -] - -{ #category : 'rendering' } -RSVerticalRightTick >> updateChartMaxAndMinValues: aChart [ -] - -{ #category : 'accessing' } -RSVerticalRightTick >> values [ - - ^ values -] - -{ #category : 'accessing' } -RSVerticalRightTick >> values: anObject [ - - values := anObject -] +" +Similar to vertical tick and hortizonal top tick +" +Class { + #name : 'RSVerticalRightTick', + #superclass : 'RSAbstractTick', + #instVars : [ + 'values' + ], + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'rendering' } +RSVerticalRightTick >> beforeRenderingIn: aChart [ + + self createXScale. + self createYScale +] + +{ #category : 'rendering' } +RSVerticalRightTick >> createTickLineFor: aNumber [ + | scaledNumber x | + scaledNumber := yScale scale: aNumber. + x := chart extent x. + ^ self newLineTick + startPoint: x @ scaledNumber; + endPoint: (x + self configuration tickSize) @ scaledNumber; + yourself +] + +{ #category : 'rendering' } +RSVerticalRightTick >> createYScale [ + + | padding | + yScale ifNil: [ yScale := NSScale linear ]. + yScale class = NSOrdinalScale ifTrue: [ ^ self ]. + padding := chart padding y. + yScale + domain: { + self min. + self max }; + range: { + (0 - padding). + (chart extent y negated + padding) } +] + +{ #category : 'accessing - defaults' } +RSVerticalRightTick >> defaultLabelLocation [ + ^ RSLocation new outer; right; offset: 2@0 +] + +{ #category : 'testing' } +RSVerticalRightTick >> isVerticalTick [ + + ^ true +] + +{ #category : 'accessing' } +RSVerticalRightTick >> max [ + ^ values max +] + +{ #category : 'accessing' } +RSVerticalRightTick >> min [ + ^ values min +] + +{ #category : 'rendering' } +RSVerticalRightTick >> updateChartMaxAndMinValues: aChart [ +] + +{ #category : 'accessing' } +RSVerticalRightTick >> values [ + + ^ values +] + +{ #category : 'accessing' } +RSVerticalRightTick >> values: anObject [ + + values := anObject +] diff --git a/src/Roassal-Chart/RSVerticalTick.class.st b/src/Roassal-Chart/RSVerticalTick.class.st index 9cea4ee2..da86402b 100644 --- a/src/Roassal-Chart/RSVerticalTick.class.st +++ b/src/Roassal-Chart/RSVerticalTick.class.st @@ -1,64 +1,64 @@ -" - -`RSVerticalTick` defines ticks for the vertical axis. It is a decoration that can be added to a `RSChart`. - -*Responsibility*: define, customize, and render ticks - -*Collaborators*: `RSVerticalTick` is added to `RSChart` - -*Example*: -```Smalltalk - x := -3.14 to: 3.14 by: 0.01. - p := RSLinePlot new. - p x: x y: x sin * 0.22 + 0.5. - p verticalTick asFloat. - p -``` -" -Class { - #name : 'RSVerticalTick', - #superclass : 'RSAbstractTick', - #category : 'Roassal-Chart-Ticks', - #package : 'Roassal-Chart', - #tag : 'Ticks' -} - -{ #category : 'rendering' } -RSVerticalTick >> createTickLineFor: aNumber [ - | scaledNumber zeroX | - scaledNumber := yScale scale: aNumber. - zeroX := self chart spineDecoration zeroPoint x. - - ^ self newLineTick - startPoint: zeroX @ scaledNumber; - endPoint: zeroX - self configuration tickSize @ scaledNumber; - yourself -] - -{ #category : 'accessing - defaults' } -RSVerticalTick >> defaultLabelLocation [ - ^ RSLocation new outer; left; offset: -2@0 -] - -{ #category : 'testing' } -RSVerticalTick >> isVerticalTick [ - ^ true -] - -{ #category : 'accessing' } -RSVerticalTick >> max [ - ^ chart maxValueY -] - -{ #category : 'accessing' } -RSVerticalTick >> min [ - ^ chart minValueY -] - -{ #category : 'rendering' } -RSVerticalTick >> updateChartMaxAndMinValues: aChart [ - - aChart - minChartValueY: self ticksData first; - maxChartValueY: self ticksData last -] +" + +`RSVerticalTick` defines ticks for the vertical axis. It is a decoration that can be added to a `RSChart`. + +*Responsibility*: define, customize, and render ticks + +*Collaborators*: `RSVerticalTick` is added to `RSChart` + +*Example*: +```Smalltalk + x := -3.14 to: 3.14 by: 0.01. + p := RSLinePlot new. + p x: x y: x sin * 0.22 + 0.5. + p verticalTick asFloat. + p +``` +" +Class { + #name : 'RSVerticalTick', + #superclass : 'RSAbstractTick', + #category : 'Roassal-Chart-Ticks', + #package : 'Roassal-Chart', + #tag : 'Ticks' +} + +{ #category : 'rendering' } +RSVerticalTick >> createTickLineFor: aNumber [ + | scaledNumber zeroX | + scaledNumber := yScale scale: aNumber. + zeroX := self chart spineDecoration zeroPoint x. + + ^ self newLineTick + startPoint: zeroX @ scaledNumber; + endPoint: zeroX - self configuration tickSize @ scaledNumber; + yourself +] + +{ #category : 'accessing - defaults' } +RSVerticalTick >> defaultLabelLocation [ + ^ RSLocation new outer; left; offset: -2@0 +] + +{ #category : 'testing' } +RSVerticalTick >> isVerticalTick [ + ^ true +] + +{ #category : 'accessing' } +RSVerticalTick >> max [ + ^ chart maxValueY +] + +{ #category : 'accessing' } +RSVerticalTick >> min [ + ^ chart minValueY +] + +{ #category : 'rendering' } +RSVerticalTick >> updateChartMaxAndMinValues: aChart [ + + aChart + minChartValueY: self ticksData first; + maxChartValueY: self ticksData last +] diff --git a/src/Roassal-Chart/RSViolinPlot.class.st b/src/Roassal-Chart/RSViolinPlot.class.st index 3555f3bc..1cd015e6 100644 --- a/src/Roassal-Chart/RSViolinPlot.class.st +++ b/src/Roassal-Chart/RSViolinPlot.class.st @@ -1,610 +1,610 @@ -" -`RSDensityPlot` is a visual representation that combines a box plot with a density distribution (an approximation of the frequency) to depict the distribution of a dataset. - -**Responsibility:** -- Plots the density distribution area, the box and the whiskers. -- Provides options to customize the plot. - -**Collaborators:** -- The instance variable `kernelDensity` is an `RSKernelDensity` object responsible for calculating the points of the density curve, which delineates the boundary of the density area. -- The instance variable `statisticalMeasures` is an `RSStatisticalMeasures` object that computes various statistical measures, including mean, median, quartiles, and interquartile range (IQR), to facilitate the construction of the box and whiskers plot. - -**Public API and Key Messages** -- `data: aCollection` to create instances passing dataset (aCollection) as argument. -- `bandwidth: aFloat` to set the bandwith (h) of the kernel in the kernel density estimation function. By default is computed by `RSKernelDensity`. The float passed indicates how soft will be the curve. - -**Instance Variables:** -- `box`: an `RSPolygon` in which the top and bottom correspond to the 3rd and 1st quartiles of the data, respectively. -- `boxPoints`: an `OrderedCollection` of `Point`s that store the computed `box` points without any scaling. -- `boxWidth`: a `Float` that represents the width of the box in the y domain units. -- `centerLine`: an `RSPolyline` representing the whiskers (upper and lower limits) calculated as (q3 - q1)*1.5. -- `centerLinePoints`: an `OrderedCollection` of `Point`s that store the computed `centerLine` points without any scaling. -- `densityArea`: an `RSPolygon` that represents the density distribution of the data, calculated by KDE method (`kernelDensity`). -- `densityAreaPoints`: an `OrderedCollection` of `Point`s that store the computed `densityArea` points without any scaling. -- `kernelDensity`: a `RSKernelDensity` object that made the calculation of the Kernel Density Estimation, several parametres are passed to this object to personalize the curve (bandwidth, kernel, etc.). -- `statisticalMeasures`: a `RSStatisticalMeasures` object that computes various statistical measures, including mean, median, quartiles, and interquartile range (IQR), to facilitate the construction of the box and whiskers plot. -- `title`: aString that stores the title of the chart. -- `xlabel`: aString that stores the label of x axis. -- `ylabel`: aString that stores the label of y axis. - - -**Example:** -```Smalltalk -| violinPlot data | -data := {-5. 12. 12. 13. 14. 14. 15. 24. }. -violinPlot := RSViolinPlot data: data. -violinPlot - bandwidth: 3; - title: 'This is a RSViolinPlot'; - xlabel: 'Data frequency (distribution)'; - ylabel: 'Data domain'. -violinPlot open. -``` -" -Class { - #name : 'RSViolinPlot', - #superclass : 'RSAbstractBandPlot', - #instVars : [ - 'models', - 'model', - 'dataBlock' - ], - #category : 'Roassal-Chart-Core', - #package : 'Roassal-Chart', - #tag : 'Core' -} - -{ #category : 'accessing' } -RSViolinPlot class >> data: aCollection [ - | plot | - plot := self new. - plot data: aCollection. - ^ plot -] - -{ #category : 'examples' } -RSViolinPlot class >> example01BasicViolinPlot [ - | boxPlot1 data1 | - data1 := { 12. 12. 13. 14. 15. 24. }. - boxPlot1 := self data: data1. - boxPlot1 xlabel: 'x axis'. - boxPlot1 ylabel: 'y axis'. - ^ boxPlot1 open -] - -{ #category : 'examples' } -RSViolinPlot class >> example02MultipleVionlinsArrayOfArrays [ - - | boxPlot1 | - boxPlot1 := self data: { - {6. 8. 7. 5. 7. 11. 9. }. - {2. 2. 2. 3. 3. 12. 4. 2. 4. 2. 2. 2. 6. 2. 2. 8. 2. 5. 9. 2. 2. 2. 2. 2.}. }. - ^ boxPlot1 open -] - -{ #category : 'examples' } -RSViolinPlot class >> example03HorizontalViolinPlot [ - | violinPlot data1 | - data1 := { 12. 12. 13. 14. 15. 24. }. - violinPlot := self data: data1. - violinPlot xlabel: 'x axis'. - violinPlot ylabel: 'y axis'. - violinPlot horizontal. - ^ violinPlot open -] - -{ #category : 'examples' } -RSViolinPlot class >> example03HorizontalVionlins [ - - | violinPlot | - violinPlot := self data: { - {6. 8. 7. 5. 7. 11. 9. }. - {2. 2. 2. 3. 3. 12. 4. 2. 4. 2. 2. 2. 6. 2. 2. 8. 2. 5. 9. 2. 2. 2. 2. 2.}. }. - violinPlot horizontal. - ^ violinPlot open -] - -{ #category : 'examples' } -RSViolinPlot class >> example03HorizontalVionlinsShowBands [ - - | violinPlot | - violinPlot := self data: { - {6. 8. 7. 5. 7. 11. 9. }. - {2. 2. 2. 3. 3. 12. 4. 2. 4. 2. 2. 2. 6. 2. 2. 8. 2. 5. 9. 2. 2. 2. 2. 2.}. }. - violinPlot horizontal. - violinPlot showBands. - ^ violinPlot open -] - -{ #category : 'examples' } -RSViolinPlot class >> example08Positions [ - | boxPlot1 data1 | - data1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. }. - { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . }. - boxPlot1 := self data: data1. - boxPlot1 positions: { 1. 6. 3. 5.}. - boxPlot1 showBands. - ^ boxPlot1 open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleDays [ - | chart p1 p2 p3 y1 y2 y3 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - - y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - - p1 := self data: y1. "blue" - p2 := self data: y2. "sky blue" - p3 := self data: y3. "orange" - chart := p1 + p2 + p3. - chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. - ^ chart open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleDaysHorizontal [ - | chart p1 p2 p3 y1 y2 y3 | - - y1 := { { 1. 2. 3. 4. 5. } . - { 5. 6. 7. 5. 10. } . - { 12. 12. 13. 14. 15. 24. } }. - - y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . - { 1. 12. 7. 10. 11. 11. 15. 10. } . - { 12. 12. 13. 15. 18. 20. 21. 24. } }. - - y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . - { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . - { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. - - p1 := self data: y1. "blue" - p2 := self data: y2. "sky blue" - p3 := self data: y3. "orange" - chart := p1 + p2 + p3. - chart horizontal. - ^ chart open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleErrorMargin [ - | violinPlot data | - data := {{5. 12. 12. 13. 14. 14. 15. 24. }. {1. 12. 12. 13. 14. 14. 15. 24. }.}. - violinPlot := self data: data. - violinPlot bandwidth: 3. - violinPlot verticalTick - numberOfTicks: 10. - violinPlot errorMargin: 0.09. - ^ violinPlot open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleMedianLineStyle [ - - | violinPlot2 data2 | - - data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}. {14. 14. 16. 12. 13. 10. 11. 14.}.}. - - violinPlot2 := self data: data2. - violinPlot2 medianLine: (RSLine new color: Color red). - violinPlot2 medianLines first color: Color green. - ^ violinPlot2 open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleModel [ - | violinPlot | - violinPlot := self new. - violinPlot models: { SequenceableCollection }. - violinPlot data: [ :cls| cls methods collect: [ :met | met linesOfCode ] ]. - ^ violinPlot open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleModels [ - | violinPlot accessor | - accessor := [ :cls| cls methods collect: [ :met | met linesOfCode ] ]. - violinPlot := self new. - violinPlot models: (String withAllSubclasses - reject: [:cls | (accessor rsValue: cls) isEmpty ]). - violinPlot data: accessor. - ^ violinPlot open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyle [ - - | violinPlot2 data2 paint | - - data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}. {14. 14. 16. 12. 13. 10. 11. 14.}.}. - - violinPlot2 := self data: data2. - paint := LinearGradientPaint fromArray: { 0 -> Color gray. 1 -> Color red }. - paint start: 0@ -50. - paint stop: 0@ 50. - violinPlot2 violinShapes do: [:vs| vs area color: paint ]. - - ^ violinPlot2 open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyleBorder [ - - | violinPlot2 data2 | - - data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}.}. - - violinPlot2 := self data: data2. - violinPlot2 borders do: [ :aRSBorder | aRSBorder color: Color red; dashArray: { 4. }. ]. - - ^ violinPlot2 open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyleBorderColorOfEachViolin [ - - | violinPlot1 data1 | - - data1 := {{6. 8. 7. 5. 7. 11. 9. }. { 12. 12. 13. 15. 18. 20. 21. 24. }}. - - violinPlot1 := self data: data1. - violinPlot1 borders first color: Color green. - violinPlot1 violinShapes last borderColor: Color red. - - ^ violinPlot1 open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyleBorderOfEachViolin [ - - | violinPlot2 data2 | - - data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}. {14. 14. 16. 12. 13. 10. 11. 14.}.}. - - violinPlot2 := self data: data2. - violinPlot2 borders first color: Color red; dashArray: { 4. }. "way 1" - violinPlot2 violinShapes second borderColor: Color orange. "way 2" - violinPlot2 violinShapes third border color: Color green. "way 3" - - ^ violinPlot2 open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyleBordersColor [ - - | violinPlot1 data1 | - - data1 := {{6. 8. 7. 5. 7. 11. 9. }. { 12. 12. 13. 15. 18. 20. 21. 24. }}. - - violinPlot1 := self data: data1. - violinPlot1 bordersColor: Color red. - - ^ violinPlot1 open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyleBordersColors [ - - | violinPlot1 data1 | - - data1 := {{6. 8. 7. 5. 7. 11. 9. }. { 12. 12. 13. 15. 18. 20. 21. 24. }}. - - violinPlot1 := self data: data1. - violinPlot1 bordersColors: {Color red. Color purple.}. - - ^ violinPlot1 open -] - -{ #category : 'examples - styling' } -RSViolinPlot class >> exampleStyleColor [ - | violinPlot data | - data := {{-5. 12. 12. 13. 14. 14. 15. 24. }.}. - violinPlot := self data: data. - violinPlot bandwidth: 3. - violinPlot color: Color purple translucent. - ^ violinPlot open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleViolinPlotClusters [ - | violinPlotA violinPlotB data aRSClusterChart | - data := {{-5. 12. 12. 13. 14. 14. 15. 24. }. {-5. 12. 12. 13. 14. 14. 15. 24. }.}. - - violinPlotA := self data: data. - violinPlotA bandwidth: 3. - "violinPlotA color: Color green." - - violinPlotB := self data: data. - violinPlotB bandwidth: 3. - "violinPlotB color: Color red." - - aRSClusterChart := violinPlotA + violinPlotB. - - ^ aRSClusterChart open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleViolinPlotColors [ - | violinPlot data | - data := {{-5. 12. 12. 13. 14. 14. 15. 24. }. {-5. 12. 12. 13. 14. 14. 15. 24. }.}. - violinPlot := self data: data. - violinPlot bandwidth: 3. - violinPlot colors: { Color green. Color blue }. - - "TO DO: Split violinShapes in compute and a defaultDensityArea" - "violinPlot violinShapes first densityArea border: (RSBorder new color: Color red; dashArray: {4.})." - ^ violinPlot open -] - -{ #category : 'examples' } -RSViolinPlot class >> exampleViolinPlotWithOutliers [ - | violinPlot data | - data := {-5. 12. 12. 13. 14. 14. 15. 24. }. - violinPlot := self data: data. - violinPlot bandwidth: 3. - ^ violinPlot open -] - -{ #category : 'accessing' } -RSViolinPlot class >> model: aModel [ - | boxPlot | - boxPlot := self new. - boxPlot model: aModel. - ^ boxPlot -] - -{ #category : 'accessing' } -RSViolinPlot class >> models: aCollectionOfModels [ - | boxPlot | - boxPlot := self new. - boxPlot models: aCollectionOfModels. - ^ boxPlot -] - -{ #category : 'rendering' } -RSViolinPlot >> bandwidth: aNumber [ - self kernelBandwidth: aNumber -] - -{ #category : 'accessing' } -RSViolinPlot >> borderColor [ - ^ self bordersColor first -] - -{ #category : 'styling' } -RSViolinPlot >> borderColor: aColor [ - self bordersColor: aColor -] - -{ #category : 'styling' } -RSViolinPlot >> borderColors: aCollectionOfColors [ - self bordersColors: aCollectionOfColors -] - -{ #category : 'accessing' } -RSViolinPlot >> borders [ - ^ bandPlotShapes collect: [ :violin | violin border ] -] - -{ #category : 'accessing' } -RSViolinPlot >> bordersColor [ - ^ self borders collect: [ :border | border color ] -] - -{ #category : 'styling' } -RSViolinPlot >> bordersColor: aColor [ - self borders do: [ :border | border color: aColor] -] - -{ #category : 'styling' } -RSViolinPlot >> bordersColors: aCollectionOfColors [ - | borders | - borders := self borders. - aCollectionOfColors doWithIndex: [ :aColor :idx | (borders at: idx) color: aColor] -] - -{ #category : 'rendering' } -RSViolinPlot >> boxes [ - ^ RSGroup new addAll: (bandPlotShapes collect: [ :violin | violin box ]); yourself -] - -{ #category : 'rendering' } -RSViolinPlot >> buildChart [ - chart add: self. - chart - extent: 360 @ 300; - padding: 15. - ^ chart -] - -{ #category : 'initialization' } -RSViolinPlot >> canHandleCluster [ - ^ true -] - -{ #category : 'accessing' } -RSViolinPlot >> colors: collectionOfColors [ - bandPlotShapes doWithIndex: [ :violinShape :idx | violinShape color: (collectionOfColors at: idx) ] -] - -{ #category : 'initialization' } -RSViolinPlot >> computeState [ - self computeXValues. - self computeYValues -] - -{ #category : 'rendering' } -RSViolinPlot >> computeXValues [ - xValues := (1 to: self numberOfViolins) -] - -{ #category : 'initialization' } -RSViolinPlot >> computeYValues [ - | yMax yMin | - yMax := (bandPlotShapes collect: [ :violinShape | violinShape maxDataValue ]) max. - yMin := (bandPlotShapes collect: [ :violinShape | violinShape minDataValue ]) min. - yValues := {yMin. yMax.} -] - -{ #category : 'accessing' } -RSViolinPlot >> createViolinShapeFor: anObject dataset: dataset [ - self assert: dataset notEmpty description: 'dataset can not be empty'. - ^ RSViolinPlotShape new - data: dataset; - model: anObject; - yourself -] - -{ #category : 'accessing' } -RSViolinPlot >> createViolinShapesWithDataSet: collectionOfDatasets [ - | groupOfModels newViolinShapes | - groupOfModels := models. - groupOfModels ifNil: [ groupOfModels := Array new: collectionOfDatasets size ]. - - newViolinShapes := groupOfModels with: collectionOfDatasets collect: [ :anObject :dataset | - self createViolinShapeFor: anObject dataset: dataset. - ]. - self violinShapes: newViolinShapes -] - -{ #category : 'accessing' } -RSViolinPlot >> createdShapes [ - ^ { bandPlotShapes } -] - -{ #category : 'accessing' } -RSViolinPlot >> data [ - | collectionOfDatasets | - collectionOfDatasets := bandPlotShapes collect: [ :violin | violin data ]. - collectionOfDatasets size = 1 ifTrue: [ collectionOfDatasets := collectionOfDatasets first]. - ^ collectionOfDatasets -] - -{ #category : 'accessing' } -RSViolinPlot >> data: dataset [ - - "dataset could be: - - aCollection of raw data (numbers) - - aCollection of collections - - aBlock to get the data from the model (previously stored in model)" - - | collectionOfDatasets | - dataset isBlock ifTrue: [ dataBlock := dataset ]. - collectionOfDatasets := self extractDatasetsFrom: dataset. - self createViolinShapesWithDataSet: collectionOfDatasets. - self computeState -] - -{ #category : 'accessing - computed' } -RSViolinPlot >> datasetsFromModels: data [ - models ifNil: [ ^ data ]. - ^ models collect: [:anOjbect | data rsValue: anOjbect ] -] - -{ #category : 'accessing - defaults' } -RSViolinPlot >> defaultShape [ - ^ RSPolygon new - noPaint -] - -{ #category : 'accessing' } -RSViolinPlot >> errorMargin: aNumber [ - bandPlotShapes do: [ :vs | vs errorMargin: aNumber ] -] - -{ #category : 'accessing - computed' } -RSViolinPlot >> extractDatasetsFrom: data [ - | result datasets | - datasets := self datasetsFromModels: data. - self assert: datasets isCollection description: 'use a collection of numbers'. - self assert: datasets isNotEmpty description: 'You can not have empty collection'. - result := datasets first isCollection - ifTrue: [ datasets ] - ifFalse: [ { datasets } ]. - ^ result -] - -{ #category : 'initialization' } -RSViolinPlot >> initialize [ - super initialize. - bandPlotShapes := OrderedCollection new. - horizontal := false -] - -{ #category : 'rendering' } -RSViolinPlot >> kernelBandwidth: aNumber [ - bandPlotShapes do: [ :violin | violin kernelBandwidth: aNumber ]. - self computeState -] - -{ #category : 'styling' } -RSViolinPlot >> medianLine: aRSLineModel [ - bandPlotShapes do: [ :bs | bs medianLine: aRSLineModel ] -] - -{ #category : 'accessing' } -RSViolinPlot >> medianLines [ - ^ RSGroup new addAll: (bandPlotShapes collect: [ :violin | violin medianLine ]); yourself -] - -{ #category : 'rendering' } -RSViolinPlot >> model [ - ^ model -] - -{ #category : 'rendering' } -RSViolinPlot >> model: aModel [ - model := aModel -] - -{ #category : 'rendering' } -RSViolinPlot >> models [ - ^ models -] - -{ #category : 'rendering' } -RSViolinPlot >> models: aCollectionOfModels [ - models := aCollectionOfModels -] - -{ #category : 'rendering' } -RSViolinPlot >> numberOfViolins [ - ^ bandPlotShapes size -] - -{ #category : 'rendering' } -RSViolinPlot >> renderIn: canvas [ - super renderIn: canvas. - offset ifNil: [ offset := 1 ]. - self computeBandsOffset. - - bandWidth ifNil: [ bandWidth := self defaultBandsWidth ]. - self computeBandsWidth. - - bandPlotShapes doWithIndex: [ :violinPlotShape :idx | - violinPlotShape color ifNil: [ violinPlotShape color: self computeColor. ]. - violinPlotShape bandScale: bandScale. - violinPlotShape dataScale: dataScale. - violinPlotShape renderIn: canvas ] -] - -{ #category : 'rendering' } -RSViolinPlot >> violinShapes [ - ^ bandPlotShapes -] - -{ #category : 'rendering' } -RSViolinPlot >> violinShapes: collectionOfRSViolinPlotShapes [ - bandPlotShapes := collectionOfRSViolinPlotShapes -] +" +`RSDensityPlot` is a visual representation that combines a box plot with a density distribution (an approximation of the frequency) to depict the distribution of a dataset. + +**Responsibility:** +- Plots the density distribution area, the box and the whiskers. +- Provides options to customize the plot. + +**Collaborators:** +- The instance variable `kernelDensity` is an `RSKernelDensity` object responsible for calculating the points of the density curve, which delineates the boundary of the density area. +- The instance variable `statisticalMeasures` is an `RSStatisticalMeasures` object that computes various statistical measures, including mean, median, quartiles, and interquartile range (IQR), to facilitate the construction of the box and whiskers plot. + +**Public API and Key Messages** +- `data: aCollection` to create instances passing dataset (aCollection) as argument. +- `bandwidth: aFloat` to set the bandwith (h) of the kernel in the kernel density estimation function. By default is computed by `RSKernelDensity`. The float passed indicates how soft will be the curve. + +**Instance Variables:** +- `box`: an `RSPolygon` in which the top and bottom correspond to the 3rd and 1st quartiles of the data, respectively. +- `boxPoints`: an `OrderedCollection` of `Point`s that store the computed `box` points without any scaling. +- `boxWidth`: a `Float` that represents the width of the box in the y domain units. +- `centerLine`: an `RSPolyline` representing the whiskers (upper and lower limits) calculated as (q3 - q1)*1.5. +- `centerLinePoints`: an `OrderedCollection` of `Point`s that store the computed `centerLine` points without any scaling. +- `densityArea`: an `RSPolygon` that represents the density distribution of the data, calculated by KDE method (`kernelDensity`). +- `densityAreaPoints`: an `OrderedCollection` of `Point`s that store the computed `densityArea` points without any scaling. +- `kernelDensity`: a `RSKernelDensity` object that made the calculation of the Kernel Density Estimation, several parametres are passed to this object to personalize the curve (bandwidth, kernel, etc.). +- `statisticalMeasures`: a `RSStatisticalMeasures` object that computes various statistical measures, including mean, median, quartiles, and interquartile range (IQR), to facilitate the construction of the box and whiskers plot. +- `title`: aString that stores the title of the chart. +- `xlabel`: aString that stores the label of x axis. +- `ylabel`: aString that stores the label of y axis. + + +**Example:** +```Smalltalk +| violinPlot data | +data := {-5. 12. 12. 13. 14. 14. 15. 24. }. +violinPlot := RSViolinPlot data: data. +violinPlot + bandwidth: 3; + title: 'This is a RSViolinPlot'; + xlabel: 'Data frequency (distribution)'; + ylabel: 'Data domain'. +violinPlot open. +``` +" +Class { + #name : 'RSViolinPlot', + #superclass : 'RSAbstractBandPlot', + #instVars : [ + 'models', + 'model', + 'dataBlock' + ], + #category : 'Roassal-Chart-Core', + #package : 'Roassal-Chart', + #tag : 'Core' +} + +{ #category : 'accessing' } +RSViolinPlot class >> data: aCollection [ + | plot | + plot := self new. + plot data: aCollection. + ^ plot +] + +{ #category : 'examples' } +RSViolinPlot class >> example01BasicViolinPlot [ + | boxPlot1 data1 | + data1 := { 12. 12. 13. 14. 15. 24. }. + boxPlot1 := self data: data1. + boxPlot1 xlabel: 'x axis'. + boxPlot1 ylabel: 'y axis'. + ^ boxPlot1 open +] + +{ #category : 'examples' } +RSViolinPlot class >> example02MultipleVionlinsArrayOfArrays [ + + | boxPlot1 | + boxPlot1 := self data: { + {6. 8. 7. 5. 7. 11. 9. }. + {2. 2. 2. 3. 3. 12. 4. 2. 4. 2. 2. 2. 6. 2. 2. 8. 2. 5. 9. 2. 2. 2. 2. 2.}. }. + ^ boxPlot1 open +] + +{ #category : 'examples' } +RSViolinPlot class >> example03HorizontalViolinPlot [ + | violinPlot data1 | + data1 := { 12. 12. 13. 14. 15. 24. }. + violinPlot := self data: data1. + violinPlot xlabel: 'x axis'. + violinPlot ylabel: 'y axis'. + violinPlot horizontal. + ^ violinPlot open +] + +{ #category : 'examples' } +RSViolinPlot class >> example03HorizontalVionlins [ + + | violinPlot | + violinPlot := self data: { + {6. 8. 7. 5. 7. 11. 9. }. + {2. 2. 2. 3. 3. 12. 4. 2. 4. 2. 2. 2. 6. 2. 2. 8. 2. 5. 9. 2. 2. 2. 2. 2.}. }. + violinPlot horizontal. + ^ violinPlot open +] + +{ #category : 'examples' } +RSViolinPlot class >> example03HorizontalVionlinsShowBands [ + + | violinPlot | + violinPlot := self data: { + {6. 8. 7. 5. 7. 11. 9. }. + {2. 2. 2. 3. 3. 12. 4. 2. 4. 2. 2. 2. 6. 2. 2. 8. 2. 5. 9. 2. 2. 2. 2. 2.}. }. + violinPlot horizontal. + violinPlot showBands. + ^ violinPlot open +] + +{ #category : 'examples' } +RSViolinPlot class >> example08Positions [ + | boxPlot1 data1 | + data1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. }. + { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . }. + boxPlot1 := self data: data1. + boxPlot1 positions: { 1. 6. 3. 5.}. + boxPlot1 showBands. + ^ boxPlot1 open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleDays [ + | chart p1 p2 p3 y1 y2 y3 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + + y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + + p1 := self data: y1. "blue" + p2 := self data: y2. "sky blue" + p3 := self data: y3. "orange" + chart := p1 + p2 + p3. + chart xTickLabels: { 'Day 1'. 'Day 2'. 'Day 3' }. + ^ chart open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleDaysHorizontal [ + | chart p1 p2 p3 y1 y2 y3 | + + y1 := { { 1. 2. 3. 4. 5. } . + { 5. 6. 7. 5. 10. } . + { 12. 12. 13. 14. 15. 24. } }. + + y2 := { { 1. 2. 2. 2. 3. 4. 3. 5. 12. } . + { 1. 12. 7. 10. 11. 11. 15. 10. } . + { 12. 12. 13. 15. 18. 20. 21. 24. } }. + + y3 := { { 1. 2. 3. 3. 3. 5. 3. 5. 5. 7. 8. 5. 6. 10. 11. } . + { 12. 7. 10. 11. 11. 13. 10. 11. 12. 11. 15. 16. } . + { 12. 12. 13. 15. 18. 20. 21. 24. 25. 24. 25. 26. 24. 23. 23. 25. 25. } }. + + p1 := self data: y1. "blue" + p2 := self data: y2. "sky blue" + p3 := self data: y3. "orange" + chart := p1 + p2 + p3. + chart horizontal. + ^ chart open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleErrorMargin [ + | violinPlot data | + data := {{5. 12. 12. 13. 14. 14. 15. 24. }. {1. 12. 12. 13. 14. 14. 15. 24. }.}. + violinPlot := self data: data. + violinPlot bandwidth: 3. + violinPlot verticalTick + numberOfTicks: 10. + violinPlot errorMargin: 0.09. + ^ violinPlot open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleMedianLineStyle [ + + | violinPlot2 data2 | + + data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}. {14. 14. 16. 12. 13. 10. 11. 14.}.}. + + violinPlot2 := self data: data2. + violinPlot2 medianLine: (RSLine new color: Color red). + violinPlot2 medianLines first color: Color green. + ^ violinPlot2 open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleModel [ + | violinPlot | + violinPlot := self new. + violinPlot models: { SequenceableCollection }. + violinPlot data: [ :cls| cls methods collect: [ :met | met linesOfCode ] ]. + ^ violinPlot open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleModels [ + | violinPlot accessor | + accessor := [ :cls| cls methods collect: [ :met | met linesOfCode ] ]. + violinPlot := self new. + violinPlot models: (String withAllSubclasses + reject: [:cls | (accessor rsValue: cls) isEmpty ]). + violinPlot data: accessor. + ^ violinPlot open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyle [ + + | violinPlot2 data2 paint | + + data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}. {14. 14. 16. 12. 13. 10. 11. 14.}.}. + + violinPlot2 := self data: data2. + paint := LinearGradientPaint fromArray: { 0 -> Color gray. 1 -> Color red }. + paint start: 0@ -50. + paint stop: 0@ 50. + violinPlot2 violinShapes do: [:vs| vs area color: paint ]. + + ^ violinPlot2 open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyleBorder [ + + | violinPlot2 data2 | + + data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}.}. + + violinPlot2 := self data: data2. + violinPlot2 borders do: [ :aRSBorder | aRSBorder color: Color red; dashArray: { 4. }. ]. + + ^ violinPlot2 open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyleBorderColorOfEachViolin [ + + | violinPlot1 data1 | + + data1 := {{6. 8. 7. 5. 7. 11. 9. }. { 12. 12. 13. 15. 18. 20. 21. 24. }}. + + violinPlot1 := self data: data1. + violinPlot1 borders first color: Color green. + violinPlot1 violinShapes last borderColor: Color red. + + ^ violinPlot1 open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyleBorderOfEachViolin [ + + | violinPlot2 data2 | + + data2 := {{ 12. 12. 13. 15. 18. 20. 21. 24. }.{15. 15. 18. 12. 13. 10. 11. 14.}. {14. 14. 16. 12. 13. 10. 11. 14.}.}. + + violinPlot2 := self data: data2. + violinPlot2 borders first color: Color red; dashArray: { 4. }. "way 1" + violinPlot2 violinShapes second borderColor: Color orange. "way 2" + violinPlot2 violinShapes third border color: Color green. "way 3" + + ^ violinPlot2 open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyleBordersColor [ + + | violinPlot1 data1 | + + data1 := {{6. 8. 7. 5. 7. 11. 9. }. { 12. 12. 13. 15. 18. 20. 21. 24. }}. + + violinPlot1 := self data: data1. + violinPlot1 bordersColor: Color red. + + ^ violinPlot1 open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyleBordersColors [ + + | violinPlot1 data1 | + + data1 := {{6. 8. 7. 5. 7. 11. 9. }. { 12. 12. 13. 15. 18. 20. 21. 24. }}. + + violinPlot1 := self data: data1. + violinPlot1 bordersColors: {Color red. Color purple.}. + + ^ violinPlot1 open +] + +{ #category : 'examples - styling' } +RSViolinPlot class >> exampleStyleColor [ + | violinPlot data | + data := {{-5. 12. 12. 13. 14. 14. 15. 24. }.}. + violinPlot := self data: data. + violinPlot bandwidth: 3. + violinPlot color: Color purple translucent. + ^ violinPlot open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleViolinPlotClusters [ + | violinPlotA violinPlotB data aRSClusterChart | + data := {{-5. 12. 12. 13. 14. 14. 15. 24. }. {-5. 12. 12. 13. 14. 14. 15. 24. }.}. + + violinPlotA := self data: data. + violinPlotA bandwidth: 3. + "violinPlotA color: Color green." + + violinPlotB := self data: data. + violinPlotB bandwidth: 3. + "violinPlotB color: Color red." + + aRSClusterChart := violinPlotA + violinPlotB. + + ^ aRSClusterChart open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleViolinPlotColors [ + | violinPlot data | + data := {{-5. 12. 12. 13. 14. 14. 15. 24. }. {-5. 12. 12. 13. 14. 14. 15. 24. }.}. + violinPlot := self data: data. + violinPlot bandwidth: 3. + violinPlot colors: { Color green. Color blue }. + + "TO DO: Split violinShapes in compute and a defaultDensityArea" + "violinPlot violinShapes first densityArea border: (RSBorder new color: Color red; dashArray: {4.})." + ^ violinPlot open +] + +{ #category : 'examples' } +RSViolinPlot class >> exampleViolinPlotWithOutliers [ + | violinPlot data | + data := {-5. 12. 12. 13. 14. 14. 15. 24. }. + violinPlot := self data: data. + violinPlot bandwidth: 3. + ^ violinPlot open +] + +{ #category : 'accessing' } +RSViolinPlot class >> model: aModel [ + | boxPlot | + boxPlot := self new. + boxPlot model: aModel. + ^ boxPlot +] + +{ #category : 'accessing' } +RSViolinPlot class >> models: aCollectionOfModels [ + | boxPlot | + boxPlot := self new. + boxPlot models: aCollectionOfModels. + ^ boxPlot +] + +{ #category : 'rendering' } +RSViolinPlot >> bandwidth: aNumber [ + self kernelBandwidth: aNumber +] + +{ #category : 'accessing' } +RSViolinPlot >> borderColor [ + ^ self bordersColor first +] + +{ #category : 'styling' } +RSViolinPlot >> borderColor: aColor [ + self bordersColor: aColor +] + +{ #category : 'styling' } +RSViolinPlot >> borderColors: aCollectionOfColors [ + self bordersColors: aCollectionOfColors +] + +{ #category : 'accessing' } +RSViolinPlot >> borders [ + ^ bandPlotShapes collect: [ :violin | violin border ] +] + +{ #category : 'accessing' } +RSViolinPlot >> bordersColor [ + ^ self borders collect: [ :border | border color ] +] + +{ #category : 'styling' } +RSViolinPlot >> bordersColor: aColor [ + self borders do: [ :border | border color: aColor] +] + +{ #category : 'styling' } +RSViolinPlot >> bordersColors: aCollectionOfColors [ + | borders | + borders := self borders. + aCollectionOfColors doWithIndex: [ :aColor :idx | (borders at: idx) color: aColor] +] + +{ #category : 'rendering' } +RSViolinPlot >> boxes [ + ^ RSGroup new addAll: (bandPlotShapes collect: [ :violin | violin box ]); yourself +] + +{ #category : 'rendering' } +RSViolinPlot >> buildChart [ + chart add: self. + chart + extent: 360 @ 300; + padding: 15. + ^ chart +] + +{ #category : 'initialization' } +RSViolinPlot >> canHandleCluster [ + ^ true +] + +{ #category : 'accessing' } +RSViolinPlot >> colors: collectionOfColors [ + bandPlotShapes doWithIndex: [ :violinShape :idx | violinShape color: (collectionOfColors at: idx) ] +] + +{ #category : 'initialization' } +RSViolinPlot >> computeState [ + self computeXValues. + self computeYValues +] + +{ #category : 'rendering' } +RSViolinPlot >> computeXValues [ + xValues := (1 to: self numberOfViolins) +] + +{ #category : 'initialization' } +RSViolinPlot >> computeYValues [ + | yMax yMin | + yMax := (bandPlotShapes collect: [ :violinShape | violinShape maxDataValue ]) max. + yMin := (bandPlotShapes collect: [ :violinShape | violinShape minDataValue ]) min. + yValues := {yMin. yMax.} +] + +{ #category : 'accessing' } +RSViolinPlot >> createViolinShapeFor: anObject dataset: dataset [ + self assert: dataset notEmpty description: 'dataset can not be empty'. + ^ RSViolinPlotShape new + data: dataset; + model: anObject; + yourself +] + +{ #category : 'accessing' } +RSViolinPlot >> createViolinShapesWithDataSet: collectionOfDatasets [ + | groupOfModels newViolinShapes | + groupOfModels := models. + groupOfModels ifNil: [ groupOfModels := Array new: collectionOfDatasets size ]. + + newViolinShapes := groupOfModels with: collectionOfDatasets collect: [ :anObject :dataset | + self createViolinShapeFor: anObject dataset: dataset. + ]. + self violinShapes: newViolinShapes +] + +{ #category : 'accessing' } +RSViolinPlot >> createdShapes [ + ^ { bandPlotShapes } +] + +{ #category : 'accessing' } +RSViolinPlot >> data [ + | collectionOfDatasets | + collectionOfDatasets := bandPlotShapes collect: [ :violin | violin data ]. + collectionOfDatasets size = 1 ifTrue: [ collectionOfDatasets := collectionOfDatasets first]. + ^ collectionOfDatasets +] + +{ #category : 'accessing' } +RSViolinPlot >> data: dataset [ + + "dataset could be: + - aCollection of raw data (numbers) + - aCollection of collections + - aBlock to get the data from the model (previously stored in model)" + + | collectionOfDatasets | + dataset isBlock ifTrue: [ dataBlock := dataset ]. + collectionOfDatasets := self extractDatasetsFrom: dataset. + self createViolinShapesWithDataSet: collectionOfDatasets. + self computeState +] + +{ #category : 'accessing - computed' } +RSViolinPlot >> datasetsFromModels: data [ + models ifNil: [ ^ data ]. + ^ models collect: [:anOjbect | data rsValue: anOjbect ] +] + +{ #category : 'accessing - defaults' } +RSViolinPlot >> defaultShape [ + ^ RSPolygon new + noPaint +] + +{ #category : 'accessing' } +RSViolinPlot >> errorMargin: aNumber [ + bandPlotShapes do: [ :vs | vs errorMargin: aNumber ] +] + +{ #category : 'accessing - computed' } +RSViolinPlot >> extractDatasetsFrom: data [ + | result datasets | + datasets := self datasetsFromModels: data. + self assert: datasets isCollection description: 'use a collection of numbers'. + self assert: datasets isNotEmpty description: 'You can not have empty collection'. + result := datasets first isCollection + ifTrue: [ datasets ] + ifFalse: [ { datasets } ]. + ^ result +] + +{ #category : 'initialization' } +RSViolinPlot >> initialize [ + super initialize. + bandPlotShapes := OrderedCollection new. + horizontal := false +] + +{ #category : 'rendering' } +RSViolinPlot >> kernelBandwidth: aNumber [ + bandPlotShapes do: [ :violin | violin kernelBandwidth: aNumber ]. + self computeState +] + +{ #category : 'styling' } +RSViolinPlot >> medianLine: aRSLineModel [ + bandPlotShapes do: [ :bs | bs medianLine: aRSLineModel ] +] + +{ #category : 'accessing' } +RSViolinPlot >> medianLines [ + ^ RSGroup new addAll: (bandPlotShapes collect: [ :violin | violin medianLine ]); yourself +] + +{ #category : 'rendering' } +RSViolinPlot >> model [ + ^ model +] + +{ #category : 'rendering' } +RSViolinPlot >> model: aModel [ + model := aModel +] + +{ #category : 'rendering' } +RSViolinPlot >> models [ + ^ models +] + +{ #category : 'rendering' } +RSViolinPlot >> models: aCollectionOfModels [ + models := aCollectionOfModels +] + +{ #category : 'rendering' } +RSViolinPlot >> numberOfViolins [ + ^ bandPlotShapes size +] + +{ #category : 'rendering' } +RSViolinPlot >> renderIn: canvas [ + super renderIn: canvas. + offset ifNil: [ offset := 1 ]. + self computeBandsOffset. + + bandWidth ifNil: [ bandWidth := self defaultBandsWidth ]. + self computeBandsWidth. + + bandPlotShapes doWithIndex: [ :violinPlotShape :idx | + violinPlotShape color ifNil: [ violinPlotShape color: self computeColor. ]. + violinPlotShape bandScale: bandScale. + violinPlotShape dataScale: dataScale. + violinPlotShape renderIn: canvas ] +] + +{ #category : 'rendering' } +RSViolinPlot >> violinShapes [ + ^ bandPlotShapes +] + +{ #category : 'rendering' } +RSViolinPlot >> violinShapes: collectionOfRSViolinPlotShapes [ + bandPlotShapes := collectionOfRSViolinPlotShapes +] diff --git a/src/Roassal-Chart/RSViolinPlotShape.class.st b/src/Roassal-Chart/RSViolinPlotShape.class.st index 3b23b106..001b88cd 100644 --- a/src/Roassal-Chart/RSViolinPlotShape.class.st +++ b/src/Roassal-Chart/RSViolinPlotShape.class.st @@ -1,219 +1,219 @@ -Class { - #name : 'RSViolinPlotShape', - #superclass : 'RSAbstractBandPlotShape', - #instVars : [ - 'boxPlotShape', - 'kernelDensity', - 'densityArea' - ], - #category : 'Roassal-Chart-Plots', - #package : 'Roassal-Chart', - #tag : 'Plots' -} - -{ #category : 'accessing' } -RSViolinPlotShape class >> data: collectionOfData1D [ - | violinShape | - violinShape := self new. - violinShape data: collectionOfData1D. - ^ violinShape -] - -{ #category : 'initialization' } -RSViolinPlotShape >> area [ - ^ densityArea -] - -{ #category : 'band' } -RSViolinPlotShape >> bandOffset: aNumber [ - super bandOffset: aNumber. - boxPlotShape bandOffset: bandOffset -] - -{ #category : 'scales' } -RSViolinPlotShape >> bandScale: aNSScale [ - super bandScale: aNSScale. - boxPlotShape bandScale: aNSScale -] - -{ #category : 'band' } -RSViolinPlotShape >> bandWidth: aNumber [ - super bandWidth: aNumber. - boxPlotShape bandWidth: bandWidth*0.1. - boxPlotShape outlierSize: (bandWidth*0.05) abs -] - -{ #category : 'border' } -RSViolinPlotShape >> border [ - ^ densityArea border -] - -{ #category : 'border' } -RSViolinPlotShape >> borderColor: aColor [ - ^ self border color: aColor -] - -{ #category : 'accessing' } -RSViolinPlotShape >> color [ - ^ color ifNil: [ densityArea color ] -] - -{ #category : 'shapes' } -RSViolinPlotShape >> createShapesAndLines [ - | shapes | - densityArea := self densityArea. - boxPlotShape color: Color white. - shapes := OrderedCollection withAll: { densityArea. }. - shapes addAll: boxPlotShape shapesToRender. - ^ shapes -] - -{ #category : 'accessing' } -RSViolinPlotShape >> data [ - ^ kernelDensity data -] - -{ #category : 'accessing' } -RSViolinPlotShape >> data: dataset [ - | aCollection | - aCollection := dataset rsValue: model. - kernelDensity data: aCollection. - boxPlotShape data: aCollection -] - -{ #category : 'scales' } -RSViolinPlotShape >> dataScale: aNSScale [ - super dataScale: aNSScale. - boxPlotShape dataScale: aNSScale -] - -{ #category : 'accessing' } -RSViolinPlotShape >> dataSorted [ - ^ boxPlotShape data -] - -{ #category : 'defaults' } -RSViolinPlotShape >> defaultBoxPlotShape [ - ^ RSBoxPlotShape new -] - -{ #category : 'defaults' } -RSViolinPlotShape >> defaultDensityArea [ - ^ RSPolygon new - noPaint - borderColor: Color black -] - -{ #category : 'defaults' } -RSViolinPlotShape >> defaultKernelDensity [ - "Check the default Kernel function in RSKernelDensity >> defaultKernel (by default a gaussian function)" - ^ RSKernelDensity new -] - -{ #category : 'initialization' } -RSViolinPlotShape >> densityArea [ - | densityCurve densityAreaPoints maxX minX | - - "invert x and y" - densityCurve := (kernelDensity densityCurve) collect: [:point | - (point y @ point x) - ]. - - "normalize x" - maxX := (densityCurve collect: [ :point | point x ]) max. - minX := (densityCurve collect: [ :point | point x ]) min. - densityCurve := densityCurve collect: [:point | - | normalizedX | - normalizedX := ((point x)-minX)/(maxX - minX). - ((normalizedX) @ point y) - ]. - - - densityAreaPoints := densityCurve copy. - densityAreaPoints := densityAreaPoints collect: [ :point | - ((bandWidth/2 * point x)+bandOffset)@(dataScale scale: point y) - ]. - densityCurve reverseDo: [ :point | - densityAreaPoints add: (bandOffset-(bandWidth/2 * point x))@(dataScale scale: point y) - ]. - - densityArea points: densityAreaPoints. - color := color ifNil: [ densityArea color ]. - densityArea color: color. - ^ densityArea -] - -{ #category : 'private' } -RSViolinPlotShape >> errorMargin: aNumber [ - kernelDensity errorMargin: aNumber -] - -{ #category : 'initialization' } -RSViolinPlotShape >> initialize [ - super initialize. - kernelDensity := self defaultKernelDensity. - boxPlotShape := self defaultBoxPlotShape. - densityArea := self defaultDensityArea. - horizontal := false. - shouldShowBand := false -] - -{ #category : 'accessing - attributes' } -RSViolinPlotShape >> kernelBandwidth: aNumber [ - kernelDensity bandwidth: aNumber -] - -{ #category : 'rendering' } -RSViolinPlotShape >> maxDataValue [ - | maxYValueOfCurve | - maxYValueOfCurve := (kernelDensity densityCurve collect: [ :point | point x ]) max. - ^ boxPlotShape maxDataValue max: maxYValueOfCurve -] - -{ #category : 'accessing' } -RSViolinPlotShape >> medianLine [ - ^ boxPlotShape medianLine -] - -{ #category : 'shapes' } -RSViolinPlotShape >> medianLine: aRSLineModel [ - boxPlotShape medianLine: aRSLineModel copy -] - -{ #category : 'rendering' } -RSViolinPlotShape >> minDataValue [ - | minYValueOfCurve | - minYValueOfCurve := (kernelDensity densityCurve collect: [ :point | point x ]) min. - ^ boxPlotShape minDataValue min: minYValueOfCurve -] - -{ #category : 'accessing - attributes' } -RSViolinPlotShape >> model: aModel [ - super model: aModel. - boxPlotShape model: aModel -] - -{ #category : 'initialization' } -RSViolinPlotShape >> outlierSize: aDimensionInPixels [ - boxPlotShape outlierSize: aDimensionInPixels -] - -{ #category : 'rendering' } -RSViolinPlotShape >> renderIn: canvas [ - self addChildrenToComposite. - canvas add: self. - ^ canvas -] - -{ #category : 'initialization' } -RSViolinPlotShape >> scalePoint: aPoint [ - ^ (bandScale scale: aPoint x) @ (dataScale scale: aPoint y) -] - -{ #category : 'initialization' } -RSViolinPlotShape >> scales: collectionOfNSScales [ - self bandScale: collectionOfNSScales first. - self dataScale: collectionOfNSScales second. - boxPlotShape bandScale: collectionOfNSScales first. - boxPlotShape dataScale: collectionOfNSScales second -] +Class { + #name : 'RSViolinPlotShape', + #superclass : 'RSAbstractBandPlotShape', + #instVars : [ + 'boxPlotShape', + 'kernelDensity', + 'densityArea' + ], + #category : 'Roassal-Chart-Plots', + #package : 'Roassal-Chart', + #tag : 'Plots' +} + +{ #category : 'accessing' } +RSViolinPlotShape class >> data: collectionOfData1D [ + | violinShape | + violinShape := self new. + violinShape data: collectionOfData1D. + ^ violinShape +] + +{ #category : 'initialization' } +RSViolinPlotShape >> area [ + ^ densityArea +] + +{ #category : 'band' } +RSViolinPlotShape >> bandOffset: aNumber [ + super bandOffset: aNumber. + boxPlotShape bandOffset: bandOffset +] + +{ #category : 'scales' } +RSViolinPlotShape >> bandScale: aNSScale [ + super bandScale: aNSScale. + boxPlotShape bandScale: aNSScale +] + +{ #category : 'band' } +RSViolinPlotShape >> bandWidth: aNumber [ + super bandWidth: aNumber. + boxPlotShape bandWidth: bandWidth*0.1. + boxPlotShape outlierSize: (bandWidth*0.05) abs +] + +{ #category : 'border' } +RSViolinPlotShape >> border [ + ^ densityArea border +] + +{ #category : 'border' } +RSViolinPlotShape >> borderColor: aColor [ + ^ self border color: aColor +] + +{ #category : 'accessing' } +RSViolinPlotShape >> color [ + ^ color ifNil: [ densityArea color ] +] + +{ #category : 'shapes' } +RSViolinPlotShape >> createShapesAndLines [ + | shapes | + densityArea := self densityArea. + boxPlotShape color: Color white. + shapes := OrderedCollection withAll: { densityArea. }. + shapes addAll: boxPlotShape shapesToRender. + ^ shapes +] + +{ #category : 'accessing' } +RSViolinPlotShape >> data [ + ^ kernelDensity data +] + +{ #category : 'accessing' } +RSViolinPlotShape >> data: dataset [ + | aCollection | + aCollection := dataset rsValue: model. + kernelDensity data: aCollection. + boxPlotShape data: aCollection +] + +{ #category : 'scales' } +RSViolinPlotShape >> dataScale: aNSScale [ + super dataScale: aNSScale. + boxPlotShape dataScale: aNSScale +] + +{ #category : 'accessing' } +RSViolinPlotShape >> dataSorted [ + ^ boxPlotShape data +] + +{ #category : 'defaults' } +RSViolinPlotShape >> defaultBoxPlotShape [ + ^ RSBoxPlotShape new +] + +{ #category : 'defaults' } +RSViolinPlotShape >> defaultDensityArea [ + ^ RSPolygon new + noPaint + borderColor: Color black +] + +{ #category : 'defaults' } +RSViolinPlotShape >> defaultKernelDensity [ + "Check the default Kernel function in RSKernelDensity >> defaultKernel (by default a gaussian function)" + ^ RSKernelDensity new +] + +{ #category : 'initialization' } +RSViolinPlotShape >> densityArea [ + | densityCurve densityAreaPoints maxX minX | + + "invert x and y" + densityCurve := (kernelDensity densityCurve) collect: [:point | + (point y @ point x) + ]. + + "normalize x" + maxX := (densityCurve collect: [ :point | point x ]) max. + minX := (densityCurve collect: [ :point | point x ]) min. + densityCurve := densityCurve collect: [:point | + | normalizedX | + normalizedX := ((point x)-minX)/(maxX - minX). + ((normalizedX) @ point y) + ]. + + + densityAreaPoints := densityCurve copy. + densityAreaPoints := densityAreaPoints collect: [ :point | + ((bandWidth/2 * point x)+bandOffset)@(dataScale scale: point y) + ]. + densityCurve reverseDo: [ :point | + densityAreaPoints add: (bandOffset-(bandWidth/2 * point x))@(dataScale scale: point y) + ]. + + densityArea points: densityAreaPoints. + color := color ifNil: [ densityArea color ]. + densityArea color: color. + ^ densityArea +] + +{ #category : 'private' } +RSViolinPlotShape >> errorMargin: aNumber [ + kernelDensity errorMargin: aNumber +] + +{ #category : 'initialization' } +RSViolinPlotShape >> initialize [ + super initialize. + kernelDensity := self defaultKernelDensity. + boxPlotShape := self defaultBoxPlotShape. + densityArea := self defaultDensityArea. + horizontal := false. + shouldShowBand := false +] + +{ #category : 'accessing - attributes' } +RSViolinPlotShape >> kernelBandwidth: aNumber [ + kernelDensity bandwidth: aNumber +] + +{ #category : 'rendering' } +RSViolinPlotShape >> maxDataValue [ + | maxYValueOfCurve | + maxYValueOfCurve := (kernelDensity densityCurve collect: [ :point | point x ]) max. + ^ boxPlotShape maxDataValue max: maxYValueOfCurve +] + +{ #category : 'accessing' } +RSViolinPlotShape >> medianLine [ + ^ boxPlotShape medianLine +] + +{ #category : 'shapes' } +RSViolinPlotShape >> medianLine: aRSLineModel [ + boxPlotShape medianLine: aRSLineModel copy +] + +{ #category : 'rendering' } +RSViolinPlotShape >> minDataValue [ + | minYValueOfCurve | + minYValueOfCurve := (kernelDensity densityCurve collect: [ :point | point x ]) min. + ^ boxPlotShape minDataValue min: minYValueOfCurve +] + +{ #category : 'accessing - attributes' } +RSViolinPlotShape >> model: aModel [ + super model: aModel. + boxPlotShape model: aModel +] + +{ #category : 'initialization' } +RSViolinPlotShape >> outlierSize: aDimensionInPixels [ + boxPlotShape outlierSize: aDimensionInPixels +] + +{ #category : 'rendering' } +RSViolinPlotShape >> renderIn: canvas [ + self addChildrenToComposite. + canvas add: self. + ^ canvas +] + +{ #category : 'initialization' } +RSViolinPlotShape >> scalePoint: aPoint [ + ^ (bandScale scale: aPoint x) @ (dataScale scale: aPoint y) +] + +{ #category : 'initialization' } +RSViolinPlotShape >> scales: collectionOfNSScales [ + self bandScale: collectionOfNSScales first. + self dataScale: collectionOfNSScales second. + boxPlotShape bandScale: collectionOfNSScales first. + boxPlotShape dataScale: collectionOfNSScales second +] diff --git a/src/Roassal-Chart/RSXLabelDecoration.class.st b/src/Roassal-Chart/RSXLabelDecoration.class.st index 6ef77c81..dadb1fbd 100644 --- a/src/Roassal-Chart/RSXLabelDecoration.class.st +++ b/src/Roassal-Chart/RSXLabelDecoration.class.st @@ -1,49 +1,49 @@ -" - -`RSXLabelDecoration` adds a title to the X-axis in a chart. - -*Responsibility*: Add a title and allows for its style to be defined (e.g., color, size, rotation, offset) - -*Collaborators*: must be added to an `RSChart` - -*Example*: -```Smalltalk -x := -3.14 to: 3.14 by: 0.1. -y := x sin. - -p := RSLinePlot new x: x y: y. -p addDecoration: (RSChartTitleDecoration new title: 'hello'; fontSize: 20). -p addDecoration: (RSXLabelDecoration new title: 'My X Axis'; fontSize: 12). -p addDecoration: (RSYLabelDecoration new title: 'My Y Axis'; fontSize: 15; horizontal). -p open -``` -" -Class { - #name : 'RSXLabelDecoration', - #superclass : 'RSAbstractLabelDecoration', - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'public - positioning shorcuts' } -RSXLabelDecoration >> above [ - self location - offset: 0@ -5; - above; - center -] - -{ #category : 'public - positioning shorcuts' } -RSXLabelDecoration >> below [ - self location - offset: 0@5; - below; - center -] - -{ #category : 'initialization' } -RSXLabelDecoration >> initialize [ - super initialize. - self below -] +" + +`RSXLabelDecoration` adds a title to the X-axis in a chart. + +*Responsibility*: Add a title and allows for its style to be defined (e.g., color, size, rotation, offset) + +*Collaborators*: must be added to an `RSChart` + +*Example*: +```Smalltalk +x := -3.14 to: 3.14 by: 0.1. +y := x sin. + +p := RSLinePlot new x: x y: y. +p addDecoration: (RSChartTitleDecoration new title: 'hello'; fontSize: 20). +p addDecoration: (RSXLabelDecoration new title: 'My X Axis'; fontSize: 12). +p addDecoration: (RSYLabelDecoration new title: 'My Y Axis'; fontSize: 15; horizontal). +p open +``` +" +Class { + #name : 'RSXLabelDecoration', + #superclass : 'RSAbstractLabelDecoration', + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'public - positioning shorcuts' } +RSXLabelDecoration >> above [ + self location + offset: 0@ -5; + above; + center +] + +{ #category : 'public - positioning shorcuts' } +RSXLabelDecoration >> below [ + self location + offset: 0@5; + below; + center +] + +{ #category : 'initialization' } +RSXLabelDecoration >> initialize [ + super initialize. + self below +] diff --git a/src/Roassal-Chart/RSXMarkerDecoration.class.st b/src/Roassal-Chart/RSXMarkerDecoration.class.st index 254b374e..baae9722 100644 --- a/src/Roassal-Chart/RSXMarkerDecoration.class.st +++ b/src/Roassal-Chart/RSXMarkerDecoration.class.st @@ -1,63 +1,63 @@ -" -Set some markers along the X axis. - --=-=-=-=-=-=-=-=-= -x := (-3.14 to: 3.14 by: 0.01). - -p := RSLinePlot new. -p x: x y: x sin * 0.22 + 0.5. - -p verticalTick asFloat. - -p addDecoration: (RSYMarkerDecoration new average). -p addDecoration: (RSYMarkerDecoration new min). -p addDecoration: (RSYMarkerDecoration new max). - -p addDecoration: (RSXMarkerDecoration new max). -p addDecoration: (RSXMarkerDecoration new min). -p addDecoration: (RSXMarkerDecoration new value: 0). -p --=-=-=-=-=-=-=-=-= -" -Class { - #name : 'RSXMarkerDecoration', - #superclass : 'RSAbstractMarkerDecoration', - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'public - configuration' } -RSXMarkerDecoration >> average [ - getRelevantValueBlock := [ :aPlot | aPlot definedValuesX average ] -] - -{ #category : 'rendering' } -RSXMarkerDecoration >> createMarkerLineFromPlot: aPlot [ - - | value | - value := self getValueToBeMarkedFromPlot: aPlot. - - ^ self shape copy - from: (aPlot xScale scale: value) @ 0; - to: (aPlot xScale scale: value) @ chart extent y negated -] - -{ #category : 'public - configuration' } -RSXMarkerDecoration >> max [ - getRelevantValueBlock := [ :p | p maxChartValueX ] -] - -{ #category : 'public - configuration' } -RSXMarkerDecoration >> min [ - getRelevantValueBlock := [ :p | p minChartValueX ] -] - -{ #category : 'public - configuration' } -RSXMarkerDecoration >> sumUpTo: ratio [ - self assert: (ratio between: 0 and: 1). - getRelevantValueBlock := [ :aPlot | | total | - total := aPlot definedValuesY sum. - (1 to: aPlot definedValuesY size) detect: [ :i | - (aPlot definedValuesY first: i) sum >= (ratio * total) ] ] -] +" +Set some markers along the X axis. + +-=-=-=-=-=-=-=-=-= +x := (-3.14 to: 3.14 by: 0.01). + +p := RSLinePlot new. +p x: x y: x sin * 0.22 + 0.5. + +p verticalTick asFloat. + +p addDecoration: (RSYMarkerDecoration new average). +p addDecoration: (RSYMarkerDecoration new min). +p addDecoration: (RSYMarkerDecoration new max). + +p addDecoration: (RSXMarkerDecoration new max). +p addDecoration: (RSXMarkerDecoration new min). +p addDecoration: (RSXMarkerDecoration new value: 0). +p +-=-=-=-=-=-=-=-=-= +" +Class { + #name : 'RSXMarkerDecoration', + #superclass : 'RSAbstractMarkerDecoration', + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'public - configuration' } +RSXMarkerDecoration >> average [ + getRelevantValueBlock := [ :aPlot | aPlot definedValuesX average ] +] + +{ #category : 'rendering' } +RSXMarkerDecoration >> createMarkerLineFromPlot: aPlot [ + + | value | + value := self getValueToBeMarkedFromPlot: aPlot. + + ^ self shape copy + from: (aPlot xScale scale: value) @ 0; + to: (aPlot xScale scale: value) @ chart extent y negated +] + +{ #category : 'public - configuration' } +RSXMarkerDecoration >> max [ + getRelevantValueBlock := [ :p | p maxChartValueX ] +] + +{ #category : 'public - configuration' } +RSXMarkerDecoration >> min [ + getRelevantValueBlock := [ :p | p minChartValueX ] +] + +{ #category : 'public - configuration' } +RSXMarkerDecoration >> sumUpTo: ratio [ + self assert: (ratio between: 0 and: 1). + getRelevantValueBlock := [ :aPlot | | total | + total := aPlot definedValuesY sum. + (1 to: aPlot definedValuesY size) detect: [ :i | + (aPlot definedValuesY first: i) sum >= (ratio * total) ] ] +] diff --git a/src/Roassal-Chart/RSYLabelDecoration.class.st b/src/Roassal-Chart/RSYLabelDecoration.class.st index f916be3d..35b37843 100644 --- a/src/Roassal-Chart/RSYLabelDecoration.class.st +++ b/src/Roassal-Chart/RSYLabelDecoration.class.st @@ -1,52 +1,52 @@ -" - -`RSYLabelDecoration` adds a title to the Y-axis in a chart. - -*Responsibility*: Add a title and allows for its style to be defined (e.g., color, size, rotation, offset) - -*Collaborators*: must be added to an `RSChart` - -*Example*: -```Smalltalk -x := -3.14 to: 3.14 by: 0.1. -y := x sin. - -p := RSLinePlot new x: x y: y. -p addDecoration: (RSChartTitleDecoration new title: 'hello'; fontSize: 20). -p addDecoration: (RSXLabelDecoration new title: 'My X Axis'; fontSize: 12). -p addDecoration: (RSYLabelDecoration new title: 'My Y Axis'; fontSize: 15; horizontal). -p open -``` -" -Class { - #name : 'RSYLabelDecoration', - #superclass : 'RSAbstractLabelDecoration', - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'initialization' } -RSYLabelDecoration >> initialize [ - super initialize. - self vertical. - self left -] - -{ #category : 'accessing' } -RSYLabelDecoration >> left [ - self location - offset: -5 @ 0; - left; - outer; - middle -] - -{ #category : 'accessing' } -RSYLabelDecoration >> right [ - self location - offset: 5 @ 0; - right; - outer; - middle -] +" + +`RSYLabelDecoration` adds a title to the Y-axis in a chart. + +*Responsibility*: Add a title and allows for its style to be defined (e.g., color, size, rotation, offset) + +*Collaborators*: must be added to an `RSChart` + +*Example*: +```Smalltalk +x := -3.14 to: 3.14 by: 0.1. +y := x sin. + +p := RSLinePlot new x: x y: y. +p addDecoration: (RSChartTitleDecoration new title: 'hello'; fontSize: 20). +p addDecoration: (RSXLabelDecoration new title: 'My X Axis'; fontSize: 12). +p addDecoration: (RSYLabelDecoration new title: 'My Y Axis'; fontSize: 15; horizontal). +p open +``` +" +Class { + #name : 'RSYLabelDecoration', + #superclass : 'RSAbstractLabelDecoration', + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'initialization' } +RSYLabelDecoration >> initialize [ + super initialize. + self vertical. + self left +] + +{ #category : 'accessing' } +RSYLabelDecoration >> left [ + self location + offset: -5 @ 0; + left; + outer; + middle +] + +{ #category : 'accessing' } +RSYLabelDecoration >> right [ + self location + offset: 5 @ 0; + right; + outer; + middle +] diff --git a/src/Roassal-Chart/RSYMarkerDecoration.class.st b/src/Roassal-Chart/RSYMarkerDecoration.class.st index dbf2388a..3f8d5a4d 100644 --- a/src/Roassal-Chart/RSYMarkerDecoration.class.st +++ b/src/Roassal-Chart/RSYMarkerDecoration.class.st @@ -1,54 +1,54 @@ -" -Set some markers along the Y axis. - --=-=-=-=-=-=-=-=-= -x := (-3.14 to: 3.14 by: 0.01). - -p := RSLinePlot new. -p x: x y: x sin * 0.22 + 0.5. - - -p verticalTick asFloat. - -p addDecoration: (RSYMarkerDecoration new average). -p addDecoration: (RSYMarkerDecoration new min). -p addDecoration: (RSYMarkerDecoration new max). - -p addDecoration: (RSXMarkerDecoration new max). -p addDecoration: (RSXMarkerDecoration new min). -p addDecoration: (RSXMarkerDecoration new value: 0). -p --=-=-=-=-=-=-=-=-= -" -Class { - #name : 'RSYMarkerDecoration', - #superclass : 'RSAbstractMarkerDecoration', - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'public - configuration' } -RSYMarkerDecoration >> average [ - getRelevantValueBlock := [ :aPlot | aPlot definedValuesY average ] -] - -{ #category : 'rendering' } -RSYMarkerDecoration >> createMarkerLineFromPlot: aPlot [ - - | value | - value := self getValueToBeMarkedFromPlot: aPlot. - ^ RSLine new - from: 0 @ (aPlot yScale scale: value); - to: chart extent x @ (aPlot yScale scale: value) -] - -{ #category : 'public - configuration' } -RSYMarkerDecoration >> max [ - getRelevantValueBlock := [ :p | p maxChartValueY ] -] - -{ #category : 'public - configuration' } -RSYMarkerDecoration >> min [ - getRelevantValueBlock := [ :p | p minChartValueY ] -] +" +Set some markers along the Y axis. + +-=-=-=-=-=-=-=-=-= +x := (-3.14 to: 3.14 by: 0.01). + +p := RSLinePlot new. +p x: x y: x sin * 0.22 + 0.5. + + +p verticalTick asFloat. + +p addDecoration: (RSYMarkerDecoration new average). +p addDecoration: (RSYMarkerDecoration new min). +p addDecoration: (RSYMarkerDecoration new max). + +p addDecoration: (RSXMarkerDecoration new max). +p addDecoration: (RSXMarkerDecoration new min). +p addDecoration: (RSXMarkerDecoration new value: 0). +p +-=-=-=-=-=-=-=-=-= +" +Class { + #name : 'RSYMarkerDecoration', + #superclass : 'RSAbstractMarkerDecoration', + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'public - configuration' } +RSYMarkerDecoration >> average [ + getRelevantValueBlock := [ :aPlot | aPlot definedValuesY average ] +] + +{ #category : 'rendering' } +RSYMarkerDecoration >> createMarkerLineFromPlot: aPlot [ + + | value | + value := self getValueToBeMarkedFromPlot: aPlot. + ^ RSLine new + from: 0 @ (aPlot yScale scale: value); + to: chart extent x @ (aPlot yScale scale: value) +] + +{ #category : 'public - configuration' } +RSYMarkerDecoration >> max [ + getRelevantValueBlock := [ :p | p maxChartValueY ] +] + +{ #category : 'public - configuration' } +RSYMarkerDecoration >> min [ + getRelevantValueBlock := [ :p | p minChartValueY ] +] diff --git a/src/Roassal-Chart/RSZoomTickDecoration.class.st b/src/Roassal-Chart/RSZoomTickDecoration.class.st index 3bd222b6..04827176 100644 --- a/src/Roassal-Chart/RSZoomTickDecoration.class.st +++ b/src/Roassal-Chart/RSZoomTickDecoration.class.st @@ -1,197 +1,197 @@ -" -this decoration is an example of how to put some interactions in the canvas and interact with some objects like the ticks in the chart -" -Class { - #name : 'RSZoomTickDecoration', - #superclass : 'RSAbstractChartDecoration', - #instVars : [ - 'resetLabel', - 'visibleRectangle', - 'currentShapes', - 'elasticBox' - ], - #category : 'Roassal-Chart-Decoration', - #package : 'Roassal-Chart', - #tag : 'Decoration' -} - -{ #category : 'accessing' } -RSZoomTickDecoration >> boxScale [ - | spine | - spine := chart spine encompassingRectangle. - ^ NSScale linear - domain: { spine origin. spine corner }; - range: { visibleRectangle origin. visibleRectangle corner} -] - -{ #category : 'accessing' } -RSZoomTickDecoration >> chartElements [ - | elements | - elements := OrderedCollection new. - elements addAll: chart plots. - chart verticalTick ifNotNil: [:tick | elements add: tick ]. - chart horizontalTick ifNotNil: [:tick | elements add: tick ]. - ^ elements -] - -{ #category : 'accessing' } -RSZoomTickDecoration >> computeVisibleRectangle: eventRectangle [ - | rect spine scale | - "spine the the chart box" - spine := chart spine encompassingRectangle. - rect := (eventRectangle origin max: spine origin) - corner: (eventRectangle corner min: spine corner). - scale := self boxScale. - ^ (scale scale: rect origin) corner: (scale scale: rect corner) -] - -{ #category : 'accessing' } -RSZoomTickDecoration >> createElasticBox [ - ^ RSElasticBoxInteraction new - targetShapes: #(); - in: [ :elastic | elastic box border width: 0.2 ]; - when: RSSelectionEndEvent send: #updateSelection: to: self; - yourself -] - -{ #category : 'accessing' } -RSZoomTickDecoration >> createResetLabel [ - | label | - label := self shape copy - text: 'Reset zoom'; - when: RSMouseClick send: #resetZoom: to: self; - yourself. - label @ RSHighlightable blue. - RSLocation new - inner; top; right; - move: label on: self chart spine. - ^ label -] - -{ #category : 'accessing' } -RSZoomTickDecoration >> createdShapes [ - ^ { resetLabel } -] - -{ #category : 'initialization' } -RSZoomTickDecoration >> defaultShape [ - ^ RSLabel new - fontSize: 5; - bold; - yourself -] - -{ #category : 'accessing' } -RSZoomTickDecoration >> elasticBox [ - ^ elasticBox ifNil: [ elasticBox := self createElasticBox ] -] - -{ #category : 'rendering' } -RSZoomTickDecoration >> renderIn: canvas [ - canvas addInteraction: self elasticBox. - resetLabel := self createResetLabel. - canvas add: resetLabel. - - currentShapes := #(). - self resetZoom: canvas -] - -{ #category : 'rendering' } -RSZoomTickDecoration >> renderPlots [ - | visibleShapes scale | - visibleShapes := chart createdShapes select: [ :s | s encompassingRectangle intersects: visibleRectangle ]. - scale := chart spine extent/visibleRectangle extent. - "I use a composite shape because the composite can scale the selected shapes, - but more important we can clip the selected shapes" - visibleShapes := RSComposite new - fromRectangle: visibleRectangle; - addAll: (visibleShapes collect: [:s | | copy | - "it is necessary to create a copy of the current shape because we will modify its position" - copy := s copy. - copy matrix: s matrix copy. - copy translateBy: visibleRectangle floatCenter negated. - copy ]); - scaleBy: scale; - translateBy: chart spine encompassingRectangle floatCenter - visibleRectangle floatCenter; - yourself. - currentShapes add: visibleShapes -] - -{ #category : 'rendering' } -RSZoomTickDecoration >> renderPlotsAndTicksIn: canvas [ - currentShapes := OrderedCollection new. - self renderPlots. - self renderTicks. - canvas addAll: currentShapes -] - -{ #category : 'rendering' } -RSZoomTickDecoration >> renderTicks [ - self renderYAxis. - self renderXAxis -] - -{ #category : 'rendering' } -RSZoomTickDecoration >> renderXAxis [ - | horizontalTick tickShapes newPlot scale | - horizontalTick := chart horizontalTick. - horizontalTick ifNil: [ ^ self ]. - "should be a copy to not lose created shapes from original horizontal tick" - scale := horizontalTick xScale. - "scale inspect." - newPlot := chart copy. - newPlot - minValueX: (scale invert: visibleRectangle origin x); - maxValueX: (scale invert: visibleRectangle corner x). - - horizontalTick := horizontalTick copy. - horizontalTick xScale: nil. - horizontalTick chart: newPlot. - - tickShapes := RSGroup new. - - horizontalTick - beforeRenderingIn: newPlot; - renderIn: tickShapes. - - currentShapes addAll: tickShapes -] - -{ #category : 'rendering' } -RSZoomTickDecoration >> renderYAxis [ - | verticalTick tickShapes ymin ymax scale | - verticalTick := chart verticalTick. - verticalTick ifNil: [ ^ self ]. - - ymin := visibleRectangle origin y. - ymax := visibleRectangle corner y. - tickShapes := verticalTick createdShapes - select: [ :s |s position y between: ymin and: ymax ] - thenCollect: [:s | | copy | - copy := s copy. - s isLine ifFalse: [ copy matrix: s matrix copy ]. - copy ]. - scale := self boxScale. - tickShapes do: [ :each | each translateTo: each position x @ (scale invert: each position) y ]. - currentShapes addAll: tickShapes -] - -{ #category : 'events' } -RSZoomTickDecoration >> resetZoom: evt [ - | canvas | - canvas := evt canvas. - visibleRectangle := self chart spine encompassingRectangle. - currentShapes do: #remove. - currentShapes := self chartElements flatCollect: [ :element | element createdShapes ifNil: [#()] ]. - canvas addAll: currentShapes. - resetLabel pushFront. - evt signalUpdate -] - -{ #category : 'events' } -RSZoomTickDecoration >> updateSelection: evt [ - visibleRectangle := self computeVisibleRectangle: evt shape encompassingRectangle. - currentShapes do: #remove. - self renderPlotsAndTicksIn: evt canvas. - resetLabel pushFront -] +" +this decoration is an example of how to put some interactions in the canvas and interact with some objects like the ticks in the chart +" +Class { + #name : 'RSZoomTickDecoration', + #superclass : 'RSAbstractChartDecoration', + #instVars : [ + 'resetLabel', + 'visibleRectangle', + 'currentShapes', + 'elasticBox' + ], + #category : 'Roassal-Chart-Decoration', + #package : 'Roassal-Chart', + #tag : 'Decoration' +} + +{ #category : 'accessing' } +RSZoomTickDecoration >> boxScale [ + | spine | + spine := chart spine encompassingRectangle. + ^ NSScale linear + domain: { spine origin. spine corner }; + range: { visibleRectangle origin. visibleRectangle corner} +] + +{ #category : 'accessing' } +RSZoomTickDecoration >> chartElements [ + | elements | + elements := OrderedCollection new. + elements addAll: chart plots. + chart verticalTick ifNotNil: [:tick | elements add: tick ]. + chart horizontalTick ifNotNil: [:tick | elements add: tick ]. + ^ elements +] + +{ #category : 'accessing' } +RSZoomTickDecoration >> computeVisibleRectangle: eventRectangle [ + | rect spine scale | + "spine the the chart box" + spine := chart spine encompassingRectangle. + rect := (eventRectangle origin max: spine origin) + corner: (eventRectangle corner min: spine corner). + scale := self boxScale. + ^ (scale scale: rect origin) corner: (scale scale: rect corner) +] + +{ #category : 'accessing' } +RSZoomTickDecoration >> createElasticBox [ + ^ RSElasticBoxInteraction new + targetShapes: #(); + in: [ :elastic | elastic box border width: 0.2 ]; + when: RSSelectionEndEvent send: #updateSelection: to: self; + yourself +] + +{ #category : 'accessing' } +RSZoomTickDecoration >> createResetLabel [ + | label | + label := self shape copy + text: 'Reset zoom'; + when: RSMouseClick send: #resetZoom: to: self; + yourself. + label @ RSHighlightable blue. + RSLocation new + inner; top; right; + move: label on: self chart spine. + ^ label +] + +{ #category : 'accessing' } +RSZoomTickDecoration >> createdShapes [ + ^ { resetLabel } +] + +{ #category : 'initialization' } +RSZoomTickDecoration >> defaultShape [ + ^ RSLabel new + fontSize: 5; + bold; + yourself +] + +{ #category : 'accessing' } +RSZoomTickDecoration >> elasticBox [ + ^ elasticBox ifNil: [ elasticBox := self createElasticBox ] +] + +{ #category : 'rendering' } +RSZoomTickDecoration >> renderIn: canvas [ + canvas addInteraction: self elasticBox. + resetLabel := self createResetLabel. + canvas add: resetLabel. + + currentShapes := #(). + self resetZoom: canvas +] + +{ #category : 'rendering' } +RSZoomTickDecoration >> renderPlots [ + | visibleShapes scale | + visibleShapes := chart createdShapes select: [ :s | s encompassingRectangle intersects: visibleRectangle ]. + scale := chart spine extent/visibleRectangle extent. + "I use a composite shape because the composite can scale the selected shapes, + but more important we can clip the selected shapes" + visibleShapes := RSComposite new + fromRectangle: visibleRectangle; + addAll: (visibleShapes collect: [:s | | copy | + "it is necessary to create a copy of the current shape because we will modify its position" + copy := s copy. + copy matrix: s matrix copy. + copy translateBy: visibleRectangle floatCenter negated. + copy ]); + scaleBy: scale; + translateBy: chart spine encompassingRectangle floatCenter - visibleRectangle floatCenter; + yourself. + currentShapes add: visibleShapes +] + +{ #category : 'rendering' } +RSZoomTickDecoration >> renderPlotsAndTicksIn: canvas [ + currentShapes := OrderedCollection new. + self renderPlots. + self renderTicks. + canvas addAll: currentShapes +] + +{ #category : 'rendering' } +RSZoomTickDecoration >> renderTicks [ + self renderYAxis. + self renderXAxis +] + +{ #category : 'rendering' } +RSZoomTickDecoration >> renderXAxis [ + | horizontalTick tickShapes newPlot scale | + horizontalTick := chart horizontalTick. + horizontalTick ifNil: [ ^ self ]. + "should be a copy to not lose created shapes from original horizontal tick" + scale := horizontalTick xScale. + "scale inspect." + newPlot := chart copy. + newPlot + minValueX: (scale invert: visibleRectangle origin x); + maxValueX: (scale invert: visibleRectangle corner x). + + horizontalTick := horizontalTick copy. + horizontalTick xScale: nil. + horizontalTick chart: newPlot. + + tickShapes := RSGroup new. + + horizontalTick + beforeRenderingIn: newPlot; + renderIn: tickShapes. + + currentShapes addAll: tickShapes +] + +{ #category : 'rendering' } +RSZoomTickDecoration >> renderYAxis [ + | verticalTick tickShapes ymin ymax scale | + verticalTick := chart verticalTick. + verticalTick ifNil: [ ^ self ]. + + ymin := visibleRectangle origin y. + ymax := visibleRectangle corner y. + tickShapes := verticalTick createdShapes + select: [ :s |s position y between: ymin and: ymax ] + thenCollect: [:s | | copy | + copy := s copy. + s isLine ifFalse: [ copy matrix: s matrix copy ]. + copy ]. + scale := self boxScale. + tickShapes do: [ :each | each translateTo: each position x @ (scale invert: each position) y ]. + currentShapes addAll: tickShapes +] + +{ #category : 'events' } +RSZoomTickDecoration >> resetZoom: evt [ + | canvas | + canvas := evt canvas. + visibleRectangle := self chart spine encompassingRectangle. + currentShapes do: #remove. + currentShapes := self chartElements flatCollect: [ :element | element createdShapes ifNil: [#()] ]. + canvas addAll: currentShapes. + resetLabel pushFront. + evt signalUpdate +] + +{ #category : 'events' } +RSZoomTickDecoration >> updateSelection: evt [ + visibleRectangle := self computeVisibleRectangle: evt shape encompassingRectangle. + currentShapes do: #remove. + self renderPlotsAndTicksIn: evt canvas. + resetLabel pushFront +] diff --git a/src/Roassal-Chart/SequenceableCollection.extension.st b/src/Roassal-Chart/SequenceableCollection.extension.st index 9766fbb2..0a9fb4c8 100644 --- a/src/Roassal-Chart/SequenceableCollection.extension.st +++ b/src/Roassal-Chart/SequenceableCollection.extension.st @@ -1,8 +1,8 @@ -Extension { #name : 'SequenceableCollection' } - -{ #category : '*Roassal-Chart' } -SequenceableCollection >> rsHistogram [ - | plot | - plot := RSHistogramPlot new x: self. - ^ plot -] +Extension { #name : 'SequenceableCollection' } + +{ #category : '*Roassal-Chart' } +SequenceableCollection >> rsHistogram [ + | plot | + plot := RSHistogramPlot new x: self. + ^ plot +] diff --git a/src/Roassal-Chart/package.st b/src/Roassal-Chart/package.st index 2f196e11..7fd40f3d 100644 --- a/src/Roassal-Chart/package.st +++ b/src/Roassal-Chart/package.st @@ -1 +1 @@ -Package { #name : 'Roassal-Chart' } +Package { #name : 'Roassal-Chart' } From 66a0ec53e6a3efe9c312c21bf0963517dac04d05 Mon Sep 17 00:00:00 2001 From: Yann Le Goff Date: Wed, 20 Nov 2024 17:06:26 +0100 Subject: [PATCH 2/2] add round on label position for fixing floating operations --- src/Roassal-Chart/RSKiviat.class.st | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Roassal-Chart/RSKiviat.class.st b/src/Roassal-Chart/RSKiviat.class.st index 026215f1..b4c62cdc 100644 --- a/src/Roassal-Chart/RSKiviat.class.st +++ b/src/Roassal-Chart/RSKiviat.class.st @@ -202,22 +202,23 @@ RSKiviat >> defaultPalette [ { #category : 'utilities' } RSKiviat >> fixLabelPosition: label angle: angle [ - | positions gap | + | positions gap roundedUpTo | + roundedUpTo := 0.0001. label position: angle cos @ angle sin * radius. gap := self labelGapSize. positions := Dictionary newFromPairs: { - 0. + (0 roundUpTo: roundedUpTo). (0.5 @ 0). - Float halfPi. + (Float halfPi roundUpTo: roundedUpTo). (0 @ 0.5). - Float halfPi negated. + (Float halfPi negated roundUpTo: roundedUpTo). (0 @ -0.5). - Float pi. + (Float pi roundUpTo: roundedUpTo). (-0.5 @ 0). - (Float pi + Float halfPi). + (Float pi + Float halfPi roundUpTo: roundedUpTo). (0 @ 0.5) }. positions - at: angle + at: (angle roundUpTo: roundedUpTo) ifPresent: [ :fix | label translateBy: label extent * fix + (gap * fix sign) ] ifAbsent: [