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

Make line plot 0/0 proportions have hollow circle #1686

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

chowington
Copy link
Member

Fixes #1082

@chowington chowington requested a review from bobular March 9, 2023 22:36
Copy link
Member

@bobular bobular left a comment

Choose a reason for hiding this comment

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

I'm not sure it's worth trying to get rid of the tiny "london underground" horizontal lines at this point.

Approve of everything so far. But merging this would not fix everything in the rather large ticket, so I'm not approving. (I see it's still in Draft though)

otherArrayKeys.forEach(
// e.g. x, binLabel etc
(key) => {
// initialize empty array if needed
if (accum[key] == null) accum[key] = [];
if (get(accum, key) == null) set(accum, key, []);
Copy link
Member

Choose a reason for hiding this comment

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

Interested to know why the change here. Style or substance, or both?

Ah - I get it now. It's path-based access, e.g. x['a']['b'] is get(x, 'a.b') - needed for marker.symbol

el.binSampleSize[0].hasOwnProperty('numeratorN')
? el.binSampleSize.map((obj: any) =>
obj.numeratorN || obj.denominatorN ? 'circle' : 'circle-open'
)
Copy link
Member

Choose a reason for hiding this comment

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

Interestingly, the markers still look like they are connected with lines when they're close together.
Even if we set the data[].mode: 'markers' instead of lines+markers they look like little London Underground logos
image

The line is coming from the error bars, even though the values are null for the offending 0/0 points. Heres data[].mode: 'none'

image

It will be a pain to create a separate series with no error bars for the special zero points.

Here's what it looks like when switching off error bars via the UI (and putting lines+markers back on)

image

Copy link
Member

@bobular bobular left a comment

Choose a reason for hiding this comment

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

If we choose to merge (see parent ticket discussion), I approve this. Just one style/TS comment/suggestion.

@@ -2126,8 +2138,12 @@ function processInputData(
binSpec?: BinSpec,
binWidthSlider?: BinWidthSlider,
overlayVariable?: Variable,
colorPaletteOverride?: string[]
colorPaletteOverride?: string[],
dependentIsProportion?: boolean
Copy link
Member

Choose a reason for hiding this comment

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

An alternative to passing yet another argument might be to create at io-ts type for the numeratorN/denominatorN structure, so:

      binSampleSize: union([
        array(
          type({
            N: number,
          })
        ),
        array(
          type({
            numeratorN: number,
            denominatorN: number,
          })
        ),
      ]),

would become

export type ProportionSampleSizes = TypeOf<typeof ProportionSampleSizes>;
const ProportionSampleSizes = type({
            numeratorN: number,
            denominatorN: number,
          });

...

      binSampleSize: union([
        array(
          type({
            N: number,
          })
        ),
        array(ProportionSampleSizes),
      ]),

Then in the markerSymbol function you could replace the dependentIsProportion with something like

el.binSampleSize.every((bss) => ProportionSampleSizes.is(bss))

I don't know if that makes it any more (or less) readable... (and it probably doesn't fix all the TS issues)

It would be nice to avoid the any and the hasOwnProperty check on just the first array element. But I may just be introducing more TS spaghetti to compensate...

Copy link
Member Author

Choose a reason for hiding this comment

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

I might wrap back around to the numeratorN stuff, though there are more pressing TS issues, like the many anys, as you mention.

Copy link
Member

@bobular bobular Mar 21, 2023

Choose a reason for hiding this comment

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

I think we might need to explicitly "tag" the LineplotResponse series produced by processZeroOverZeroData with an "isZeroSeries" property?

Introspecting to figure out which symbol to use is tricky. I'm noticing a el.binSampleSize[0] is undefined error when the zeroSeries is empty, though that may be avoidable with a length check - I haven't tried that.

Here's how to reproduce:
LLINEUP -> x=observation date, y=Plasmodium by RDT, num=Yes, denom=all
(basically there are no 0/0s)

@bobular
Copy link
Member

bobular commented Mar 20, 2023

Had a quick look at the "splitting" code. Was wondering why the nullZeroHack function is still used. But as I say, it was a very quick look. Are you ready for a more detailed look at the new code?

@chowington
Copy link
Member Author

chowington commented Mar 20, 2023

Had a quick look at the "splitting" code. Was wondering why the nullZeroHack function is still used. But as I say, it was a very quick look. Are you ready for a more detailed look at the new code?

I plan on removing my added code in nullZeroHack that handles the marker.symbol array, but the original version of the function should stay, right?

A detailed look might be helpful if you have any ideas on how to fix the nasty TS errors that I've been sidestepping with these anys! I spent time trying to handle it more robustly, but it's a bit of a monster, at least to me.

Also, there's the one property facetVariableDetails that's giving me a headache because it's a constant-size tuple (a TS concept, whereas it's just another array in JS), meaning it shouldn't be iterated over as an array, but TS/JS doesn't seem to give any method for weeding out tuples from general arrays. So I'm just excluding it manually, which is annoying. Any thoughts?

@bobular
Copy link
Member

bobular commented Mar 21, 2023

I plan on removing my added code in nullZeroHack that handles the marker.symbol array, but the original version of the function should stay, right?

The nullZeroHack was originally designed to handle the 0/0 cases because their y values were null (though sadly my code comments don't say that in as many words). We can remove it if we're creating a new zeroOverZero series.

Copy link
Member

@bobular bobular left a comment

Choose a reason for hiding this comment

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

Also, there's the one property facetVariableDetails that's giving me a headache because it's a constant-size tuple (a TS concept, whereas it's just another array in JS), meaning it shouldn't be iterated over as an array, but TS/JS doesn't seem to give any method for weeding out tuples from general arrays. So I'm just excluding it manually, which is annoying. Any thoughts?

That's annoying.

The only thing I can think of is to, explicitly exclude tuples of size 1 and 2 something like this:

type Foo = {
  x : number[],
  a : [ number, number ] | [ number ]
};
type PickByType<T, Value> = {
  [P in keyof T as T[P] extends Value | undefined ? P : never]: T[P];
};
type OmitByType<T, Value> = {
  [P in keyof T as T[P] extends Value | undefined ? never : P]: T[P];
};

type Goo = OmitByType<PickByType<Foo, any[]>, [ any ] | [ any, any ]>>;

// Goo is { x: number[] }

It avoids having to name facetVariableDetails directly, but if we decide to allow 3 facet variables (we won't) it will need updating.

In the rest of the code, I don't see any major and potentially impactful uses of any, in terms of technical debt/maintenance. Let me know if I can look again at this though.

> = [];
const newZeroSeries: Array<LineplotResponse['lineplot']['data'][number]> = [];

const stopIndex = hasMissingData
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need to worry about the early stopping before the final element in the lineplot response array that may correspond to the "no data for overlay/facet".

In processInputData, the zeroSplitLineplotData that this function returns is passed through the (somewhat opaque) .some(do_stuff; return breakAfterThisSeries(index)) which will ignore the final element if the user hasn't asked to show the "missing data".

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.

Line plot tool - update UI for 0/0 proportions
2 participants