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

Component | Treemap: New component #2

Open
wants to merge 18 commits into
base: exaforce
Choose a base branch
from
Open

Conversation

rokotyan
Copy link

@rokotyan rokotyan commented Nov 26, 2024

Closes f5#516

@curran curran changed the title Component | Tremap: New component Component | Treemap: New component Dec 30, 2024
@curran curran mentioned this pull request Dec 30, 2024
@curran
Copy link

curran commented Dec 31, 2024

Current status:

  • Scaffolded out the entire Treemap API
  • Got a minimal Treemap to show up!
image
  • Working through the coloring next

@curran
Copy link

curran commented Jan 2, 2025

Current status:

image
  • Unlocked color, first pass
  • Added padding configuration

Next steps:

  • Make the colors match those of this example:
packages/dev/src/examples/misc/nested-donut/nested-donut-layers/index.tsx
image
  • Make the text labels work
  • Start on a new dev example with search & highlighting

@curran
Copy link

curran commented Jan 2, 2025

Current status:

image
  • Got the colors to an OK point, based on the coloring ideas from nested donut
  • Ran into an issue where opacity was being used in conjunction with CSS variables
  • To accommodate that, I'm having the component render two rectangles for each tile, one background rect (white) and one foreground rect (using CSS var + opacity)
  • Some of the color ideas from nested donut don't really work with the treemap
  • Having the siblings be different opacities works well in nested donut because it adds contrast between adjacent segments
  • Having the siblings be different opacities IMO does not add value in the treemap context, since the adjacent nodes in the tree are not adjacent visually
  • I propose to just increment the opacity correlating with depth instead, to add contrast between nested rather than adjacent nodes

Next steps:

  • Try incrementing opacity based on depth not adjacent nodes
  • Get the labels to show up
  • Start on a new dev example with search & highlighting

@curran
Copy link

curran commented Jan 3, 2025

Current status:

image
  • Opacity is now determined based on tree depth, not sibling index

Copy link
Author

@rokotyan rokotyan left a comment

Choose a reason for hiding this comment

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

Great progress! Lets some small, mainly code style related comments to the code.

const nestedData = group(data.keys(), ...layerAccessors as [(d: number) => string])

const rootNode = config.value !== undefined
? hierarchy(nestedData).sum(index => typeof index === 'number' && getNumber(data[index], config.value, index))
Copy link
Author

Choose a reason for hiding this comment

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

I'm curious, what will happen when typeof index is not a number?

Also you can use the isNumber function from utils here.

Copy link

Choose a reason for hiding this comment

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

TBH I don't fully grok this section of the code. It's mainly copied over from the packages/ts/src/components/nested-donut/index.ts component. I think index is always a number, but TypeScript doesn't know that, so this check is probably just to make the TypeScript linting happy.

Anyway, I'm debating how much of this to rip out, and how much to keep. Some of it is definitely useful to support the layers config idea. Ideally we could refactor the logic that's useful for both components into a single definition. Maybe some of the _getHierarchyData implementation from the nested donut could be moved into a shared utility.

Would it be fine to introduce such a refactoring in this PR? Or maybe it would be best to have duplication for the first version, then refactor later? Not sure.

In any case, if we clean this up here we can also clean it up in nested donut.

Copy link

Choose a reason for hiding this comment

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

I still need to circle back and test this code path. I'm thinking to add a new example that tests this path, compare that to what nested donut does, and truly get to the bottom of what this is supposed to do, and why that typeof check is even there. I suspect is has to do with using the output of group as the input to hierarchy, in which case the type of data is different for internal nodes (a 2-element array) and leaf nodes (the integer index - which I think this is checking for).

packages/ts/src/components/treemap/config.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/config.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/config.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/config.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/index.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/index.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/index.ts Outdated Show resolved Hide resolved
packages/ts/src/components/treemap/index.ts Show resolved Hide resolved

export interface TreemapNode<Datum> extends HierarchyRectangularNode<TreemapDatum<Datum>> {
_id: string;
_state?: {
Copy link
Author

Choose a reason for hiding this comment

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

I know NestedDonut has a similar type, but do we really need this _state as a dedicated nested object?

I feel like we can flatten it and that will make the code look cleaner (by removing _state?. fragments).

Copy link

Choose a reason for hiding this comment

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

I agree. I was also skeptical about pulling in this logic from the nested donut. Will try to get rid of this.

Copy link

Choose a reason for hiding this comment

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

It's tricky to remove this, because it helps with propagating the colors from the parents to the children.

I tried computing the color at render time, but it's more complex since we'd need to traverse up the tree for each node to figure out what color it should be, based on its parents.

I say let's leave this logic here, as it's in a way cleaner to traverse the whole hierarchy just once to compute the colors in a dedicated step. Perhaps later we can clean it up in both the treemap and nested donut at the same time, if there's a cleaner way.

@rokotyan
Copy link
Author

rokotyan commented Jan 6, 2025

@curran Also I think we should use Unovis text utils here to handle labels. Check out the Annotations component for a usage example:

const content = typeof annotation.content === 'string' ? { ...UNOVIS_TEXT_DEFAULT, text: annotation.content } : annotation.content
const x = parseUnit(annotation.x, this._width)
const y = parseUnit(annotation.y, this._height)
const width = parseUnit(annotation.width, this._width)
const height = parseUnit(annotation.height, this._height)
const options = { ...annotation, x, y, width, height }
const contentGroupElement = select(elements[i]).select<SVGGElement>(`.${s.annotationContent}`)
renderTextIntoFrame(contentGroupElement.node(), content, options)

@rokotyan
Copy link
Author

rokotyan commented Jan 7, 2025

The initial transition looks a bit weird to me. I think it would be better if the rectangles were already placed where they should be, and just faded out.

Screen.Recording.2025-01-06.at.4.08.14.PM.mp4

@curran
Copy link

curran commented Jan 7, 2025

Thanks so much for the detailed review! I'll go through each point and make sure to address them all.

I have not been paying attention to the transitions at all, thanks for calling that out!

I'll plan to work through the development like this:

  • Get labels working
  • Address small feedback points
  • Get transitions working well
  • Label internal nodes

checkpoint for potential PR merge

  • Use Unovis text utils to handle labels
  • Add the feature of highlighting specific nodes
  • Build out a dev example illustrating the highlight feature, perhaps simulating search
  • Consider adding text coloring logic for better contrast on dark tiles

@curran
Copy link

curran commented Jan 9, 2025

Current status:

image

@curran
Copy link

curran commented Jan 10, 2025

Directions moving forward

  • Investigate the possibility of having a single rect per tile, by getting the hex value from the CSS variable via getComputedStyle - reference in
    const hex = color(isStringCSSVariable(backgroundColor) ? getCSSVariableValue(backgroundColor, group.node()) : backgroundColor)?.hex()
  • Text should fade in
  • Use Unovis text utils
  • Use a more complex sample dataset
  • Make a new dev example with basic search, with some styling applied to the tiles that "highlight" the matching data, first pass by changing the color accessor such that the highlighted ones are "normal" and the non-highlighted ones are "grayed out"

@curran
Copy link

curran commented Jan 10, 2025

Current status:

searchDemo.mov
  • Basic search is working
  • This first version visualizes a subset of the data, rather than highlighting it within the context of all the data. This might be better? Maybe this is what we want as the final search behavior? Not sure...
  • Will work on the highlighting version next

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.

2 participants