отзывчивый график d c. js занимает все окно - PullRequest
1 голос
/ 18 февраля 2020

У меня проблемы с адаптацией диаграммы, я пытаюсь применить тот же пример с D C. js и Crossfilter, которые находятся по этой ссылке: resizing-series .

Диаграмма отзывчива, однако есть некоторые ошибки при взаимодействии с моими таблицами. Например, когда я нажимаю на график или любую из таблиц, график становится очень большим, занимая весь экран. Как показано на рисунке ниже:

enter image description here enter image description here

Когда я покидаю экран, разделенный на Console, график отображается быстро, так как я изменяю размер экрана, размер графика также изменяется, однако, если я оставлю Console, график будет занимать весь экран и не вернется к исходному размеру по умолчанию, показанному на первом изображении.

enter image description here enter image description here

Кто-нибудь знает, как подсказать, как исправить эти ошибки? Я не мог понять, почему это происходит.

Спасибо заранее.

var composite = dc.compositeChart('#composite');
var vendedorTable = dc.dataTable("#vendedores");
var citiesTable = dc.dataTable("#cities");

function remove_empty_bins(source_group) {
  return {
    top: function(N) {
      return source_group.all().filter(function(d) {
        return d.value.totalAno > 1e-3 ||
          d.value.totalHomologo > 1e-3;
      }).slice(0, N);
    }
  };
}

var url = 'https://gist.githubusercontent.com/bernalvinicius/3cece295bc37de1697e7f83418e7fcc9/raw/a5820379ec6eae76ee792495cc5dd1685c977a73/vendedores.json';
d3.json(url).then(function(data) {

  data.forEach(d =>
    Object.assign(d, {
      mes: d.Month,
      atual: d.Vendas_Ano,
      passado: d.Vendas_Ant
    })
  );
  var cf = crossfilter(data);

  vendedorDim = cf.dimension(function(d) {
    return d.vendnm;
  });
  var vendedorGroup = vendedorDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);

  citiesDim = cf.dimension(function(d) {
    return d.zona;
  });
  var citiesGroup = citiesDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);

  var dim = cf.dimension(dc.pluck('mes')),
    grp1 = dim.group().reduceSum(dc.pluck('atual')),
    grp2 = dim.group().reduceSum(dc.pluck('passado'));
  var minMonth = dim.bottom(1)[0].mes;
  var maxMonth = dim.top(1)[0].mes;

  var all = cf.groupAll();

  dc.dataCount(".dc-data-count")
    .dimension(cf)
    .group(all);

  function reduceAdd(p, v) {
    p.totalAno += +v.Vendas_Ano;
    p.totalHomologo += +v.Vendas_Ant;
    return p;
  }

  function reduceRemove(p, v) {
    p.totalAno -= v.Vendas_Ano;
    p.totalHomologo -= v.Vendas_Ant;
    return p;
  }

  function reduceInitial() {
    return {
      totalAno: 0,
      totalHomologo: 0,
    };
  }

  // Fake Dimension
  rank = function(p) {
    return ""
  };

  // Chart by months
  composite
    .width(600)
    .height(300)
    .x(d3.scaleLinear().domain([1, 12]))
    .yAxisLabel("")
    .xAxisLabel("Month")
    .legend(dc.legend().x(500).y(0).itemHeight(13).gap(5))
    .renderHorizontalGridLines(true)
    .compose([
      dc.lineChart(composite)
      .dimension(dim)
      .colors('steelblue')
      .group(grp1, "Currently Year"),
      dc.lineChart(composite)
      .dimension(dim)
      .colors('darkorange')
      .group(grp2, "Last Year")
    ])
    .brushOn(true);

  composite.brush().extent([-0.5, data.length + 1.5])
  composite.extendBrush = function(brushSelection) {
    if (brushSelection) {
      vendedorTable.filter(null);
      vendedorDim.filter(null);
      citiesTable.filter(null);
      citiesDim.filter(null);
      const point = Math.round((brushSelection[0] + brushSelection[1]) / 2);
      return [
        point - 0.5,
        point + 0.5
      ];
    }
  };

  // Sales Table
  vendedorTable.width(500)
    .height(480)
    .dimension(remove_empty_bins(vendedorGroup))
    .group(rank)
    .columns([function(d) {
        return d.key;
      },
      function(d) {
        return Number(Math.round(d.value.totalAno * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      },
      function(d) {
        return Number(Math.round(d.value.totalHomologo * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      }
    ])
    .sortBy(function(d) {
      return d.value.totalAno
    })
    .order(d3.descending)

  // Cities Table
  citiesTable.width(500)
    .height(480)
    .dimension(remove_empty_bins(citiesGroup))
    .group(rank)
    .columns([function(d) {
        return d.key;
      },
      function(d) {
        return Number(Math.round(d.value.totalAno * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      },
      function(d) {
        return Number(Math.round(d.value.totalHomologo * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      }
    ])
    .sortBy(function(d) {
      return d.value.totalAno
    })
    .order(d3.descending)

  // Sales click events
  vendedorTable.on('pretransition', function(table) {
    table.selectAll('td.dc-table-column')
      .on('click', function(d) {
        let filters = table.filters().slice();
        if (filters.indexOf(d.key) === -1)
          filters.push(d.key);
        else
          filters = filters.filter(k => k != d.key);
        if (filters.length === 0)
          vendedorDim.filter(null);
        else
          vendedorDim.filterFunction(function(d) {
            return filters.indexOf(d) !== -1;
          })
        table.replaceFilter([filters]);

        citiesTable.filter(null);
        citiesDim.filter(null);
        composite.filter(null);

        dc.redrawAll();
      });
    let filters = table.filters();
    table.selectAll('tr.dc-table-row')
      .classed('sel-rows', d => filters.indexOf(d.key) !== -1);
  });

  // Cities click events
  citiesTable.on('pretransition', function(table) {
    table.selectAll('td.dc-table-column')
      .on('click', function(d) {
        let filters = table.filters().slice();
        if (filters.indexOf(d.key) === -1)
          filters.push(d.key);
        else
          filters = filters.filter(k => k != d.key);
        if (filters.length === 0)
          citiesDim.filter(null);
        else
          citiesDim.filterFunction(function(d) {
            return filters.indexOf(d) !== -1;
          })
        table.replaceFilter([filters]);

        vendedorTable.filter(null);
        vendedorDim.filter(null);
        composite.filter(null);

        dc.redrawAll();
      });
    let filters = table.filters();
    table.selectAll('tr.dc-table-row')
      .classed('sel-rows', d => filters.indexOf(d.key) !== -1);
  });


  dc.renderAll();

  // reset functions
  $('#reset').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });

  $('#resetTable').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });

  $('#resetTable2').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });


  /****************************************************************************/
  // Functions to handle responsive

  var adjustX = 10,
    adjustY = 40;

  apply_resizing(composite, adjustX, adjustY, function(composite) {
    composite.legend().x(window.innerWidth - 200);
  });

  var find_query = function() {
    var _map = window.location.search.substr(1).split('&').map(function(a) {
      return a.split('=');
    }).reduce(function(p, v) {
      if (v.length > 1)
        p[v[0]] = decodeURIComponent(v[1].replace(/\+/g, " "));
      else
        p[v[0]] = true;
      return p;
    }, {});
    return function(field) {
      return _map[field] || null;
    };
  }();

  var resizeMode = find_query('resize') || 'widhei';

  function apply_resizing(composite, adjustX, adjustY, onresize) {
    if (resizeMode === 'viewbox') {
      composite
        .width(300)
        .height(200)
        .useViewBoxResizing(true);
      d3.select(composite.anchor()).classed('fullsize', false);
    } else {
      adjustX = adjustX || 0;
      adjustY = adjustY || adjustX || 0;
      composite
        .width(window.innerWidth - adjustX)
        .height(window.innerHeight - adjustY);
      window.onresize = function() {
        if (onresize) {
          onresize(composite);
        }
        composite
          .width(window.innerWidth - adjustX)
          .height(window.innerHeight - adjustY);

        if (composite.rescale) {
          composite.rescale();
        }
        composite.redraw();
      };
    }
  }
});
#composite {
  padding: 10px;
}

.dc-table-group {
  visibility: collapse;
}

tr.dc-table-row.sel-rows {
  background-color: lightblue;
}

.brush .custom-brush-handle {
  display: none;
}
<!-- favicon -->
<link rel="shortcut icon" href="https://img.icons8.com/nolan/64/puzzle.png">
<!-- bootstrap.css -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- bootstrap-theme.css -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- dc.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.8/dc.css">
<!-- jquery.js -->
<script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
<!-- bootstrap.js -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- d3.v5.js -->
<script src="https://d3js.org/d3.v5.js"></script>
<!-- crossfilter.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.js"></script>
<!-- dc.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.js"></script>

<title>12</title>
</head>

<body>
  <div class="container-fluid">
    <div class="row content">
      <div class="col-md-10">
        <div class="col-md-4">
          <div id="composite"></div>
        </div>
      </div>
    </div>
    <div class="row content">
      <div class="col-md-10">
        <div style="padding: 20px;;" class="row marginClass">
          <h4 class="pull-left" id="Introduction">
            <small>Fictitious company data | Drilldown Example |</small>
          </h4>
          <h6 class="dc-data-count" style="float: left;margin-left:5px;">
            <span>
                            <span class="filter-count"></span> selected from
            <span class="total-count"></span> records |
            <a id="reset"> Reset </a>
            </span>
          </h6>
        </div>

        <div class="col-md-4">
          <table class="table" id="vendedores">
            <thead>
              <tr>
                <th>Sales</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>

        <div class="col-md-4">
          <table class="table" id="cities">
            <thead>
              <tr>
                <th>City</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
      </div>
    </div>
  </div>
</body>

1 Ответ

1 голос
/ 18 февраля 2020

Спасибо за публикацию примера! Ошибка не появляется в функции фрагмента кода SO, если вы не go в режиме полной страницы. Мне показалось, * с вашей скрипкой легче работать.

Примечание: это полная перезапись моего предыдущего ответа, которая не была ясна.

1. Использование ResizeObserver

обновлено 4/28/20

Начиная с Safari 13.1 (выпущено 24 марта 2020 г.) все современные браузеры поддерживают ResizeObserver . Это самый чистый способ определения изменения размера диаграммы.

Я рекомендую

  1. Используйте макет сверху вниз, такой как flexbox или grid, чтобы расположить элементы div для ваших диаграмм
  2. Используйте ResizeObserver, чтобы определить, когда размер div изменился
  3. Скажите диаграммам, чтобы определить размер div диаграммы, используя .width(null).height(null)

В настоящее время существует один пример изменения размера , который делает это.

Специальное значение null указывает диаграмме, чтобы размер узла SVG соответствовал размеру его родительского div:

chart1.width(null)
    .height(null)

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

const callback = chart => entries => {
    redraw_chart_no_transitions(
        chart
            .width(null)
            .height(null)
            .rescale());
    };

Настройка наблюдателя выглядит следующим образом:

new ResizeObserver(callback(chart1)).observe(d3.select('#test1').node());

Пожалуйста, см. пример для получения более подробной информации.

2. Использование window.onresize

Другие примеры изменения размера следят за window.onresize, поскольку до недавнего времени это был единственный эффективный и надежный кросс-браузерный способ обнаружения изменений.

Они рассчитать размеры диаграммы на основе размера окна, что хорошо работает, если макет находится снизу вверх, например, с использованием макета по умолчанию float: left.

Вот функция, которая устанавливает это:

function apply_resizing(chart, adjustX, adjustY, onresize) {
    if(!Array.isArray(chart))
        chart = [chart];
    if(!isNaN(adjustX))
        adjustX = (dx => x => x-dx)(adjustX);
    adjustX = adjustX || (x => x);
    if(!isNaN(adjustY))
        adjustY = (dy => y => y-dy)(adjustY);
    adjustY = adjustY || adjustX || (y => y);
    chart.forEach(c => c.width(adjustX(window.innerWidth))
                  .height(adjustY(window.innerHeight)));
    window.onresize = function () {
        if (onresize) {
            chart.forEach(onresize);
        }
        chart.forEach(c => {
            c.width(adjustX(window.innerWidth))
                .height(adjustY(window.innerHeight));
            if (c.rescale) {
                c.rescale();
            }
        });
        redraw_chart_no_transitions(chart);
    };
}

Одна диаграмма может быть инициализирована следующим образом:

apply_resizing(chart, 20);

Это заполняет окно, но уменьшает ширину диаграммы на 20 пикселей.

Функция также может принимать несколько диаграмм в массиве и функции настройки для поддержки сложных макетов, например, когда две диаграммы должны разбивать окно по вертикали:

apply_resizing([heatmapChart, barChart], 20, y => y/2-fudge);
...