响应式d3刷选

3
我有一个基于Mike Bostock的'通过刷选实现聚焦和上下文'的d3时间轴,我正在尝试使其响应式。
我已经能够在大部分情况下实现这一点,但是我在刷子的范围方面遇到了困难。作为一种解决方法,我尝试将其设置为上下文的新宽度,但它的行为极不稳定。我尝试的其他所有方法似乎都没有效果 - 范围矩形的宽度没有改变。
我需要一种方法来查找范围矩形的x和宽度,并将它们应用于我的x比例尺(命名为xContext)进行调整大小。这里有一个'工作'版本here,完整的代码如下所示。调整大小函数位于底部。
非常感谢您的帮助。
var marginTimeline = {top: 0, right: 18, bottom: 260, left: 0},
    marginContext = {top: 400, right: 18, bottom: 80, left: 0},
    w = parseInt(d3.select("#chart").style("width")) - marginTimeline.left - marginTimeline.right,
    hTimeline = parseInt(d3.select("#chart").style("height")) - marginTimeline.top - marginTimeline.bottom,
    hContext = parseInt(d3.select("#chart").style("height")) - marginContext.top - marginContext.bottom;

//Height of the bars drawn. Context bars are half this.
var barHeight = hTimeline * 0.04;    

var formatDate = d3.time.format("%Y%m%d"),
    parseDate = formatDate.parse;

var xTimeline = d3.time.scale().range([0, w]),
    xContext = d3.time.scale().range([0, w]),
    yTimeline = d3.scale.linear().domain([0, 6]).range([hTimeline, 0]).nice(),
    yContext = d3.scale.linear().range([hContext, 0]);

var thous = d3.format(",");
var displayDate = d3.time.format("%d %b %Y");
var displayMonthYear = d3.time.format("%b %Y");
var displayYear = d3.time.format("%Y");

var xAxisTimeline = d3.svg.axis().scale(xTimeline).orient("bottom"),
    xAxisContext = d3.svg.axis().scale(xContext).orient("bottom"),
    yAxisTimeline = d3.svg.axis().scale(yTimeline).orient("left").outerTickSize(0).ticks(0),
    yAxisContext = d3.svg.axis().scale(yContext).orient("left").outerTickSize(0).ticks(0);

var svg = d3.select("#chart")
    .attr("width", w + marginTimeline.left + marginTimeline.right)
    .attr("height", hTimeline + marginTimeline.top + marginTimeline.bottom)
    .append("g");

svg.append("defs").append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", w)
    .attr("height", hTimeline);

var opTimeline = svg.append("g")
    .attr("class", "timeline")
    .attr("width", w)
    .attr("height", hTimeline)
    .attr("transform", "translate(10,0)");

var opContext = svg.append("g")
    .attr("class", "context")
    .attr("transform", "translate(10," + marginContext.top + ")");

var brush = d3.svg.brush()
    .x(xContext)
    .extent([0, 1])
    .on("brush", brushed);

queue()
  .defer(d3.json, "http://pasi.com.au/omarpasha/api/get_category_posts/?slug=shows&include=title,url,content,custom_fields")
  .defer(d3.json, "http://pasi.com.au/omarpasha/api/get_category_posts/?slug=timeline&include=title,url,content,custom_fields")
  .await(ready); 

function ready(error, shows, history) {
                  shows.posts.forEach(function(d) {
                  d.id = d.id;
                  d.title = d.title;
                  d.showpage = d.url;
                  d.startDate = parseDate(d.custom_fields.starting_date[0]);
                  d.endDate = parseDate(d.custom_fields.finishing_date[0]);
})
                  history.posts.forEach(function(d) {
                  d.id = d.id;
                  d.title = d.title;
                  d.startDate = parseDate(d.custom_fields.starting_date[0]);
                  d.endDate = parseDate(d.custom_fields.finishing_date[0]);
                  d.line = d.custom_fields.line;
                  d.dateFormat = d.custom_fields.date_format;
});


var minDateShows = d3.min(shows.posts.map(function(d) { return d.startDate; })); 

var minDateHistory = d3.min(history.posts.map(function(d) { return d.startDate; })); 

var minDate =  (minDateShows < minDateHistory ? minDateShows : minDateHistory);

var leftDate = new Date(minDate.getTime());
    leftDate.setDate(leftDate.getDate()-40);

var maxDateShows = d3.max(shows.posts.map(function(d) { return d.endDate; })); 

var maxDateHistory = d3.max(history.posts.map(function(d) { return d.endDate; })); 

var maxDate =  (maxDateShows > maxDateHistory ? maxDateShows : maxDateHistory);

var rightDate = new Date(maxDate.getTime());
    rightDate.setDate(rightDate.getDate()+1400);


  xTimeline.domain([leftDate, rightDate]);
  xContext.domain(xTimeline.domain());
  yContext.domain(yTimeline.domain());

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset(function(d) { if (xTimeline(d.endDate)  > 800) { return [-10, 8] } else { return [-10, -8]  } })
  .direction(function(d) { if (xTimeline(d.endDate)  > 800) { return 'nw' } else { return 'ne'  } })
  .html(function(d) {
    if (displayMonthYear(d.startDate) == displayMonthYear(d.endDate)) {
          return d.title + "<br/><p class='yellow'>" + displayMonthYear(d.startDate) + "</p>"; }
        else { 
          return d.title + "<br/><p class='yellow'>"+ displayMonthYear(d.startDate) + " to " + displayMonthYear(d.endDate) + "</p>"; }
  });

var tip2 = d3.tip()
  .attr('class', 'd3-tip')
  .direction(function(d) { if (xTimeline(d.endDate)  > 800) { return 'nw' } else { return 'ne'  } })
  .offset(function(d) {
      if (xTimeline(d.endDate)  > 800) {
        return [-10, 8];
      } else {
        return [-10, -8];
      }
  })
  .html(function(d) {
    var toolTipContent = "";
    if ((xTimeline(d.endDate) - xTimeline(d.startDate) == 0)) {
      toolTipContent = getToolTipContent(d, true);
    } else {
      toolTipContent = getToolTipContent(d, false);
    }
    return toolTipContent;
  });

function getToolTipContent(d, sameDates) {
  var toolTipContent = d.title + "<br/><p class='yellow'>";
  if (d.dateFormat == "Year only") {
    toolTipContent +=  (sameDates)
      ? displayYear(d.startDate) + "</p>" + d.content
      : displayYear(d.startDate) + " to " + displayYear(d.endDate);
  } else if (d.dateFormat == "Month and year") {
    toolTipContent +=  (sameDates)
      ? displayMonthYear(d.startDate) + "</p>" + d.content
      : displayMonthYear(d.startDate) + " to " + displayMonthYear(d.endDate);
  } else {
    toolTipContent +=  (sameDates)
      ? displayDate(d.startDate) + "</p>" + d.content
      : displayDate(d.startDate) + " to " + displayDate(d.endDate);
  }
  toolTipContent += "</p>" + d.content;
  return toolTipContent;
}  

svg.call(tip);
svg.call(tip2);

opTimeline.append("line")
   .attr("class", "show show-line")
   .attr("x1", 0)
   .attr("x2",  w)
   .attr("y1", yTimeline(5))
   .attr("y2", yTimeline(5));

opTimeline.append("line")
   .attr("class", "ost ost-line")
   .attr("x1", 0)
   .attr("x2",  w)
   .attr("y1", yTimeline(3))
   .attr("y2", yTimeline(3));

opTimeline.append("line")
   .attr("class", "blackart blackart-line")
   .attr("x1", 0)
   .attr("x2",  w)
   .attr("y1", yTimeline(1))
   .attr("y2", yTimeline(1));

opContext.append("line")
   .attr("class", "context show context-show-line")
   .attr("x1", 0)
   .attr("x2",  w)
   .attr("y1", yContext(5))
   .attr("y2", yContext(5));

opContext.append("line")
   .attr("class", "context ost context-ost-line")
   .attr("x1", 0)
   .attr("x2",  w)
   .attr("y1", yContext(3))
   .attr("y2", yContext(3));

opContext.append("line")
   .attr("class", "context blackart context-blackart-line")
   .attr("x1", 0)
   .attr("x2",  w)
   .attr("y1", yContext(1))
   .attr("y2", yContext(1));

opTimeline.append("text")
   .attr("class", "show show-text")
   .attr("x", 10)
   .attr("y", yTimeline(5) + 26)
   .text("Shows");

opTimeline.append("text")
   .attr("class", "ost ost-text")
   .attr("x", 10)
   .attr("y", yTimeline(3) + 26)
   .text("Ostrowsky Family");

opTimeline.append("text")
   .attr("class", "blackart blackart-text")
   .attr("x", 10)
   .attr("y", yTimeline(1) + 26)
   .text("Black Art");

svg.append("text")
   .attr("class", "explanation")
   .attr("x", 10)
   .attr("y", 380)
   .text("Move the handles below to adjust the time period");

opTimeline.append("g")
   .selectAll("rect")
   .data(shows.posts)
   .enter()
   .append("svg:a")
   .attr("xlink:href", function(d){return d.showpage;})
   .append("rect")
   .attr("class", "event show-event show")
   .attr("clip-path", "url(#clip)")
   .attr("x", (function(d) { return xTimeline(d.startDate); }))
   .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 12)) {
    return (xTimeline(d.endDate) - xTimeline(d.startDate));}
    else {
      return 12
    } }))
   .attr("y", yTimeline(5) - (barHeight * 0.5))
   .attr("height", barHeight)
   .attr("rx", 10)
   .attr("ry", 10);

opTimeline.append("g")
   .selectAll("rect")
   .data(history.posts)
   .enter()
   .append("rect")
   .attr("class", (function(d) { if (d.line == "Ostrowsky family") { return "event ost-event ost" } else { return "event blackart-event blackart" } }))
   .attr("clip-path", "url(#clip)")
   .attr("x", (function(d) { return xTimeline(d.startDate); }))
   .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 12)) {
    return (xTimeline(d.endDate) - xTimeline(d.startDate));}
    else {
      return 12
    } }))
   .attr("y", (function(d) { if (d.line == "Ostrowsky family") { return yTimeline(3) - (barHeight * 0.5) } else { return yTimeline(1) - (barHeight * 0.5) } }))
   .attr("height", barHeight)
   .attr("rx", 10)
   .attr("ry", 10);

opContext.append("g")
   .selectAll("rect")
   .data(shows.posts)
   .enter()
   .append("rect")
   .attr("class", "event show-event show")
   .attr("x", (function(d) { return xContext(d.startDate); }))
   .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 6)) {
    return (xTimeline(d.endDate) - xTimeline(d.startDate));}
    else {
      return 6
    } }))
   .attr("y", yContext(5) - (barHeight * 0.25))
   .attr("height", barHeight/2)
   .attr("rx", 5)
   .attr("ry", 5);

opContext.append("g")
   .selectAll("rect")
   .data(history.posts)
   .enter()
   .append("rect")
   .attr("class", (function(d) { if (d.line == "Ostrowsky family") { return "event ost-event ost" } else { return "event blackart-event blackart" } }))
   .attr("x", (function(d) { return xContext(d.startDate); }))
   .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 6)) {
    return (xTimeline(d.endDate) - xTimeline(d.startDate));}
    else {
      return 6
    } }))
   .attr("y", (function(d) { if (d.line == "Ostrowsky family") { return yContext(3) - (barHeight * 0.25) } else { return yContext(1) - (barHeight * 0.25) } }))
   .attr("height", barHeight/2)
   .attr("rx", 5)
   .attr("ry", 5);

opTimeline.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + hTimeline + ")")
    .call(xAxisTimeline);


opContext.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + hContext + ")")
      .call(xAxisContext);

var brushg = opContext.append("g")
      .attr("class", "x brush")
      .call(brush)
      .selectAll("rect")
      .attr("y", -6)
      .attr("height", hContext + 7);

opContext.selectAll(".e")
      .append("image")
      .attr("xlink:href",'../wp-content/themes/omarpasha/img/right-handle.png')
      .attr("width", 10)
      .attr("height", 70)
      .attr("y", -6);

opContext.selectAll(".w")
      .append("image")
      .attr("xlink:href",'../wp-content/themes/omarpasha/img/left-handle.png')
      .attr("width", 10)
      .attr("height", 70)
      .attr("x", -10)
      .attr("y", -6);

opTimeline.selectAll(".show-event")
   .on('mouseover', tip.show)
   .on('mouseout', tip.hide);

opTimeline.selectAll(".ost-event, .blackart-event")
   .on('mouseover', tip2.show)
   .on('mouseout', tip2.hide);


function resize() {
    marginContext = {top: 400, right: 18, bottom: 80, left: 0},
    w = parseInt(d3.select("#chart").style("width")) - marginTimeline.left - marginTimeline.right,
    hTimeline = parseInt(d3.select("#chart").style("height")) - marginTimeline.top - marginTimeline.bottom,
    hContext = parseInt(d3.select("#chart").style("height")) - marginContext.top - marginContext.bottom;

    var barHeight = hTimeline * 0.04;    

        xTimeline.range([0, w]),
        xContext.range([0, w]),
        yTimeline.range([hTimeline, 0]).nice(),
        yContext.range([hContext, 0]);

    svg
      .attr("width", w + marginTimeline.left + marginTimeline.right)
      .attr("height", hTimeline + marginTimeline.top + marginTimeline.bottom);

    svg.select("#clip rect")
      .attr("width", w)
      .attr("height", hTimeline);

    d3.select(".background")
      .attr("width", w);

    opTimeline
      .attr("width", w)
      .attr("height", hTimeline)
      .attr("transform", "translate(10,0)");

    opContext
      .attr("transform", "translate(10," + marginContext.top + ")");

    opTimeline.select('.x.axis')
      .attr("transform", "translate(0," + hTimeline + ")")
      .call(xAxisTimeline);

    opContext.select('.x.axis')
      .attr("transform", "translate(0," + hContext + ")")
      .call(xAxisContext);

    opTimeline.select(".show-line")
       .attr("x2",  w);

    opTimeline.select(".ost-line")
       .attr("x2",  w);

    opTimeline.select(".blackart-line")
       .attr("x2",  w);

    opContext.select(".context-show-line")
       .attr("x2",  w);

    opContext.select(".context-ost-line")
       .attr("x2",  w);

    opContext.select(".context-blackart-line")
       .attr("x2",  w);

    opTimeline.selectAll(".event")
       .attr("x", (function(d) { return xTimeline(d.startDate); }))
       .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 12)) {
        return (xTimeline(d.endDate) - xTimeline(d.startDate));}
        else {
          return 12
        } }));

    opContext.selectAll(".event")
       .attr("x", (function(d) { return xContext(d.startDate); }))
       .attr("width",  (function(d) { if ((xContext(d.endDate) - xContext(d.startDate) > 6)) {
        return (xContext(d.endDate) - xContext(d.startDate));}
        else {
          return 6
        } }));

    brush
      .x(xContext)
      .extent([0, 1])
      .on("brush", brushed);
}

d3.select(window).on('resize', resize); 
  resize();

};

function brushed() {
  xTimeline.domain(brush.empty() ? xContext.domain() : brush.extent());
  opTimeline.selectAll("rect").attr("x", (function(d) { return xTimeline(d.startDate); }))
      .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 12)) { return (xTimeline(d.endDate) - xTimeline(d.startDate));} else { return 12 } }));
  opTimeline.select(".x.axis").call(xAxisTimeline);
}

你解决了这个问题吗?我测试了链接,没有发现你的响应式画笔存在问题。 - ux.engineer
是的,我弄明白了。其实不是我自己弄明白的-我找到了一位JavaScript知识比我更好的人,他们解决了这个问题。这实际上非常简单-在调整大小函数的开头声明一个变量以捕获调整大小开始时程度的状态。你还应该知道,这是D3 v3,v4中刷子程度的处理方法已经改变了。感谢您提醒我回答这个问题。 - tgerard
是的,我后来注意到这确实是v3版本,而且在v4中许多功能发生了很大变化。但是我已经快完成D3 v4本地响应式图表了。虽然当D3FC更成熟时,我会在以后的阶段进行重构。 - ux.engineer
1个回答

1

我曾找一个Stack Overflow外部的人帮我解决这个问题。解决方案很简单 - 在调整大小函数的开始处捕捉刷子范围的状态。没有其他改变。所以现在调整大小函数看起来像这样(仍然相当冗长,但可以工作):

function resize() {
    var extent = brush.extent();

    w = parseInt(d3.select("#chart").style("width")) - marginTimeline.left - marginTimeline.right,
    hTimeline = parseInt(d3.select("#chart").style("height")) - marginTimeline.top - marginTimeline.bottom;

    var barHeight = hTimeline * 0.04;    

        xTimeline.range([0, w]),
        xContext.range([0, w]),
        yTimeline.range([hTimeline, 0]).nice(),
        yContext.range([hContext, 0]);


    svg
      .attr("width", w + marginTimeline.left + marginTimeline.right);

    svg.select("#clip rect")
      .attr("width", w);

    opTimeline
      .attr("width", w)
      .attr("transform", "translate(10,0)");

    opContext
      .attr("transform", "translate(10," + marginContext.top + ")");

    opTimeline.select('.x.axis')
      .attr("transform", "translate(0," + hTimeline + ")")
      .call(xAxisTimeline);

    opContext.select('.x.axis')
      .attr("transform", "translate(0," + hContext + ")")
      .call(xAxisContext);

    opTimeline.select(".show-line")
       .attr("x2",  w);

    opTimeline.select(".ost-line")
       .attr("x2",  w);

    opTimeline.select(".blackart-line")
       .attr("x2",  w);

    opContext.select(".context-show-line")
       .attr("x2",  w);

    opContext.select(".context-ost-line")
       .attr("x2",  w);

    opContext.select(".context-blackart-line")
       .attr("x2",  w);

    opTimeline.selectAll(".event")
       .attr("x", (function(d) { return xTimeline(d.startDate); }))
       .attr("width",  (function(d) { if ((xTimeline(d.endDate) - xTimeline(d.startDate) > 12)) {
        return (xTimeline(d.endDate) - xTimeline(d.startDate));}
        else {
          return 12
        } }));

    opContext.selectAll(".event")
       .attr("x", (function(d) { return xContext(d.startDate); }))
       .attr("width",  (function(d) { if ((xContext(d.endDate) - xContext(d.startDate) > 6)) {
        return (xContext(d.endDate) - xContext(d.startDate));}
        else {
          return 6
        } }));

  brush.extent(extent);
  // Now just call the methods to update the brush.
  opContext.select("g.x.brush").call(brush);
  brushed();


}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接