Javascript 在D3中缩小时,如何在时间刻度中聚集图标?

Javascript 在D3中缩小时,如何在时间刻度中聚集图标?,javascript,html,css,d3.js,cluster-analysis,Javascript,Html,Css,D3.js,Cluster Analysis,我有一个D3(使用D3版本3.5.2)时间刻度图,此时仅使用x轴。该图基于日期数组在x轴上绘制一系列svg“rect”元素,同时将y轴上的数据点随机化,以防止出现聚类问题。图表还具有缩放和平移功能 我想做的是缩小比例后,如下所示: 将所有图标聚集在一起,以便在用户缩小时根据时间线图特定垂直部分的“事件或数据点的集合重叠量”缩小时显示新图标 例如,如果2018年5月有七个数据点聚集在一起,那么它将显示一个图标,图标内显示特定垂直时间片(或2018年5月)的事件数,因此在这种情况下,数字七将出现在聚

我有一个D3(使用D3版本3.5.2)时间刻度图,此时仅使用x轴。该图基于日期数组在x轴上绘制一系列svg“rect”元素,同时将y轴上的数据点随机化,以防止出现聚类问题。图表还具有缩放和平移功能

我想做的是缩小比例后,如下所示:

将所有图标聚集在一起,以便在用户缩小时根据时间线图特定垂直部分的“事件或数据点的集合重叠量”缩小时显示新图标

例如,如果2018年5月有七个数据点聚集在一起,那么它将显示一个图标,图标内显示特定垂直时间片(或2018年5月)的事件数,因此在这种情况下,数字七将出现在聚集图标框中。放大到2018年5月,将导致“聚集图标”消失,并显示整个时间范围内实际显示的七个单独的“rect”元素(星期二、23日、星期四、25日等)

这里棘手的部分是获取特定日期的缩小垂直部分的实际元素数量,并在用户缩小时呈现聚集图标,而在用户放大时则相反(隐藏聚集图标并在时间线上呈现单个图标)

这是我当前的代码:

//D3时间刻度演示
常数宽度=1200,
高度=500,
parsedDate=d3.time.format(“%Y-%m-%d”).parse;
常数更改日期=[
'1988-01-01', '1988-01-02', '1988-01-03',
'1988-01-04', '1988-01-05', '1988-01-06',
'1988-01-07', '1989-01-08', '1989-01-09',
'1995-01-10', '1995-01-11', '1998-01-12',
'1998-01-13', '1998-01-14', '2002-01-15',
'2002-01-16', '2002-01-17', '2004-01-18',
'2004-01-19', '2004-01-20', '2004-01-21',
'2004-01-22', '2007-01-23', '2007-01-24',
'2007-01-25', '2007-01-26', '2007-01-27',
'2008-01-28', '2008-01-29', '2008-01-30',
'2008-01-31', '2008-02-01', '2010-02-02',
'2010-02-03', '2010-02-04', '2012-02-05',
'2012-02-06', '2012-02-07', '2012-02-08',
'2014-02-09', '2014-02-10', '2014-02-11',
'2017-02-12', '2017-02-13', '2017-02-14',
'2018-02-15', '2018-02-16', '2018-02-17',
'2018-02-18', '2018-02-19', '2018-02-20'
].map(d=>parsedDate(d));
const svg=d3。选择(“#时间刻度”)
.append('svg')
.attr('preserveAspectRatio','xMinYMin-meet')
.attr('viewBox','0 0${width}${height}`)
.classed('svg-content',true);
//.attr('width',width)
//.attr('高度'),高度;
const clipPath=svg.append('defs')
.append('clipPath'))
.attr('id','clip')
.append('rect')
.attr('宽度',宽度-110)
.attr('高度'),高度;
const xScale=d3.time.scale()
.domain([新日期(Date.parse(d3.min(changedDates,d=>d))),新日期(Date.parse(d3.max(changedDates,d=>d)))]
.范围([10,宽度-110]);
常数yScale=d3.scale.linear()
.domain([200,0])
.范围([0,高度-29]);
const xAxis=d3.svg.axis()
.scale(xScale)
.尺寸(1)
.orient(“底部”);
const yAxis=d3.svg.axis()
.刻度(yScale)
.尺寸(1)
.tickValue([0100200])
.东方(右);
const zoom=d3.behavior.zoom()
.on('zoom',函数(){
select('g.xaxis')。call(xaxis)。selectAll('text')。style('font-size','10px');
updateEvents();
}).x(xScale);
//绘制要在其上进行交互的基础区域
const rect=svg.append('rect')
.attr('x',0)
.attr('y',0)
.attr('宽度',宽度-100)
.attr('height',height)
.attr('opacity',0)
.呼叫(缩放);
append('g')
.attr('class','xaxis')
.attr('transform','translate('+10+','+480+'))
.呼叫(xAxis)
.selectAll('text')
.style('font-size','10px');
append('g')
.attr('class','yaxis')
.attr('transform','translate('+1100+','+10+'))
.呼叫(yAxis)
.selectAll('text')
.style('font-size','10px');
const renderEvents=日期=>{
const events=svg.selectAll('rect')。数据(日期);
events.enter()
.append('rect')
.attr('类','项')
.attr('x',d=>xScale(d))
.attr('y',()=>Math.random()*100)
.attr('宽度',10)
.attr('height',10)
.attr('transform',(d,i)=>(i==changedDates.length-1)?'translate('+0+','+362+'):'translate('+10+','+362+'))
.attr('clip-path','url(#clip'))
.样式(“填充”、“蓝色”);
events.exit()
.remove();
}
常量更新事件=()=>{
//这里的控制台日志是为了尝试并计算出不同数量的反转x比例值,以尝试并破译元素数量的模式
//需要显示在群集图标框中。
svg.selectAll('rect.item').attr('x',d=>xScale(d)).classed('deleteIcon',d=>{console.log('text d:',Math.floor(xScale(d));});
log(`chart上的元素:${svg.selectAll('rect.item').size()}`);
}
渲染(更改日期)
.svg容器{
显示:内联块;
位置:相对位置;
宽度:100%;
垫底:100%;
垂直对齐:顶部;
溢出:隐藏;
顶部:20px;
}
.svg内容{
显示:内联块;
位置:绝对位置;
排名:0;
左:0;
}

D3时间刻度介绍

我已经实现了一个基于

它在解析期间为日期提供了一个固定的y坐标。 它计算日期的月份组。分组后,组的键是以毫秒为单位的时间,因此使用时必须首先将其转换为日期:
new date().setTime(parseInt(d.key))

基于X轴上的月份间隔,它是d
<!DOCTYPE html>
<meta charset="utf-8">
<style>

.axis path {
  display: none;
}

.axis line {
  stroke-opacity: 0.3;
  shape-rendering: crispEdges;
}

.view {
  fill: none;
  stroke:none;
}

button {
  position: absolute;
  top: 20px;
  left: 20px;
}

</style>
<button>Reset</button>
<div id="timescale" class="svg-container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>

//D3 Timescale demo

const width = 1200,
    height = 500,
    parsedDate = d3.utcParse("%Y-%m-%d");

const changedDates = [
    '1988-01-01', '1988-01-02', '1988-01-03',
    '1988-01-04', '1988-01-05', '1988-01-06',
    '1988-01-07', '1989-01-08', '1989-01-09',
    '1995-01-10', '1995-01-11', '1998-01-12',
    '1998-01-13', '1998-01-14', '2002-01-15',
    '2002-01-16', '2002-01-17', '2004-01-18',
    '2004-01-19', '2004-01-20', '2004-01-21',
    '2004-01-22', '2007-01-23', '2007-01-24',
    '2007-01-25', '2007-01-26', '2007-01-27',
    '2008-01-28', '2008-01-29', '2008-01-30',
    '2008-01-31', '2008-02-01', '2010-02-02',
    '2010-02-03', '2010-02-04', '2012-02-05',
    '2012-02-06', '2012-02-07', '2012-02-08',
    '2014-02-09', '2014-02-10', '2014-02-11',
    '2017-02-12', '2017-02-13', '2017-02-14',
    '2018-02-15', '2018-02-16', '2018-02-17',
    '2018-02-18', '2018-02-19', '2018-02-20'
].map(d => { return { date: parsedDate(d), y: Math.random() * 100 + 50 }; });

const svg = d3.select('#timescale')
        .append('svg')
            .attr('preserveAspectRatio', 'xMinYMin meet')
            .attr('viewBox', `0 0 ${width} ${height}`)
            .classed('svg-content', true);
        // .attr('width', width)
        // .attr('height', height);

const clipPath = svg.append('defs')
    .append('clipPath')
    .attr('id', 'clip')
    .append('rect')
    .attr('width', width - 110)
    .attr('height', height);

var minDate = d3.min(changedDates, d => d.date);
var maxDate = d3.max(changedDates, d => d.date);
minDate.setUTCFullYear(minDate.getUTCFullYear()-1);
maxDate.setUTCFullYear(maxDate.getUTCFullYear()+1);

const xScale = d3.scaleTime()
    .domain([minDate, maxDate])
    .range([10, width - 110]);

const yScale = d3.scaleLinear()
    .domain([200, 0])
    .range([0, height - 29]);

const xAxis = d3.axisBottom()
    .scale(xScale)
    .tickSize(1);

const yAxis = d3.axisRight()
    .scale(yScale)
    .tickSize(1)
    .tickValues([0, 100, 200]);

var view = svg.append("rect")
    .attr("class", "view")
    .attr("x", 0.5)
    .attr("y", 0.5)
    .attr("width", width - 109)
    .attr("height", height - 28);

var gX = svg.append('g')
    .attr('class', 'xaxis')
    .attr('transform', `translate(10,${height-20})`)
    .call(xAxis);
    // .selectAll('text')
    // .style('font-size', '10px');

svg.append('g')
    .attr('class', 'yaxis')
    .attr('transform', 'translate(' + 1100 + ',' + 10 + ')')
    .call(yAxis);
    // .selectAll('text')
    // .style('font-size', '10px');

var zoom = d3.zoom()
    .scaleExtent([1, 100])
//    .translateExtent([[-100, -100], [width + 90, height + 100]])
    .on("zoom", zoomed);

d3.select("button")
    .on("click", resetted);

svg.call(zoom);

var points = svg.append("g")
    .attr('class', 'points');

var monthCount = d3.nest()
    .key(function(d) { return Date.UTC(d.date.getUTCFullYear(), d.date.getUTCMonth(), 1); })
    .rollup(function(v) { return { count: v.length, y: Math.random() * 100 + 50 }; })
    .entries(changedDates);

function drawDates(dates, xScale) {
  var points = svg.select(".points");
  points.selectAll(".item").remove();
  // use domain to group the dates
  var minDate = xScale.domain()[0];
  var minDatep1m = new Date(minDate.getTime()+30*24*60*60*1000); // + 1 month
  var deltaX = xScale(minDatep1m) - xScale(minDate);
  if (deltaX > 20) {
    points.selectAll('.item')
      .data(dates)
      .enter()
      .append('rect')
      .attr('class', 'item')
      .attr('x', d => xScale(d.date))
      .attr('y', d => d.y)
      .attr('width', 10)
      .attr('height', 10)
      .attr('clip-path', 'url(#clip)')
      .style('fill', 'blue');
  } else {
    var groups = points.selectAll('.item')
      .data(monthCount)
      .enter()
      .append('g')
      .attr('class', 'item')
      .attr('transform', d => `translate(${xScale(new Date().setTime(parseInt(d.key)))},${d.value.y})`);
    groups.append('rect')
      .attr('x', -5)
      .attr('y', -5)
      .attr('width', 10)
      .attr('height', 10)
      .attr('clip-path', 'url(#clip)')
      .style('fill', 'red');
    groups.append('text')
      .text(d => d.value.count);
  }
}

drawDates(changedDates, xScale);

function zoomed() {
  var transformedX = d3.event.transform.rescaleX(xScale);
  gX.call(xAxis.scale(transformedX));
  drawDates(changedDates, transformedX)
}

function resetted() {
  svg.transition()
      .duration(750)
      .call(zoom.transform, d3.zoomIdentity);
}

</script>