[DTOSS-12327] - Render image markers on breast features diagram#1100
[DTOSS-12327] - Render image markers on breast features diagram#1100colinrotherham merged 16 commits intomainfrom
Conversation
03160d6 to
8d89d00
Compare
|
The review app at this URL has been deleted: |
| this.$image.addEventListener('mousemove', this.onMouseMove.bind(this)) | ||
| this.$image.addEventListener('mouseleave', this.onMouseLeave.bind(this)) | ||
| this.$image.addEventListener('click', this.onClick.bind(this)) | ||
| if (!readOnly) { |
There was a problem hiding this comment.
do we have a unit test for this case?
| const point = this.$imageMap.createPoint(x, y, id) | ||
| const region = this.$imageMap.createRegion($path, point) | ||
|
|
||
| this.setMarker(region, index) |
There was a problem hiding this comment.
Is features still the right name for this, now that we have markers as well? They seem like different representations of the same thing.
Also, having all these side effects inside a getter feels like a code smell to me. Perhaps we could turn features into an attribute and then onUpdate calls a method to recompute it?
There was a problem hiding this comment.
Agree entirely
I've pushed up a change to use event listeners
- Breast diagram listens via
addEventListener() - Breast diagram handles
this.onClick()when image map dispatches click - Breast diagram calls
this.add()to modifyvalues
I've temporarily added 4124fa2 so you can see this.remove() in action too
| this.$imageMap.setState('active', feature.region, null) | ||
| const { $imageMap, features, markers } = this | ||
|
|
||
| // Remove excess markers |
There was a problem hiding this comment.
I'm finding this code quite difficult to follow.
From the name, I expected render() to be called any time something changes, but actually it's just called one time as part of initialising the diagram.
It looks almost as if markers should be empty at this point, but it's populated as a side effect of accessing this.features. Then we are removing some markers from $root but it's not clear why we would have extra markers at this point.
I think this complexity comes from having to keep in sync multiple different things, and having to do that throughout the whole class. To reason about the correctness of the code you have to consider:
- the values array
- the features array
- the image map's state (a region is active or not active)
- the markers array
- whether the marker is added to $root or not
I don't know if there's a way we could either reduce the amount of state, or encapsulate the state changes in add/update/remove methods so that these things all change at the same time?
There was a problem hiding this comment.
Love this feedback, you're right
My latest push makes sure:
- Image map only needs to worry about regions (SVG paths)
- Feature diagram only needs to worry about markers
Regarding this bit:
// Remove excess markers
for (const marker of markers.splice(values.length)) {
marker.$root.remove()
}I've used a similar technique to particle systems in games, to keep memory usage down
Rather than create, append and position every marker on every render, I've reused them
Similarly when features are removed from the diagram values.length is reduced, so we .remove() unused markers over that length rather than clearing and creating them all again
| * @param {Partial<ImageMarkerConfig>} [config] - Image marker config | ||
| */ | ||
| constructor($root, config = {}) { | ||
| $root = $root ?? document.createElement(config.href ? 'a' : 'div') |
There was a problem hiding this comment.
Maybe worth adding a comment to explain that div is for the readonly version. Sidenote: is <div> ok to use here or should it be <g> or something specific to svg?
There was a problem hiding this comment.
Yeah good point
Are we still happy with the proposal to use <div> for readonly and <a><button> otherwise?
| * @param {DOMPoint} point - SVG point at pointer coordinates | ||
| */ | ||
| set point(point) { | ||
| const offsetX = Math.min(Math.max(point.x, 20), this.config.width - 20) |
There was a problem hiding this comment.
what's the significance of 20 here? We're bounding the point to within 20px of the border?
There was a problem hiding this comment.
20px is the width of the gutter
I've split out the code and added some comments in 5fb6b76
i.e. All clicks within the gutter are rounded 20px away from the image map edges
It makes sure we don't lose the makers near the edges:
| cursor: crosshair; | ||
| } | ||
|
|
||
| input[readonly] ~ .app-breast-diagram__image-map .app-breast-diagram__svg { |
There was a problem hiding this comment.
Possibly worth creating a specific class like app-breast-diagram__image-map--readonly rather than depending strictly on the html structure for this.
8d89d00 to
4124fa2
Compare
MatMoore
left a comment
There was a problem hiding this comment.
Sorry for the slow reply. I think it looks a lot clearer now post-refactor. Lets merge (once the temporary commit is removed) 🚢
I guess next part is the selection of feature type and the legend on the right?
In the meantime I'll finish up #1196
4124fa2 to
438cc69
Compare
438cc69 to
f3a0be0
Compare
f3a0be0 to
62bec64
Compare
| "method": "post", | ||
| "data-module": "app-breast-diagram", | ||
| "data-debug": { | ||
| "value": "true" if "debug" in request.GET else false, |
There was a problem hiding this comment.
you've got a mix of string and bool here. If the intention is for value to be boolean you can simplify the expression to "debug" in request.GET
There was a problem hiding this comment.
Yeah it's intentional
We need an actual attribute value for the component config to pick up
Whentrueit appendsdata-debug- When
"true"it appendsdata-debug="true" - When
falseit skips the attribute
There was a problem hiding this comment.
Ah got it, all good then 👍🏻
62bec64 to
3c8965f
Compare
|
Thanks @MatMoore You'll see these two variables in mammograms/medical_information/breast_features/form.jinja {% set diagram_debug = "debug" in request.GET %}
{% set diagram_read_only = false %}They become Ready for you to wire up in #1109 |
dd6dbef to
be7ebb1
Compare
|
|
|
||
| if (!markers[index]) { | ||
| markers[index] = new ImageMarker(null, { | ||
| href: $input.readOnly ? undefined : `#marker-${index + 1}`, |
There was a problem hiding this comment.
We can address where the marker links to (or if we use a button) when adding the form fields



Description
This PR uses the persisted region X/Y data to render image markers
Breast.features.diagram.mp4
Jira link
Review notes
In draft whilst I sort out some failing tests
Review checklist