响应式 d3 刷牙

Responsive d3 brushing

本文关键字:刷牙 d3 响应      更新时间:2023-09-26

我有一个基于Mike Bostock的"通过刷牙的焦点+上下文"的d3时间表,我正在尝试使其响应。

我已经能够用大部分来实现这一点,但我正在为刷子的范围而苦苦挣扎。作为一种解决方法,我尝试仅将其设置为上下文的新宽度,但它的行为非常不稳定。我尝试过的其他所有方法似乎都没有效果——范围矩形不会改变宽度。

我需要一种方法来查找范围矩形的 x 和宽度,并在调整大小时将它们应用于我的 x 比例(名为 xContext)。这里有一个"工作"版本,完整的代码如下。调整大小功能位于底部。

提前非常感谢。

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);
}

我让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();

}