Skip to content
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

mrc-5442 Per-graph settings, with separate fit plot settings #210

Merged
merged 16 commits into from
Jul 26, 2024

Conversation

EmmaLRussell
Copy link
Contributor

@EmmaLRussell EmmaLRussell commented Jul 9, 2024

This branch implements per-graph settings (Y axis log scale and lock), so that the multiple graphs no longer share settings. Graph settings are now a property of each graph config, rather than directly the graphs module. Because the fit graph does not use the multiple graph config, it also has its own graph settings.

In the UI, graph settings are included in the graph config area, which has been slightly restyled - the 'Graph N' headings have been removed and replaced with a border around each config to separate them. The Fit Graph settings are visible on the options tab when Fit tab is open - in this case, the multiple graph configs are hidden too.

image

One other change is that the plots now take graphConfig as a prop rather than just the graph index - I was having a reactivity problem when graphs were deleted such that the last plot was still trying to read settings from the previous maximum indexed config in the store. Providing config as a prop solved this issue..

I haven't updated all the components to use setup script tag and defineProps, as I did in the last branch for RunPlot - didn't want to add too much additional noise. Could maybe do that update in a separate ticket,

@EmmaLRussell EmmaLRussell changed the title mrc-5484 Per-graph settings, with separate fit plot settings mrc-5442Per-graph settings, with separate fit plot settings Jul 16, 2024
@EmmaLRussell EmmaLRussell changed the title mrc-5442Per-graph settings, with separate fit plot settings mrc-5442 Per-graph settings, with separate fit plot settings Jul 16, 2024
@EmmaLRussell EmmaLRussell changed the base branch from mrc-5484 to mrc-5490 July 16, 2024 16:09
@EmmaLRussell EmmaLRussell marked this pull request as ready for review July 25, 2024 09:34
Copy link
Collaborator

@david-mears-2 david-mears-2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are quite nitty, sorry, I'm prepared for most to be ignored.

I couldn't work out within 1 minute what the 'lock y axis' feature was intended to do. From its name, I expected it to prevent the y-axis automatically adjusting to fit the lines in. But I couldn't make the y-axis automatically adjust, so I don't know what 'locking' is meant to do. I was using the http://localhost:3000/apps/day1/ example and dragging variables back and forth between graphs hoping to see some automatic re-scaling of the y-axis that I could turn off by clicking 'lock'.

};
}
});
</script>
Copy link
Collaborator

@david-mears-2 david-mears-2 Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that the GraphSettings component checks props.fitPlot in three places to do slightly different computed behavior. So an alternative refactor would be to define the fit plot graph settings as a component that inherits (extends) from the GraphSettings, and overwrites these three computeds. That idea doesn't provide much at the moment, probably, but I thought I'd mention it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, could do. I don't think one usage is any more fundamental than the other though. Inclined to leave it as is, slightly messy but simple.

graphIndex: {
type: Number,
required: false,
default: -1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the value in giving the graphIndex a default index of -1? If the prop isn't set, you might get multiple components claiming to have this index.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't provide the prop it should be because fitPlot is true. We could explicitly make this nullable and make null the default, but that's going to clutter up the code with null checks. Or we could squash both props into one and assume fitPlot if index is -1. But I think it's better to be explicit and it's probably ok to assume polite usage - but I'll add an explanatory comment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, I hadn't thought about the null checks.

@@ -6,6 +6,9 @@
:plot-data="allPlotData"
:redrawWatches="solution ? [solution] : []"
:linked-x-axis="linkedXAxis"
:fit-plot="false"
:graph-index="graphIndex"
:graph-config="graphConfig"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the alternative to prop-drilling the graphConfig of each graph would be to pass down only the graphIndex, and have the lowest-level component (WodinPlot) look up the config directly from store.state.graphs.config[props.graphIndex]. Again I don't particularly favour doing that -- we're only drilling through one intermediate component -- it's just an idea that occurs.

Copy link
Contributor Author

@EmmaLRussell EmmaLRussell Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did have that. but as noted in the description, this caused reactivity issues:

One other change is that the plots now take graphConfig as a prop rather than just the graph index - I was having a reactivity problem when graphs were deleted such that the last plot was still trying to read settings from the previous maximum indexed config in the store. Providing config as a prop solved this issue..

I'll actually put a comment in the code to this effect so we remember why we've done it this way.

@@ -29,6 +33,10 @@ export default defineComponent({
type: Number,
default: 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the default graphIndex for this component and for RunPlot, 0, is different from the default graphIndex of GraphSettings and WodinPlot (which is -1), and it's not obvious to me as an outsider to the codebase why these are different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes a sort of sense because RunPlot and RunStochasticPlot will always have fitPlot=false, so should have a valid index. But I think it would make even more sense to make this a required prop!

@@ -54,7 +54,8 @@ export default defineComponent({

const yAxisSettings = computed(() => {
const isNotTimePlot = plotSettings.value.plotType !== SensitivityPlotType.TimeAtExtreme;
const logScale = store.state.graphs.settings.logScaleYAxis && isNotTimePlot;

const logScale = store.state.graphs.config[0].settings.logScaleYAxis && isNotTimePlot;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a sensitivity summary plot is guaranteed to correspond to the first graph config?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not, perhaps the S. S. plot should be given a graphIndex to hold onto and use that to look up the right graphConfig.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does for now - we're going to implement multiple sensitivity plots in another ticket: https://mrc-ide.myjetbrains.com/youtrack/issue/mrc-5485/Include-all-Graphs-on-Sensitivity-tab

@@ -48,6 +51,7 @@ export default defineComponent({
const store = useStore();

const solutions = computed(() => store.state.sensitivity.result?.batch?.solutions || []);
const graphConfig = computed(() => store.state.graphs.config[0]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question - unclear to me why we know that the relevant graph config is the first one.
We should do something that ensures that the 0 of line 13 and the 0 of this line are always equal: e.g. move this computed into the child so that the graphIndex prop is used to look up the graphConfig, or set a graphIndex variable in this component that both line 13 and this line can reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, we'll sort this in mrc-5485, it can just look at the first config until then.

logScaleYAxis: false,
lockYAxis: false,
yAxisRange: [0, 0]
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For newcomers to the codebase, it would be extremely helpful to have commentary in this file that explains the difference between "graph settings", "graph config", and "graphs state" -- the names are roughly synonymous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent point, will add some comments

@david-mears-2
Copy link
Collaborator

Role-playing as a QA tester. Do we want to set limits on what people can do their scales?

Screenshot from 2024-07-25 14-34-33

Copy link
Contributor Author

@EmmaLRussell EmmaLRussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't work out within 1 minute what the 'lock y axis' feature was intended to do. From its name, I expected it to prevent the y-axis automatically adjusting to fit the lines in. But I couldn't make the y-axis automatically adjust, so I don't know what 'locking' is meant to do. I was using the http://localhost:3000/apps/day1/ example and dragging variables back and forth between graphs hoping to see some automatic re-scaling of the y-axis that I could turn off by clicking 'lock'.

This works for me:

  1. Add a second graph
  2. Drag "I" to the second graph - The graph auto-scales to I's maximum value, ~160K
  3. Check "lock y axis" for the second graph
  4. Drag "R" to the second graph. The y axis is locked and does not autoscale to R's maximum value (800K)
  5. Uncheck "lock y axis" for the second graph - the second graph autoscales to R's max value.

You can also play around with adjusting the axes by dragging on the graph area to select a portion of the graph. Again, you should find that the y axis of this selection if fixed if you subsequenly lock y axis.

graphIndex: {
type: Number,
required: false,
default: -1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't provide the prop it should be because fitPlot is true. We could explicitly make this nullable and make null the default, but that's going to clutter up the code with null checks. Or we could squash both props into one and assume fitPlot if index is -1. But I think it's better to be explicit and it's probably ok to assume polite usage - but I'll add an explanatory comment.

};
}
});
</script>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, could do. I don't think one usage is any more fundamental than the other though. Inclined to leave it as is, slightly messy but simple.

@@ -6,6 +6,9 @@
:plot-data="allPlotData"
:redrawWatches="solution ? [solution] : []"
:linked-x-axis="linkedXAxis"
:fit-plot="false"
:graph-index="graphIndex"
:graph-config="graphConfig"
Copy link
Contributor Author

@EmmaLRussell EmmaLRussell Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did have that. but as noted in the description, this caused reactivity issues:

One other change is that the plots now take graphConfig as a prop rather than just the graph index - I was having a reactivity problem when graphs were deleted such that the last plot was still trying to read settings from the previous maximum indexed config in the store. Providing config as a prop solved this issue..

I'll actually put a comment in the code to this effect so we remember why we've done it this way.

@@ -29,6 +33,10 @@ export default defineComponent({
type: Number,
default: 0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes a sort of sense because RunPlot and RunStochasticPlot will always have fitPlot=false, so should have a valid index. But I think it would make even more sense to make this a required prop!

@@ -54,7 +54,8 @@ export default defineComponent({

const yAxisSettings = computed(() => {
const isNotTimePlot = plotSettings.value.plotType !== SensitivityPlotType.TimeAtExtreme;
const logScale = store.state.graphs.settings.logScaleYAxis && isNotTimePlot;

const logScale = store.state.graphs.config[0].settings.logScaleYAxis && isNotTimePlot;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does for now - we're going to implement multiple sensitivity plots in another ticket: https://mrc-ide.myjetbrains.com/youtrack/issue/mrc-5485/Include-all-Graphs-on-Sensitivity-tab

@@ -48,6 +51,7 @@ export default defineComponent({
const store = useStore();

const solutions = computed(() => store.state.sensitivity.result?.batch?.solutions || []);
const graphConfig = computed(() => store.state.graphs.config[0]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, we'll sort this in mrc-5485, it can just look at the first config until then.

logScaleYAxis: false,
lockYAxis: false,
yAxisRange: [0, 0]
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent point, will add some comments

@EmmaLRussell
Copy link
Contributor Author

Role-playing as a QA tester. Do we want to set limits on what people can do their scales?

Screenshot from 2024-07-25 14-34-33

We've generally taken the approach of giving users the option to get themselves into trouble as a didactic opportunity!

@EmmaLRussell
Copy link
Contributor Author

As discussed yesterday, I've added a ticket for the behaviour where y axis range selection should be overriden by new variable selection on the graph: https://mrc-ide.myjetbrains.com/youtrack/issue/mrc-5560

Also as discussed, I think it's ok for the graphs to revert to 0 as minimum y value as we don't expect any negative values.

@EmmaLRussell EmmaLRussell merged commit 4662e4f into mrc-5490 Jul 26, 2024
2 checks passed
Copy link
Collaborator

@M-Kusumgar M-Kusumgar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive left an optional comment, it would be work but i think it would be cleaner too

Comment on lines +68 to +73
// We could look up graphConfig from graphIndex - however this causes a specific reactivity bug
// when the last plot is deleted and the plot tries to observe the old maximum index before it is updated.
graphConfig: {
type: Object as PropType<GraphConfig | null>,
required: true
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i think using the index as a unique identifier is the problem here, i think the only piece of information all these components need to be passed down is the GraphConfig.id and use .finds or .findIndexs to locate where we are, this removes the need for graphIndex and graphConfig to be passed down and any component can just get that from the store, this will deal with the deleted graph hopefully as you can put that logic after the find. Also the store mutation will be alright too as you can just pass in the id instead of the index into the mutation

on this point i think the logic for updating graph settings and seeing whether we are at the fit plot or not can be extracted out into a composable that gets whether we are fit plot or not straight from the store based on what tab is selected, i think it would be cleaner

thats probably a lot of work so see if you feel up to these changes or not, happy for this to be ignored

Copy link
Contributor Author

@EmmaLRussell EmmaLRussell Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I definitely had issues with just using index, and tbh I thought needing to use findIndex felt dirtier than passing in graphIndex which we already know as well as the config - which just goes to show that cleanliness can be subjective!

I agree on the composable though, good to keep this dubious logic in one place either way - will make a ticket.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants