【问题标题】:Asynchronously loading new data to a d3 chart将新数据异步加载到 d3 图表
【发布时间】:2015-05-11 15:38:33
【问题描述】:

我正在使用 d3.js 构建一种时间线工具,可以在设定的时间段内向前和向后缩放和翻页。我的轴完美过渡,但我似乎无法让我的数据重新加载。这个想法是缩放级别定义了一个移动窗口,例如用户在几天内前后移动的 1 小时。每次他们这样做时,都会调用以获取该小时的数据。

为大代码块道歉,处理新数据获取的方法位于底部。如果有任何不同,该工具就是 angular.js 的指令。

 scope.zoomLevels = [
                {
                    value: 1,
                    unit: 'minutes',
                    tickFormat: $window.d3.time.format('%S')
                },
                {
                    value: 30,
                    unit: 'minutes',
                    tickFormat: $window.d3.time.format('%H:%M')
                },
                {
                    value: 1,
                    unit: 'hours',
                    tickFormat: $window.d3.time.format('%H:%M')
                },
                {
                    value: 3,
                    unit: 'hours',
                    tickFormat: $window.d3.time.format('%H:%M')
                },
                {
                    value: 6,
                    unit: 'hours',
                    tickFormat: $window.d3.time.format('%H:%M')
                },
                {
                    value: 12,
                    unit: 'hours',
                    tickFormat: $window.d3.time.format('%H:%M')
                },
                {
                    value: 1,
                    unit: 'days',
                    tickFormat: $window.d3.time.format('%H:%M')
                }
            ];

            var margin = {top: 0, right: 0, bottom: 0, left: 0},
                width = $window.innerWidth,
                height = $window.innerHeight - margin.top - margin.bottom,
                xAxisHeight = $window.innerHeight - margin.top - ($window.innerHeight/2);

            var start = $window.moment(Datasets.current.dates[0], 'DD/MM/YYYY'),
                end = $window.moment(Datasets.current.dates[Datasets.current.dates.length - 1], 'DD/MM/YYYY'),
                duration = $window.moment.duration(end.diff(start));

            scope.zoomLevel = 3;
            scope.page = 0;
            scope.maxPage = duration.asMinutes()/scope.zoomLevels[scope.zoomLevel].value;

            //$scope.tweets = Tweets.getTweets(start.format('x'), end.format('x'));

            var rows = Math.floor(height/ 60);

            console.log(rows + ' Rows');

            var x = $window.d3.time.scale()
                .domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()])
                .range([0, width]);

            var y =  $window.d3.scale.linear()
                .domain([(rows/2) * -1,(rows/2)])
                .range([height, 0]);

            var xAxis = $window.d3.svg.axis()
                .scale(x);

            var yAxis =  $window.d3.svg.axis()
                .scale(y)
                .tickSize(5)
                .orient('right');

            var svg = $window.d3.select('#timeline').append('svg')
                .attr('width', width + margin.left + margin.right)
                .attr('height', height)
                .append('g')
                .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            svg.append('g')
                .attr('class', 'x axis')
                .attr('transform', 'translate(0,' + xAxisHeight + ')')
                .call(xAxis)
                .selectAll('text')
                .attr('y', 15)
                .attr('x', 0)
                .attr('dy', '.35em');

            svg.append('g')
                .attr('class', 'y axis')
                .call(yAxis);

            Articles.get({startDate: start.format(), endDate: start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).format(), limit: 20}, function(data){

                var bar = svg.selectAll('.news')
                    .data(data.items)
                    .enter()
                    .append('g')
                    .attr('class', 'news')
                    .on('mouseover', function(d) {
                        console.log(d);
                    })
                    .on('mouseout', function(d) {

                    });

                bar.append('rect')
                    .attr('width', 250)
                    .attr('height', 60)
                    .attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
                    .attr('y', function(d){return y(2);})

                bar.append('foreignObject')
                    .attr('width', 250)
                    .attr('height', 60)
                    .attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
                    .attr('y', function(d){return y(2);})
                    .append('xhtml:p')
                    .attr("class","statement")
                    .text(function(d) { return d.title; });

            });

            scope.movePage = function(direction){
                if(!(direction === 1 && scope.page === scope.maxPage) || !(direction === -1 && scope.page === 0)){

                    console.log('Paging allowed');

                    if(direction === 1){

                        start = start.add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit);

                        x.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()]);

                        Articles.get({startDate: start.format(), endDate: start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).format(), limit: 20}, function(data) {

                            console.log(data.items.length);

                            svg.selectAll('.news').data(data.items).transition().duration(1500).ease('sin-in-out');

                            svg.selectAll('g.axis.x').transition().duration(1500).ease('sin-in-out').call(xAxis);
                        });

                        scope.page++;
                    }

                    if(direction === -1){

                        start = start.subtract(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit);

                        x.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()]);

                        svg.selectAll('g.axis.x').transition().duration(1500).ease('sin-in-out').call(xAxis);

                        scope.page--;

                    }
                }
            };

            scope.toggleZoom = function(direction){
                if(!(direction === 1 && scope.zoomLevel === 6) || !(direction === -1 && scope.zoomLevel === 0)){

                    duration = $window.moment.duration(end.diff(start));

                    scope.zoomLevel = scope.zoomLevel + direction;

                    console.log(scope.zoomLevels[scope.zoomLevel].unit);

                    switch (scope.zoomLevels[scope.zoomLevel].unit){
                        case 'minutes':

                            scope.maxPage = duration.asMinutes()/scope.zoomLevels[scope.zoomLevel].value;

                            console.log(duration.asMinutes() + ' Minutes');
                            console.log((duration.asMinutes()/scope.zoomLevels[scope.zoomLevel].value) + ' Pages');
                            break;
                        case 'hours':

                            scope.maxPage = duration.asHours()/scope.zoomLevels[scope.zoomLevel].value;

                            console.log(duration.asHours() + ' Hours');
                            console.log((duration.asHours()/scope.zoomLevels[scope.zoomLevel].value) + ' Pages');
                            break;
                        case 'days':

                            scope.maxPage = duration.asDays()/scope.zoomLevels[scope.zoomLevel].value;

                            console.log(duration.asDays() + ' Days');
                            console.log((duration.asDays()/scope.zoomLevels[scope.zoomLevel].value) + ' Pages');
                            break;
                    }

                    x.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()]);

                    svg.selectAll('g.axis').transition().duration(1500).ease('sin-in-out').call(xAxis);

                }
            };

更新 #1

我已经能够在 console.log(data.items.length) 行之后使用 Articles.get 回调中的以下代码来显示新数据并删除旧数据

var news = svg.selectAll(".news").data(data.items);

                            // Add new element
                            news.enter()
                                .append("g")
                                .attr("class", "news")
                                .transition()
                                .duration(1500)
                                .ease("sin-in-out");

                            news.append('rect')
                                .attr('width', 250)
                                .attr('height', 60)
                                .attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
                                .attr('y', function(d){return y(2);});

                            news.append('foreignObject')
                                .attr('width', 250)
                                .attr('height', 60)
                                .attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
                                .attr('y', function(d){return y(2);})
                                .append('xhtml:p')
                                .attr("class","statement")
                                .text(function(d) { return d.title; });

                            // Remove old elements
                            news.exit()
                                .attr("class", "news")
                                .transition()
                                .duration(1500)
                                .ease("sin-in-out")
                                .remove();

但是动画并没有真正起作用。最终,如果用户单击下一页,我希望代表每个新闻项目的 svg 元素以与轴移动相同的速度向左移动。目前,当用户单击前进和后退按钮时,它们会立即出现/消失。

【问题讨论】:

  • console.log(data.items.length); 有没有发生过? Paging allowed 怎么样?
  • 是的,它们都发生了。 svg.selectAll('g.axis.x').transition().duration(1500).ease('sin-in-out').call(xAxis);同样发生的情况是,轴会用轴域上的新开始日期和结束日期重绘
  • data.items.length 是 20?
  • 并非总是如此,最多可以是 20 个。它从数据库中提取国际新闻头条,每个都带有时间戳并绘制在图表上。
  • 好的,很酷,只需检查它是否大于零。我想你必须调试你的 svg.selectAll('.news') 行。也许添加一个$scope.debug = data.items,然后你可以放一个{{ debug | json }} 在你的 html 中的某个地方,看看来自 API 的数据是否有问题

标签: javascript angularjs d3.js


【解决方案1】:
if(!(direction === 1 && scope.page === scope.maxPage) || !(direction === -1 && scope.page === 0)){

if(direction === 1){ ... }
if(direction === -1){ .. }

这并不容易解析,你确定任何一个内部代码块都可以在给定这些条件的情况下执行吗?

【讨论】:

  • 是的,我使用一个变量(方向)来指示用户是在时间上向前还是向后移动。 1 向前移动,-1 向后移动。另一个逻辑决定这是否是一个有效的举动。例如,它们位于第一页,因此无法后退。
  • 对不起,是的,我得到了那部分。它不会解决您的问题,但在这种情况下,顶级条件应该是&&。 (如果他们在开始之前没有尝试返回,并且他们没有尝试超越结束,请继续并更改页面)
  • 应该如此!感谢现场
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 2019-01-11
  • 1970-01-01
  • 2020-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多