Отслеживайте и фильтруйте значение за последний месяц в dc.js

У меня проблема, аналогичная описанной в DC.JS, полученном в прошлом месяце. значение как фильтр.

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

Для этого мне нужно отслеживать последний месяц, выбранный с помощью линейной диаграммы. См. Мой jsFiddle здесь: https://jsfiddle.net/BernG/wo60z64j/12/

var data = [{date:"201501",company:"A", product: "Prod1", stock:575}
    ,   {date:"201502",company:"A", product: "Prod1", stock:325}
    ,   {date:"201503",company:"A", product: "Prod1", stock:200}
    ,   {date:"201504",company:"A", product: "Prod1", stock:450}
    ,   {date:"201501",company:"A", product: "Prod2", stock:279}
    ,   {date:"201502",company:"A", product: "Prod2", stock:93}
    ,   {date:"201503",company:"A", product: "Prod2", stock:0}
    ,   {date:"201504",company:"A", product: "Prod2", stock:372}    
    ,   {date:"201501",company:"A", product: "Prod3", stock:510}
    ,   {date:"201502",company:"A", product: "Prod3", stock:340}
    ,   {date:"201503",company:"A", product: "Prod3", stock:680}
    ,   {date:"201504",company:"A", product: "Prod3", stock:170}    
    ,   {date:"201501",company:"B",product: "Prod1", stock:1000}
    ,   {date:"201502",company:"B",product: "Prod1", stock:1100}
    ,   {date:"201503",company:"B",product: "Prod1", stock:900}
    ,   {date:"201504",company:"B",product: "Prod1", stock:1200}
    ,   {date:"201501",company:"B",product: "Prod2", stock:1000}
    ,   {date:"201502",company:"B",product: "Prod2", stock:1200}
    ,   {date:"201503",company:"B",product: "Prod2", stock:900}
    ,   {date:"201504",company:"B",product: "Prod2", stock:1200}        
    ,   {date:"201501",company:"B",product: "Prod3", stock:1000}
    ,   {date:"201502",company:"B",product: "Prod3", stock:1100}
    ,   {date:"201503",company:"B",product: "Prod3", stock:900}
    ,   {date:"201504",company:"B",product: "Prod3", stock:600}];


// Reading and formatting values
var dateFormat = d3.time.format('%Y%m');
data.forEach(function (d) {
    d.dd = dateFormat.parse(d.date);
    d.year = d3.time.year(d.dd);
    d.month = d3.time.month(d.dd);
});

// Definition of crossfilter and dimensions
var     ndx = crossfilter(data)
    ,   dimMonth = ndx.dimension(function(d){return d.month})
    ,   dimProduct = ndx.dimension(function(d){return d.product})
    ,   dimCompany = ndx.dimension(function(d){return d.company;});


var     lastStaticDate = dimMonth.top(1)[0].month;      // identify last date in full time domain
var     firstStaticDate = dimMonth.bottom(1)[0].month;  // identify first date in full time domain


// Definition of a function to keep track of the last date value in the brush attached to the chartMonth. 
// If chartMonth object does not exist or its corresponding brush is empty (has not been brushed), then it returns the lastStaticDate 
// otherwise, it returns the last date in the brush selection
var getLastDate = function(){
                    if (typeof chartMonth === "undefined"){   // test if chartMonth is already in the DOM, if not return lastStaticDate
                        return lastStaticDate;
                        }
                    else { 
                        if (chartMonth.brush().empty()) {    // if chartMonth has been created but brush does not have have a selection
                            return lastStaticDate;
                            }   
                        else {
                            return chartMonth.brush().extent()[1];
                            };
                        }
                    };

var lastDate = d3.time.month.ceil(getLastDate());   // lastDate is snapped to return a date where we have data in the x-domain 


dateBal.innerHTML = lastDate;       


var     grpMonth = dimMonth.group().reduceSum(function(d){return d.stock;});    // the line chart displays all values in the x-axis to show stock evolution

// Definition of custom reduce functions                    
function reduceAdd(p,v) {   
    if (p.month===lastDate){                                
        p.stock += v.stock;
        return p;
        }
    else {return p;}
};

function reduceRemove(p,v) {
    if (p.month!== lastDate){                               
        p.stock -= v.stock;
        return p;}
    else {return p;}
};

function reduceInitial() { 
    return {stock: 0}
};

// Application of reduce functions

var     grpCompany = dimCompany
                    .group()
                    .reduce(reduceAdd, reduceRemove, reduceInitial);

var     grpProduct = dimProduct
                    .group()
                    .reduce(reduceAdd, reduceRemove, reduceInitial);


var chartCompany = dc.barChart('#chartCompany');
var chartProduct = dc.barChart("#chartProduct");
var chartMonth = dc.lineChart('#chartMonth');

chartCompany
    .width(400)
    .height(400)
    .margins({top: 50, right: 50, bottom: 50, left: 50})
    .dimension(dimCompany)
    .group(grpCompany)
    .x(d3.scale.ordinal())
    .xUnits(dc.units.ordinal)
    .elasticY(true);

chartProduct
    .width(400)
    .height(400)
    .margins({top: 50, right: 50, bottom: 50, left: 50})
    .dimension(dimProduct)
    .group(grpProduct)
    .x(d3.scale.ordinal())
    .xUnits(dc.units.ordinal)
    .elasticY(true);


chartMonth
    .width(400)
    .height(400)
    .margins({top: 50, right: 50, bottom: 50, left: 50})
    .renderlet(function (chart) {
        // rotate x-axis labels
        chart.selectAll('g.x text')
        .attr('transform', 'translate(-10,10) rotate(315)');
        })
    .dimension(dimMonth)
    .group(grpMonth)
    .x(d3.time.scale().domain([firstStaticDate, lastStaticDate]))
    .xUnits(d3.time.months)
    .elasticX(true);

dc.renderAll(); 

Решение, которое я попробовал, основано на единственном ответе на вышеупомянутый вопрос (который, кстати, не был отмечен как принятый) и следует этой логике:

1) Сначала я пытаюсь определить последнюю дату в наборе перекрестно отфильтрованных данных. После инициализации это будет последняя дата моего измерения времени перед перемещением кисти на линейной диаграмме. В противном случае он возвращает дату из второго элемента экстента кисти. К этой дате я применяю функцию потолка, чтобы убедиться, что возвращаемая дата существует как точная дата в моем измерении времени.

2) Я применяю пользовательские функции сокращения, исключая данные за месяцы, отличные от текущего выбора.

Мои конкретные вопросы:

  • Как сделать переменную (lastDate) реактивной? В консоли хорошо работает следующее: d3.time.month.ceil (getLastDate ()). Однако он не реагирует на событие интерактивной чистки.

  • Какие изменения необходимо внести в мои пользовательские функции сокращения, чтобы накапливать только те значения, которые соответствуют lastDate, и исключают все остальные? По какой-то причине функции сокращения клиента в том виде, в каком они определены в настоящее время, неправильно накапливают значения запасов. Например, при инициализации, если я проверяю объект, в котором находится grpCompany, он показывает значение акции как 0. grpCompany.all () в консоли

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

  • Исходное сообщение, похоже, не имеет принятого ответа.
  • Было предложение помочь, если OP предоставит рабочий jsFiddle, но его не было.
  • Я хотел сделать следующий комментарий в исходном сообщении с просьбой о разъяснениях, но это было невозможно сделать, потому что у меня еще нет репутации, необходимой для публикации комментария.

person BGutierrez    schedule 27.04.2016    source источник


Ответы (1)


Хорошо, это действительно сложно, поэтому я начал свой комментарий по предыдущему вопросу, сказав, что Crossfilter не подходит для этого. Мой ответ был довольно сложным, и вам не удалось сделать то, что я хотел предложить, но это не ваша вина. Библиотека Universe поддерживает кое-что из того, что вы предлагаете, но в целом я бы рекомендовал пересмотреть ваш подход, если он это сложно. Однако ваш сценарий, как указано, также немного отличается от сценария в предыдущем вопросе и имеет более простой ответ:

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

chartMonth
  ...
  .filterHandler(function(d,f) {
    if(f.length > 0) {
      d.filterExact(d3.time.month.ceil(f[0][1]));
      dateBal.innerHTML = d3.time.month.ceil(f[0][1]);
    } else {
      d.filterAll();
      dateBal.innerHTML = "No month selected"
    }
    return f;
  })

Вот рабочая версия на основе вашего JSFiddle: https://jsfiddle.net/esjewett/utohm7mq/2/ < / а>

Итак, это простой ответ. На самом деле он просто фильтрует весь ваш кросс-фильтр до последнего месяца, выбранного в кисти (или фактически до следующего месяца). Если вы также хотите отфильтровать некоторые другие диаграммы по фактическим датам, выбранным кистью, то вы говорите о наличии нескольких конфликтующих фильтров, а Crossfilter в настоящее время не поддерживает это очень хорошо. Мы заинтересованы в добавлении функций для поддержки нескольких групп фильтров, но пока не начали заниматься этим. В этом случае лучший подход сейчас, вероятно, состоит в том, чтобы поддерживать 2 отдельных кросс-фильтра и выводить разные группы диаграмм из разных кросс-фильтров.

person Ethan Jewett    schedule 27.04.2016
comment
Спасибо за быстрый ответ. Предлагаемое решение, безусловно, очень простое и отлично работает. Я просто немного изменил filterHandler, чтобы включить условие, в котором f.length == 0, поэтому я могу применить фильтр с помощью lastStaticDate. Это позволяет отображать гистограммы при загрузке страницы, уже отфильтрованной на основе последней даты в линейной диаграмме. - person BGutierrez; 28.04.2016