问题描述
我正在尝试使用 D3 创建一个直方图,它对条形和轴都有很好的动画过渡。让酒吧工作很简单,但我正在努力了解如何用轴做同样的事情。在下面的示例中,转换看起来正在发生,但实际上每次都添加了一个新轴,而没有删除旧轴。
我的最终目标是使用 R2D3 开发这样的小部件,然后将 javascript 交给其他人以在 Java 应用程序中实现,所以我需要确保它是可转移的并且不使用 R/ javascript 文件中的闪亮/R2D3 特定内容。
这是hist.js
脚本
// !preview r2d3 data=data.frame(density = c(10,20,5),from = c(0,1,3),to = c(1,3,4))
//
// r2d3: https://rstudio.github.io/r2d3
//
var margin = {left:40,right:30,top:10,bottom:30,axis_offset:10};
var min_from = d3.min(data,function(d) {return d.from;});
var max_to = d3.max(data,function(d) {return d.to;});
var max_density = d3.max(data,function(d) { return d.density;});
svg.append('g')
.attr('transform','translate('+(margin.left - margin.axis_offset)+',0)')
.attr("class","y_axis");
svg.append('g')
.attr('transform','translate(0,'+(height - margin.bottom + margin.axis_offset)+')')
.attr("class","x_axis");
var x = d3.scaleLinear()
.domain([min_from,max_to])
.range([margin.left,width-margin.right])
.nice();
var y = d3.scaleLinear()
.domain([0,max_density])
.range([height-margin.bottom,margin.top])
.nice();
svg.selectAll('.y_axis')
.transition()
.duration(500)
.call(d3.axisLeft(y));
svg.selectAll('.x_axis')
.transition()
.duration(500)
.call(d3.axisBottom(x));
var bars = svg.selectAll('rect').data(data);
bars.enter().append('rect')
.attr('x',function(d) { return x(d.from); })
.attr('width',function(d) { return x(d.to) - x(d.from)-1;})
.attr('y',function(d) { return y(d.density); })
.attr('height',function(d) { return y(0) - y(d.density); })
.attr('fill','steelblue');
bars.exit().remove();
bars.transition()
.duration(500)
.attr('x',function(d) { return y(0) - y(d.density); });
这是我运行它的闪亮应用
library(shiny)
library(r2d3)
library(data.table)
library(jsonlite)
get_hist <- function(x) {
buckets <- seq(0,mean(x)+3*sd(x),length.out = 21)
h <- hist(x,breaks = c(buckets,Inf),plot = FALSE)
y <- data.table(count = h$counts,from = head(h$breaks,-1),to = head(shift(h$breaks,-1))[-.N]
y[,density := count/(to-from)]
y[]
}
new_data <- function() {
sh <- 1
rgamma(10,sh,1/sh)
}
ui <- fluidPage(
actionButton("add_data","Add more data"),d3Output('d3_hist')
)
server <- function(input,output,session) {
samp <- reactiveVal(new_data())
observeEvent(input$add_data,{
samp(c(samp(),new_data()))
})
output$d3_hist <- renderD3({
y <- get_hist(samp())
r2d3(data = toJSON(y),script = 'hist.js')
})
}
shinyApp(ui,server)
解决方法
每次更新数据时,都会为每个轴创建一个新的 g
元素。这将创建多个 g
元素,其类为 x_axis
/ y_axis
,您可以在所有这些元素上调用轴生成器:
svg.append('g') // append a new g every update for x axis
.attr('transform','translate(0,'+(height - margin.bottom + margin.axis_offset)+')')
.attr("class","x_axis");
svg.selectAll('.x_axis') // call axis generator for every x axis g.
.transition()
.duration(500)
.call(d3.axisBottom(x));
r2d3 的设置有点不同,因为通常您会为每个轴创建一个 g
,然后使用更新函数来更新数据和轴。这里每次都运行整个 javascript 脚本,因此我们需要避免在每个轴上附加 g
:
例如:
if(svg.select(".y_axis").empty()) {
svg.append('g')
.attr('transform','translate('+(margin.left - margin.axis_offset)+',0)')
.attr("class","y_axis");
}
if(svg.select(".x_axis").empty()) {
svg.append('g')
.attr('transform','+(height - margin.bottom + margin.axis_offset)+')')
.attr("class","x_axis");
}
在 javascript 中可以采用不同的方法,但这可能是最直接的方法。理想情况下,r2d3 可以更轻松地让您在每次更新时运行更新函数而不是整个脚本。