1

I'm trying to use Roassal3, in Pharo 10, to visualise multiple series of data. I've managed to draw the chart fine but want to label the lines. I tried using RSLegend to display the text with corresponding colour boxes (corresponding to the line colours) but they come out far too large and end up shrinking the graph to fit the canvas. If I could manipulate the legends to display differently, that would help, but I recall seeing an example, using an earlier version of Roassal, which displays a label for each line, at the end of the line. This would be ideal, so I tried adding an RSLabel as the markerEnd for the plot. This worked except that the labels all came out at various angles (presumably the angle of the plot lines, at the end). How can I get the end marker labels to display horizontally? Documentation on Roassal3 is still a work in progress, so I can't find any examples.

I've updated the sample code to show what I've tried with end markers, legends and decorators (none are ideal but I may be able to work with decorators):

| chart plot dates values firstDate labels legend offset plotLabel renderedLabel canvasCopy |
chart := RSChart new.
canvasCopy := chart canvas copy.
dates := (20 to: 1 by: -1) collect: [ :i | Date today subtractDays: i ]. 
firstDate := dates first.
offset := 20 @ -50.
values := #(
    #(4.29 4.01 3.82 3.91 4.01 3.73 4.47 4.28 4.18 4.00 3.72 4.27 3.99 4.91 5.09 4.91 5.09 4.91 4.44 4.91)
    #(2.0 1.98 1.98 1.98 1.99 1.96 2.07 1.96 1.90 1.95 1.98 2.04 2.12 2.12 2.21 2.27 2.27 2.10 2.19 1.95)
    ).
labels := #('series 1' 'series 2').
values with: labels do: [ :series :label | 
   plot := RSLinePlot new markerEnd: (RSLabel new text: label).
   plot 
       x: (dates collect: [ :date | date julianDayNumber - firstDate julianDayNumber ])
       y: series.
   chart addPlot: plot.
    plotLabel := RSYLabelDecoration new right; 
        title: '~' , label;
        fontSize: 12;
        rotationAngle: 90;
        color: (chart colorFor: plot);
        offset: offset;
        yourself.
    chart addDecoration: plotLabel.
    renderedLabel := (plotLabel copy renderIn: canvasCopy) label.
    offset := (0 - renderedLabel textWidth) @ (offset y + renderedLabel textHeight + 4). 
 ].
canvasCopy shapes copy do: [ :shape | canvasCopy removeShape: shape ].
chart addDecoration: (RSHorizontalTick new labelConversion: [ :value | 
    Date julianDayNumber: firstDate julianDayNumber + value ]; useDiagonalLabel; yourself).
chart addDecoration: RSVerticalTick new.
chart ylabel: 'The values'.
chart build.
legend := RSLegend new.
legend container: chart canvas.
labels with: chart plots do: [ :c : p |
    legend text: c withBoxColor: (chart colorFor: p) ].
legend layout horizontalCompactTree .
legend build.
^chart canvas open
Tony Weddle
  • 2,081
  • 1
  • 11
  • 15
  • **Just an idea, not the actual solution:** add small horizontal segments to the data so that end marker labels appear horizontally aligned. – Leandro Caniglia Jun 08 '22 at 13:32
  • Thanks for the idea, which I played with. However, it seems the end markers are drawn at right angles to the line direction, so I ended up with those labels being drawn vertically. The lines would have to be going vertically upwards, at the end, for the labels to be drawn horizontally, and that is impossible for a line plot. – Tony Weddle Jun 08 '22 at 23:16
  • Then, **in case there is no simpler solution** offered by the software, I don't see any other option than finding the place where those labels are drawn and add the feature you are looking for. – Leandro Caniglia Jun 09 '22 at 01:08
  • Yes, I've been looking but I have no experience with traits, in Pharo, which makes tracing through difficult, and the implementation of Roassal3 seems very complex. I just can't find where the markerEnd is drawn onto the polyline and what options their may be for altering how that's done. I think, for now, I'll see if I can get the legend working, and perhaps drawn on the right side of the chart, instead of below, or maybe better stacked at the bottom. – Tony Weddle Jun 09 '22 at 02:41
  • 1
    Then my advice is to contact Alexandre Bergel at the University of Chile. I'm sure he will be glad to help you. – Leandro Caniglia Jun 09 '22 at 09:31
  • Once you have the solution, don't forget to post it here as an answer to your question! – Leandro Caniglia Jun 09 '22 at 11:56

2 Answers2

0

Following a tip from Alexandre Bergel, I now have a reasonable way to draw labels at the end of plot lines. For interest, I've included, in the playground code below, three ways to identify the plot lines, using the end of plot labels, using right side decorations and using a more standard legend. It's messy, in places but can be cleaned up a lot by moving code to more appropriate objects.

| chart plot dates values firstDate labels legend offset plotLabel renderedLabel canvasCopy maxOffset |
chart := RSChart new.
canvasCopy := RSCanvas new.
dates := (10 to: 1 by: -1) collect: [ :i | Date today subtractDays: i ]. 
firstDate := dates first.
offset := 20 @ -50.
maxOffset := 0.
values := #(
    #(3.72 4.27 3.99 4.91 5.09 4.91 5.09 4.91 4.44 4.91)
    #(4.29 4.01 3.82 3.91 4.01 3.73 4.47 4.28 4.18 4.00)
    #(1.98 2.04 2.12 2.12 2.21 2.27 2.27 2.10 2.19 1.95)
    #(2.5 2.3 2.7 2.73 2.15 2.6 2.63 2.57 2.4 2.8)
    #(2.0 1.98 1.98 1.98 1.99 1.96 2.07 1.96 1.90 1.95)
    ).
labels := #('first series' 'second series' 'series number 4' 'the third series' 'a fifth series').
values with: labels do: [ :series :label | 
   plot := RSLinePlot new.
   plot 
       x: (dates collect: [ :date | date julianDayNumber - firstDate julianDayNumber ])
       y: series.
   chart addPlot: plot.
    "If adding a legend to the right, RSYLabel decoration is needed but with modified offsets to get them to lay out vertically."
    plotLabel := RSYLabelDecoration new right; 
        title: '~' , label;
        fontSize: 12;
        rotationAngle: 90;
        color: (chart colorFor: plot);
        offset: offset;
        yourself.
    chart addDecoration: plotLabel.
    renderedLabel := (plotLabel renderIn: canvasCopy) label.
    maxOffset := maxOffset max: renderedLabel textWidth.
    offset := (0 - maxOffset) @ (offset y + renderedLabel textHeight + 4).
 ].
canvasCopy := nil.
chart addDecoration: (RSHorizontalTick new labelConversion: [ :value | 
    Date julianDayNumber: firstDate julianDayNumber + value ]; useDiagonalLabel; yourself).
chart addDecoration: RSVerticalTick new.
"When adding labels at the end of the plot lines, the right hand line of the chart box will overlay the labels so alter the
spine to a polygon which only draws the x and y axes. "
spine := chart decorations
        detect: [ :d | d class == RSChartSpineDecoration ].
spine shape: (RSPolygon new points: {(0 @ 0). (0 @ 0). (0 @ (chart extent y)). (chart extent x @ (chart extent y)). (chart extent x @ (chart extent y)). (0 @ (chart extent y)) }; noPaint; withBorder; yourself).
chart ylabel: 'The values'.
chart build.
"To use standard legend under the chart"
legend := RSLegend new defaultLabel: (RSLabel new fontSize: 8; yourself).
legend container: chart canvas.
labels with: chart plots do: [ :c : p |
    legend text: c withShape: (RSPolygon new points: { 0 @ -2. 10 @ -2. 10 @ 2. 0 @ 2 }; color: (chart colorFor: p))
    ].
legend layout grid.
legend build.

rsLabels := Dictionary new: chart plots size.
chart plots with: labels do: [ :plot :label | rsLabels at: plot put: ((RSLabel text: label) fontSize: 5) ].
lastys := chart plots collect: [ :pl | 
    Association key: pl value: (pl yScale scale: pl yValues last) ].
lastys sort: [ :el1 :el2 | 
    el1 value = el2 value 
        "If the last y coordinate is the same for both plots, order by the second last y point (reverse ordering)"
        ifTrue: [ (el1 key yValues at: el1 key yValues size - 1) > (el2 key yValues at: el2 key yValues size - 1) ] 
        ifFalse: [ el1 value < el2 value ] ].
lastys withIndexDo: [ :lasty :index |
        numPlots := lastys size.
        plot := lasty key.
        label := rsLabels at: plot.
        label color: plot color.
        chart canvas add: label.
        yPoint := lasty value.
        textHeight := label textHeight.
        index < numPlots ifTrue: [ 
            "Reset the y point to be about textHeight away from the next one, if they are close"
            (diff := (yPoint - (lastys at: index + 1) value) abs) < textHeight 
                ifTrue: [ yPoint := yPoint - (textHeight / 2) + (diff / 2) ] ].
        index > 1 ifTrue: [ 
            "Reset the y point to be about textHeight away from the last one, if they are close"
            (diff := ((lastys at: index - 1) value - yPoint ) abs) < textHeight 
                ifTrue: [ yPoint := yPoint + (textHeight / 2) - (diff / 2) ] ].
        label translateTo: ((plot xScale scale: plot xValues last) + (label textWidth / 2) + 1) @ yPoint.
    ].
^chart canvas
Tony Weddle
  • 2,081
  • 1
  • 11
  • 15
0

I have created an issue in roassal3 repository. The tip from Alexandre Bergel its good but I think and It should be simpler.

For example like this


dates := (10 to: 1 by: -1) collect: [ :i | Date today subtractDays: i ]. 
values := #(
    #(3.72 4.27 3.99 4.91 5.09 4.91 5.09 4.91 4.44 4.91)
    #(4.29 4.01 3.82 3.91 4.01 3.73 4.47 4.28 4.18 4.00)
    #(1.98 2.04 2.12 2.12 2.21 2.27 2.27 2.10 2.19 1.95)
    #(2.5 2.3 2.7 2.73 2.15 2.6 2.63 2.57 2.4 2.8)
    #(2.0 1.98 1.98 1.98 1.99 1.96 2.07 1.96 1.90 1.95)
    ).

labels := #('first series' 'second series' 'series number 4' 'the third series' 'a fifth series').
chart := (values with: labels collect: [ :series :label | 
   plot := RSLinePlot new.
    plot label: '~' , label.
   plot 
       x: dates
       y: series.
   
 ]) asCombinePlot.
chart withLineSpineAxis. 
chart ylabel: 'The values'.
chart legend layout grid.
chart

user1351870
  • 51
  • 1
  • 5