d3.js中没有固定间隔时可以进行画笔捕捉吗?

问题描述 投票:0回答:1

我在 d3 中有一个使用焦点/上下文的条形图。当用户刷选日期时,我想捕捉到可用的日期,而不是他们能够刷到开始和结束之间的任何日期。 此画笔捕捉示例 使用十二小时间隔。就我而言,没有固定的间隔。有没有办法用数据捕捉日期?

const margin = {
        top: 20,
        right: 20,
        bottom: 90,
        left: 50
    },
        margin2 = {
            top: 230,
            right: 20,
            bottom: 30,
            left: 50
        },
        width = 960 - margin.left - margin.right,
        height = 300 - margin.top - margin.bottom,
        height2 = 300 - margin2.top - margin2.bottom;

    const parseTime = d3.timeParse("%Y-%m-%d %H:%M");

    const x = d3.scaleTime().range([0, width]),
        x2 = d3.scaleTime().range([0, width]),
        y = d3.scaleLinear().range([height, 0]),
        y2 = d3.scaleLinear().range([height2, 0]),
        dur = d3.scaleLinear().range([0, 12]);

    const xAxis = d3.axisBottom(x).tickSize(0),
        xAxis2 = d3.axisBottom(x2).tickSize(0),
        yAxis = d3.axisLeft(y).tickSize(0);

    const brush = d3.brushX()
        .extent([
            [0, 0],
            [width, height2]
        ])
        .on("start brush end", brushed);

    const svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom);

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

    const focus = svg.append("g")
        .attr("class", "focus")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    const context = svg.append("g")
        .attr("class", "context")
        .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");

    d3.csv("https://raw.githubusercontent.com/sprucegoose1/sample-data/main/data.csv").then((data) => {

        const parseTime = d3.timeParse("%Y-%m-%d %H:%M");
        const mouseoverTime = d3.timeFormat("%a %e %b %Y %H:%M");
        const minTime = d3.timeFormat("%b%e, %Y");
        const parseDate = d3.timeParse("%b %Y");

        data.forEach((d) => {
            d.date = parseTime(d.date);
            d.end = parseTime(d.end);
            d.distance = +d.distance;
            return d;
        },
            (error, data) => {
                if (error) throw error;
            })

        let total = 0;

        data.forEach((d) => total = d.distance + total);

        const minDate = d3.min(data, d => d.date)

        const xMin = d3.min(data, d => d.date)

        const yMax = Math.max(20, d3.max(data, d => d.distance))

        x.domain([xMin, d3.max(data, d => d.date)])
        y.domain([0, yMax]);
        x2.domain(x.domain());
        y2.domain(y.domain());

        var rects = focus.append("g");
        rects.attr("clip-path", "url(#clip)");
        rects.selectAll("rects")
            .data(data)
            .enter().append("rect")
            .style("fill","royalblue")
            .attr("class", "rects")
            .attr("x", d => x(d.date))
            .attr("y", d => y(d.distance))
            .attr("width", 10)
            .attr("height", d => height - y(d.distance))

        focus.append("g")
            .attr("class", "axis x-axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis);

        focus.append("g")
            .attr("class", "axis axis--y")
            .call(yAxis);

        focus.append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", 0 - margin.left)
            .attr("x", 0 - (height / 2))
            .attr("dy", "1em")
            .style("text-anchor", "middle")
            .text("Distance in meters");

        svg.append("text")
            .attr("transform",
                "translate(" + ((width + margin.right + margin.left) / 2) + " ," +
                (height + margin.top + margin.bottom) + ")")
            .style("text-anchor", "middle")
            .text("Date");

        var rects = context.append("g");
        rects.attr("clip-path", "url(#clip)");
        rects.selectAll("rects")
            .data(data)
            .enter().append("rect")
            .style("fill", "royalblue")
            .attr("class", "rects")
            .attr("x", d => x2(d.date))
            .attr("y", d => y2(d.distance))
            .attr("width", 10)
            .attr("height", d => height2 - y2(d.distance));

        context.append("g")
            .attr("class", "axis x-axis")
            .attr("transform", "translate(0," + height2 + ")")
            .call(xAxis2);

        context.append("g")
            .attr("class", "brush")
            .call(brush)
            .call(brush.move, x.range());

    });

    function brushed(event) {
        var s = event.selection || x2.range();
        x.domain(s.map(x2.invert, x2));
        focus.selectAll(".rects")
            .attr("x", d => x(d.date))
            .attr("y", d => y(d.distance))
            .attr("width", 10)
            .attr("height", d => height - y(d.distance))

        focus.select(".x-axis").call(xAxis);

        var e = event.selection;
        var selectedrects = focus.selectAll('.rects').filter(() => {
            var xValue = this.getAttribute('x');
            return e[0] <= xValue && xValue <= e[1];
        });
    }
    body {
        font-family: avenir next, sans-serif;
        font-size: 12px;
    }

    .axis {
        stroke-width: 0.5px;
        stroke: #888;
        font: 10px avenir next, sans-serif;
    }

    .axis>path {
        stroke: #888;
    }

    .handle {
        width: 6px !important;
        fill: #000 !important;
        margin-left: 0px !important;
        display: block;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<div id="totalDistance">
    </div>

javascript d3.js brush snapping
1个回答
0
投票

与我的上一个答案类似,但在这个版本中,在拖动事件期间,它会找到数据集中最接近两个拖动手柄的日期并捕捉到它们。请注意,我没有完全完善它(它没有重新绘制“焦点”栏):

<!DOCTYPE html>

<html>
  <head>
    <style>
      body {
        font-family: avenir next, sans-serif;
        font-size: 12px;
      }

      .axis {
        stroke-width: 0.5px;
        stroke: #888;
        font: 10px avenir next, sans-serif;
      }

      .axis > path {
        stroke: #888;
      }

      .handle {
        width: 6px !important;
        fill: #000 !important;
        margin-left: 0px !important;
        display: block;
      }
    </style>
  </head>

  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
    <div id="totalDistance"></div>

    <script>
      const margin = {
          top: 20,
          right: 20,
          bottom: 90,
          left: 50,
        },
        margin2 = {
          top: 230,
          right: 20,
          bottom: 30,
          left: 50,
        },
        width = 960 - margin.left - margin.right,
        height = 300 - margin.top - margin.bottom,
        height2 = 300 - margin2.top - margin2.bottom;

      const parseTime = d3.timeParse('%Y-%m-%d %H:%M');

      const x = d3.scaleTime().range([0, width]),
        x2 = d3.scaleTime().range([0, width]),
        y = d3.scaleLinear().range([height, 0]),
        y2 = d3.scaleLinear().range([height2, 0]),
        dur = d3.scaleLinear().range([0, 12]);

      const xAxis = d3.axisBottom(x).tickSize(0),
        xAxis2 = d3.axisBottom(x2).tickSize(0),
        yAxis = d3.axisLeft(y).tickSize(0);

      const brush = d3
        .brushX()
        .extent([
          [0, 0],
          [width, height2],
        ])
        .on('end', brushed)
        .on('brush', brushing);

      const svg = d3
        .select('body')
        .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom);

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

      const focus = svg
        .append('g')
        .attr('class', 'focus')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

      const context = svg
        .append('g')
        .attr('class', 'context')
        .attr(
          'transform',
          'translate(' + margin2.left + ',' + margin2.top + ')'
        );

      let data;
      d3.csv(
        'https://raw.githubusercontent.com/sprucegoose1/sample-data/main/data.csv'
      ).then((d) => {
        data = d;

        const parseTime = d3.timeParse('%Y-%m-%d %H:%M');
        const mouseoverTime = d3.timeFormat('%a %e %b %Y %H:%M');
        const minTime = d3.timeFormat('%b%e, %Y');
        const parseDate = d3.timeParse('%b %Y');

        data.forEach(
          (d) => {
            d.date = parseTime(d.date);            
            d.end = parseTime(d.end);
            d.distance = +d.distance;
            return d;
          },
          (error, data) => {
            if (error) throw error;
          }
        );

        let total = 0;

        data.forEach((d) => (total = d.distance + total));

        const minDate = d3.min(data, (d) => d.date);

        const xMin = d3.min(data, (d) => d.date);

        const yMax = Math.max(
          20,
          d3.max(data, (d) => d.distance)
        );

        x.domain([xMin, d3.max(data, (d) => d.date)]);
        y.domain([0, yMax]);
        x2.domain(x.domain());
        y2.domain(y.domain());

        var rects = focus.append('g');
        rects.attr('clip-path', 'url(#clip)');
        rects
          .selectAll('rects')
          .data(data)
          .enter()
          .append('rect')
          .style('fill', 'royalblue')
          .attr('class', 'rects')
          .attr('x', (d) => x(d.date))
          .attr('y', (d) => y(d.distance))
          .attr('width', 10)
          .attr('height', (d) => height - y(d.distance));

        focus
          .append('g')
          .attr('class', 'axis x-axis')
          .attr('transform', 'translate(0,' + height + ')')
          .call(xAxis);

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

        focus
          .append('text')
          .attr('transform', 'rotate(-90)')
          .attr('y', 0 - margin.left)
          .attr('x', 0 - height / 2)
          .attr('dy', '1em')
          .style('text-anchor', 'middle')
          .text('Distance in meters');

        svg
          .append('text')
          .attr(
            'transform',
            'translate(' +
              (width + margin.right + margin.left) / 2 +
              ' ,' +
              (height + margin.top + margin.bottom) +
              ')'
          )
          .style('text-anchor', 'middle')
          .text('Date');

        var rects = context.append('g');
        rects.attr('clip-path', 'url(#clip)');
        rects
          .selectAll('rects')
          .data(data)
          .enter()
          .append('rect')
          .style('fill', 'royalblue')
          .attr('class', 'rects')
          .attr('x', (d) => x2(d.date))
          .attr('y', (d) => y2(d.distance))
          .attr('width', 10)
          .attr('height', (d) => height2 - y2(d.distance));

        context
          .append('g')
          .attr('class', 'axis x-axis')
          .attr('transform', 'translate(0,' + height2 + ')')
          .call(xAxis2);

        context
          .append('g')
          .attr('class', 'brush')
          .call(brush)
          .call(brush.move, x.range());
      });

      function brushing(event) {
        if (!event.sourceEvent) return;

        const clDt = event.selection.map(x2.invert),
          snapXs = x.domain();

        // find the dates in the dataset closest to both handles
        for (let i = 1; i < data.length; i++){
          const currDate = data[i].date, 
                prevDate = data[i-1].date;
          if (Math.abs(currDate - clDt[0]) < Math.abs(prevDate - clDt[0]))
            snapXs[0] = currDate;
          if (Math.abs(currDate - clDt[1]) < Math.abs(prevDate - clDt[1]))
            snapXs[1] = currDate;
        }
        x.domain(snapXs);
        d3.select(this).call(brush.move, [x2(snapXs[0]), x2(snapXs[1])]);
      }

      function brushed(event){
          d3.select(".x-axis").call(d3.axisBottom(x))
      };
    </script>
  </body>
</html>

© www.soinside.com 2019 - 2024. All rights reserved.