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

Selection.one #294

Closed
curran opened this issue Oct 26, 2021 · 12 comments
Closed

Selection.one #294

curran opened this issue Oct 26, 2021 · 12 comments

Comments

@curran
Copy link
Contributor

curran commented Oct 26, 2021

Managing a single element is a common scenario. For example, a group element that contains an axis or legend. It might be a nice idea to consider introducing a utility that makes it simpler to manage a single element.

The most common pattern I find myself using is something like this:

const xAxisG = g.selectAll('.x-axis').data([null]).join('g').attr('class, 'x-axis');

Something similar can be seen in d3-axis for managing the domain path.

Wouldn't it be nicer to have a utility that does this:

const xAxisG = d3.one(g, 'g', 'x-axis');

?

Here's a strawman implementation:

const one = (selection, tagName, className) =>
  selection.selectAll('.' + className).data([null]).join(tagName).attr('class', className)
@curran
Copy link
Contributor Author

curran commented Oct 30, 2021

An iteration:

  • Moves tagName to be the last argument and defaults it to 'g', which is the most common use case.
  • Renames tagName to name for consistency with other methods such as selection.create.
    // Manages a single element.
    const one = (selection, className, name = 'g') =>
      selection
        .selectAll(name + '.' + className)
        .data([null])
        .join(name)
        .attr('class', className);

Example snippet before:

      svg
        .selectAll('.y-axis')
        .data([null])
        .join('g')
        .attr('class', 'y-axis')
        .attr('transform', `translate(${margin.left}, 0)`)
        .call(axisLeft(yScale));

After:

      one(svg, 'y-axis')
        .attr('transform', `translate(${margin.left}, 0)`)
        .call(axisLeft(yScale));

@curran
Copy link
Contributor Author

curran commented Feb 21, 2022

It could even parse the tag name and class from a selector string:

const one = (selection, selector) => {
  const [name, className] = selector.split('.');
  return selection
    .selectAll(name + '.' + className)
    .data([null])
    .join(name)
    .attr('class', className);
}

I like this because when you read its invocation, the tag name is clear, and there is no "magic" (no default "g" value).

one(svg, 'g.y-axis')
  .attr('transform', `translate(${margin.left}, 0)`)
  .call(axisLeft(yScale));

@curran curran mentioned this issue Feb 21, 2022
Closed
@curran
Copy link
Contributor Author

curran commented Mar 4, 2022

Would be super useful right now.

I have this in some code currently:

svg.append("g").call(xAxis);

and I need to change it to this:

svg
  .selectAll('.x-axis')
  .data([null])
  .join('g')
  .attr('class', 'x-axis')
  .call(xAxis)

but I wish I could change it to this instead:

one(svg, 'g.x-axis').call(xAxis);

Would be super nice!

@curran
Copy link
Contributor Author

curran commented Mar 6, 2022

Another use case - setting up the SVG container:

Before:

const svg = select('body')
  .selectAll('.viz')
  .data([null])
  .join('svg')
  .attr('class', 'viz')
  .attr('width', width)
  .attr('height', height);

After:

const svg = one(select('body'), 'svg.viz')
  .attr('width', width)
  .attr('height', height);

@curran
Copy link
Contributor Author

curran commented Mar 7, 2022

Closing as the change is not welcome #300 (comment)

@curran curran closed this as completed Mar 7, 2022
@curran
Copy link
Contributor Author

curran commented Mar 7, 2022

Actually, wait a minute - we can avoid a string-based DSL and still implement the module.

Based on this feedback from #300 (comment):

I don’t want to introduce (and design and maintain) a new string-based DSL.

the proposal can be modified back to its original form:

one(g, 'g', 'x-axis');

@curran curran reopened this Mar 7, 2022
@curran
Copy link
Contributor Author

curran commented Mar 10, 2022

The class name argument could be optional. For example:

one(svg, 'g');

Example from #301

@vijithassar
Copy link

maybe d3.single() for the method name?

@curran
Copy link
Contributor Author

curran commented Jul 29, 2022

Latest implementation for reference:

const one = (selection, name, className) =>
  selection
    .selectAll(name + '.' + className)
    .data([null])
    .join(name)
    .attr('class', className);

I find myself copying this into codebases often, so thought I'd post it here for easy reference.

@curran
Copy link
Contributor Author

curran commented Sep 19, 2022

An interesting alternative solution https://the-politico.github.io/nicar2019_reactive-d3/parts/idempotence/

@Fil
Copy link
Member

Fil commented Feb 9, 2023

see discussion on #300

@Fil Fil closed this as not planned Won't fix, can't repro, duplicate, stale Feb 9, 2023
@curran
Copy link
Contributor Author

curran commented Aug 12, 2024

Added to https://github.com/curran/d3-rosetta

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

Successfully merging a pull request may close this issue.

3 participants