Как выйти из вложенного selectAll в D3 JS? - PullRequest
1 голос
/ 15 февраля 2020

Знакомство с hos D3 JS работает и, в частности .selectAll/.data/.enter; учимся вкладывать данные.

У меня есть эта рабочая демонстрация:

svg = d3.select('body')
  .append('svg')
  .attr('width',  640)
  .attr('height', 480);
svg.selectAll('text')
  .data( [ 'hello', 'world' ] )
  .enter()
    .append('text')
    .attr('x', 10)
    .attr('y', function( d, i ) {
      return 20 + i * 20;
    })
    .selectAll('tspan')
    .data( function( d, i ) { // d is from the first data
      return Array.from(d); // if needed, could return an array or object that includes the initial value, too.
    })
    .enter()
      .append('tspan')
      .attr('class', function( d, i ) {
        console.log( 'tspan class:', d, i );
        if ( ['a', 'e', 'i', 'o', 'u', 'y'].includes(d) ) {
          return 'vowel';
        }
      })
      .text( function( d, i, foo ) { // d is from the second data
        console.log( 'tspan text:', d, i, /*foo*/ );
        return d;
      })
    .exit()
    .attr('class', 'strong') // this will set strong on <tspan>, but I wanted it on <text>
    ;

(см. на Codepen: D3 JS выберите все SVG вложенные .)

Обратите внимание, что у нас есть два data() со вторым (для <tspan> с), вложенным в первый (для <text> с).

Что я пытаюсь сделать:

  • Я хочу установить атрибут класса strong на <text>.
  • I думал, что exit() выведет меня из "сферы" / гнезда работы с tspans ... но я ошибаюсь.
  • Если я закомментирую exit(), тогда атрибут сильного класса будет установлен на <tspan> с… пока я хочу это на родителе <text>!

Как мне этого добиться, кроме того:

  • Перемещение строки вверх (до второго data()).
  • Используя отдельный оператор (svg.selectAll('text').attr('class', 'strong');)

Я мог бы использовать один из этих двух вариантов, и в этом примере это будет тривиально…
Но Я хочу знать, возможно ли выбраться из вложенных выборок и если да, то как?

Надеюсь, это было достаточно ясно; если нет, пожалуйста, прокомментируйте, и я уточню:)

1 Ответ

0 голосов
/ 16 февраля 2020

Вы можете подумать об изменении ваших исходных данных, и таким образом вы можете просто использовать одну привязку данных и один шаблон обновления. В рамках этого паттерна вы можете проверять / использовать слово, а также проверять каждую букву слова (вы также можете сделать это на лету позже):

const test_data = [ 'hello', 'world', 'wthtvwls' ];

const modified_data = test_data.map((item,index) => {
  return {
    word: item,
    array: Array.from(item)
  }
});

console.log(modified_data);

Затем, используя эти данные (демо - https://codepen.io/Alexander9111/pen/qBdZrLv):

svg = d3.select('body')
  .append('svg')
  .attr('width',  640)
  .attr('height', 480);
svg.selectAll('text')
  .data( modified_data )
  .enter()
    .append('text')
    .attr('x', 10)
    .attr('y', function( d, i ) {
      return 20 + i * 20;
    })
    .html(function(d) {      
      return d.letters.map((letter,index) => {
        if (['a', 'e', 'i', 'o', 'u', 'y'].includes(letter)){
          return '<tspan class="vowel">' + letter + '</tspan>';
        } else{
          return '<tspan>' + letter + '</tspan>';
        }        
      }).join("");
    })   
    .attr('class', function( d, i ) {
        for (letter of d.letters){
          console.log('letter', letter);
          if (['a', 'e', 'i', 'o', 'u', 'y'].includes(letter)) {
            return 'strong';
          }
        }
      })
    .exit();

Обратите внимание на использование функции .html(), а затем .map() в ней, используя наши текущие data.letters в обычном шаблоне обновления, чтобы добавить либо '<tspan class="vowel">' или '<tspan>' (без гласного класса).

https://codepen.io/Alexander9111/pen/qBdZrLv

Но я не уверен, хотите ли вы такой конкретный c вариант использования или более общий ответ на вложенный шаблон обновления? Если так, то, возможно, этот блок помогает? - https://bl.ocks.org/mpbastos/bd1d6763d34ac5d3ce533a581b291364

ОБНОВЛЕНИЕ - я понимаю, что неправильно прочитал вопрос в первый раз

Так что теперь мне ясно, что мой временное решение не более полезно, чем два других решения, предложенных @Fabien (OP) (перемещение строки вверх (перед вторым data ()) или использование отдельного оператора svg.selectAll('text').attr('class', 'strong');)

Я пробовал такие шаблоны как:

svg.selectAll('text')
    .data( test_data )
    .enter()
    .append('text')
    .attr('x', 10)
    ...
    .selectAll('tspan')
    .data( ...)
    .enter()
      .append('tspan')
      .attr('class', 'example')
    .exit()
    .select(this.ParentNode)
    .attr('class', 'example')

А также с .select(this.ParentNode) до .exit(), но не сработало.

Единственное, что работает, но это также обходной путь и, вероятно, анти-шаблон вот что - перехитрите функцию attr, чтобы "захватить" родительский объект за пределами вложенного обновления:

svg = d3.select('body')
  .append('svg')
  .attr('width',  640)
  .attr('height', 480);
text = svg.selectAll('text')
    .data( test_data )
    .enter()
    .append('text')
    .attr('x', 10)
    .attr('y', function( d, i ) {
      return 20 + i * 20;
    })
    .selectAll('tspan')
    .data( function( d, i ) { // d is from the first data
      return Array.from(d); // if needed, could return an array or object that includes the initial value, too.
    })
    .enter()
      .append('tspan')
      .attr('class', function( d, i ) {
        console.log(1, i, this.parentNode);
         //hijack the attr function to "grab" parent outside nested update pattern
        if (i == 0) {
          d3.select(this.parentNode).attr('class', 'strong');
        }
        //console.log( 'tspan class:', d, i );
        if ( ['a', 'e', 'i', 'o', 'u', 'y'].includes(d) ) {
          return 'vowel';
        }
      })
      .text( function( d, i, foo ) { // d is from the second data
        //console.log( 'tspan text:', d, i, /*foo*/ );
        return d;
      })    
    .exit();

Это также не очень хорошее решение.

Я думаю, потому что когда-то вызывается функция .data() при выборе теперь он больше обычного объекта списка узлов, а функция .exit() предназначена для использования только на один уровень глубины.

Я думаю, что два решения, изложенные в ОП, являются лучшими, но я хотел бы оказаться неправым!

...