-
-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tick Marks layout #8
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the code needs a bit of cleanup.
I also think having all these code within Plot will soon become unmanagable, but that is a refactoring that can be done later.
@@ -26,14 +26,16 @@ import doodle.syntax.all.* | |||
*/ | |||
final case class Plot[ | |||
A, | |||
Alg <: Layout & Shape | |||
Alg <: Layout & Shape & Style & Text & doodle.algebra.Transform & Path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is incorrect. This is a constraint on layers
, and this seems an overly restrictive to require that they implement these algebras. (I'm not sure why the original constraint was Layout & Shape
. I don't think it needs to be anything other than doodle.algebra.Algebra
.) This constraint should be on draw
, I think.
} | ||
|
||
def draw(width: Int, height: Int): Picture[Alg, Unit] = { | ||
val allData = layers.flatMap(_.data.foldLeft(List.empty[A])(_ :+ _)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only to calculate the enclosing bounding box, right? It's quite inefficient to do all this copying.
I think this will do:
layers.foldLeft(BoundingBox.empty){ (accum, layer) =>
accum.on(layer.data.boundingBox)
}
xTicksSequence, | ||
yTicksSequence | ||
) | ||
return withTitles(plotWithTicksAndAxesAndGrid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use return
. Rewrite to an expression.
* coordinate of a tick - to place the tick on a graph. The second one is the | ||
* original coordinate - to give the tick a label with its coordinate. | ||
*/ | ||
private def xTicksToSequence( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too much repetition in this code. There shouldn't be repeated code for the x- and y-axis. You can abstract over x- and y-coordinates using a function. E.g.
val asX: Double => Point = x => Point(x, 0)
val asY: Double => Point = y => Point(0, y)
plot | ||
.on( | ||
OpenPath.empty | ||
.moveTo(tick._1.x, yTicksMapped.min - 10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit not-fan of using these methods on tuple. I'd rather destructure with an irrefutable match like so:
val (screen, _) = tick
.toList | ||
} | ||
|
||
/** Converts `Ticks` to a list of tuples. The first element is the mapped |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to be clearer on what these coordinates mean. I think of screen vs data coordinates.
Screen coordinates are the coordinates of the graph rendered on the screen. Data coordinates are the values in the data.
Consider using opaque types to distinguish these at the type level, as I think it will be very easy to get them confused in code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more changes here, I think.
|
||
def draw(width: Int, height: Int): PlotPicture = { | ||
val dataBoundingBox = layers.foldLeft(BoundingBox.empty) { (bb, layer) => | ||
bb.on(layer.data.boundingBox(layers.head.toPoint)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
layers.head.toPoint
is wrong. Different layers could have different toPoint
functions. This only compiles due to the overly restrictive type of layers (should probably be Layer[?, Alg]
not Layer[A, Alg]
)
bb.on(layer.data.boundingBox(layers.head.toPoint)) | |
bb.on(layer.data.boundingBox(layer.toPoint)) |
It might be worth adding a boundingBox
method to Layer
.
|
||
import doodle.core.Point | ||
|
||
object Coordinate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wrapper object is not needed, if I'm correctly reading the documentation on opaque types.
@@ -20,20 +20,24 @@ import chartreuse.layout.ScatterPlot | |||
import doodle.algebra.* | |||
import doodle.core.* | |||
import doodle.syntax.all.* | |||
import chartreuse.Coordinate._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import chartreuse.Coordinate._ | |
import chartreuse.Coordinate.* |
_
is Scala 2 syntax and will be removed in the future.
.path | ||
) | ||
.on( | ||
text(((dataCoordinate.x * 1000).round / 1000.0).toString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might look at https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/text/NumberFormat.html instead of this rather ad-hoc method.
plot | ||
.on( | ||
OpenPath.empty | ||
.moveTo(xTicksMapped.min - 10, screenCoordinate.y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ad-hoc magic numbers need explanation. I assume this is a temporary decision that will be replaced by style options later.
) | ||
} | ||
|
||
private def withYTicks( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this method only returned the PlotPicture
that represents the ticks, instead of adding it to the plot, there would be more possibilities for composition in the use above.
plot: PlotPicture, | ||
yTicksMapped: Ticks | ||
): PlotPicture = { | ||
xTicksSequence |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repetition of this code and withYTicks
is somewhat annoying. It would be nice to remove it.
val plotWithTicksAndAxes = | ||
withAxes(plotWithXAndYTicks, xTicksMapped, yTicksMapped) | ||
|
||
if (grid) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If all the with
methods (withGrid,
withXTicks`) just returned the picture instead of adding it to the plot, you could write something like
xTicks
.on(yTicks)
.on(axes)
.on(allLayers)
.on(if grid then theGrid else empty[Shape])
I.e. emphasize composition.
Same issue is described here: We probably need to find a replacement for |
I think it is not necessary to add the whole |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Thanks! |
Worked on the issue:
The following PR contains the following features:
This is what a plot looks like now:
The same plot, but with a grid:
Next steps:
Landmark
to fix this