D3 v4 добавьте кисть к нескольким линиям и увеличьте масштаб

#javascript #d3.js

Вопрос:

Я работаю с графиками d3 и изучаю их различные функции,я хочу иметь график с функцией масштабирования и кисти,поэтому до сих пор я могу рисовать несколько линий и всплывающие подсказки над ними, единственная оставшаяся проблема-добавление масштабирования и кисти, может ли кто-нибудь подсказать мне, как я могу добавить необходимое(масштабирование и кисть) к моему текущему графику Это текущая форма графика

Вот следующий код

     var parseDate = d3.timeParse("%Y-%m")
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]
console.log('graph')

var lineOpacity = 1
var lineStroke = "2px"

var axisPad = 6 // axis formatting
var R = 6 //legend marker

var category = ["Category A", "Category B", "Category C", "Category D", "Category E"]
// since Category B and E are really close to each other, assign them diverging colors
var color = d3.scaleOrdinal()
  .domain(category)
  .range(["#2D4057", "#7C8DA4", "#B7433D", "#2E7576", "#EE811D"])
 
let data = <any>dateDemo;
function redraw() {

  var glines
  var mouseG
  var tooltip     
  d3.select('#svg-id')
    .remove();
    d3.select('#tooltip')
    .remove();
  var chartDiv = document.getElementById('chart');

  if (!chartDiv) {
    return;
  }
  var result = (90 / 100) * chartDiv.clientHeight; //70 % height


  var margin = { top: 100, right: 60, bottom: 90, left: 30 },
    width = chartDiv.clientWidth - margin.left - margin.right,
    height = result - margin.top - margin.bottom;

    var res = data.map((d,i) => {
      return {
        date : parseDate(d.month),
        bidding_no :  d.bidding_no,
        vehicle_class : d.vehicle_class,
        premium :  d.premium
      }
    })

    var xScale = d3.scaleTime()
      .domain(<any>d3.extent(res, d=>d['date']))
      .range([0, width])

    function roundToNearest10K(x) {
      return Math.round(x / 10000) * 10000
    }

    var yScale = d3.scaleLinear()
      .domain([0, roundToNearest10K(d3.max(res, d => d['premium']))])
      .range([height, 0]);

    var svg = d3.select("#chart").append("svg")
      .attr("width", width   margin.left   margin.right)
      .attr("height", height   margin.top   margin.bottom)
      .attr('id', 'svg-id')
      .append('g')
        .attr("transform", "translate("   margin.left   ","   margin.top   ")");


    // CREATE AXES // 
    // render axis first before lines so that lines will overlay the horizontal ticks
    var xAxis = d3.axisBottom(xScale).ticks(d3.timeYear.every(1)).tickSizeOuter(axisPad*2).tickSizeInner(axisPad*2)
    var yAxis = d3.axisLeft(yScale).ticks(10, "s").tickSize(-width) //horizontal ticks across svg width

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", `translate(0, ${height})`)
      .call(xAxis)
      .call(g => {
        var years = xScale.ticks(d3.timeYear.every(1))
        var xshift = (width/(years.length))/2 
        g.selectAll("text").attr("transform", `translate(${xshift}, 0)`) //shift tick labels to middle of interval
          .style("text-anchor", "middle")
          .attr("y", axisPad)
          .attr('fill', '#A9A9A9')

        g.selectAll("line")
          .attr('stroke', '#A9A9A9')

        g.select(".domain")
          .attr('stroke', '#A9A9A9')

      })

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
      .call(g => {
        g.selectAll("text")
        .style("text-anchor", "middle")
        .attr("x", -axisPad*2)
        .attr('fill', '#A9A9A9')

        g.selectAll("line")
          .attr('stroke', '#A9A9A9')
          .attr('stroke-width', 0.7) // make horizontal tick thinner and lighter so that line paths can stand out
          .attr('opacity', 0.3)

        g.select(".domain").remove()

       })
      .append('text')
        .attr('x', 50)
        .attr("y", -10)
        .attr("fill", "#A9A9A9")
        .text("Singapore Dollars")


    // CREATE LEGEND // 
    var svgLegend = svg.append('g')
        .attr('class', 'gLegend')
        .attr("transform", "translate("   (width   20)   ","   0   ")")

    var legend = <any>svgLegend.selectAll('.legend')
      .data(category)
      .enter().append('g')
        .attr("class", "legend")
        .attr("transform", function (d, i) {return "translate(0,"   i * 20   ")"})

    legend.append("circle")
        .attr("class", "legend-node")
        .attr("cx", 0)
        .attr("cy", 0)
        .attr("r", R)
        .style("fill", d=>color(d))

    legend.append("text")
        .attr("class", "legend-text")
        .attr("x", R*2)
        .attr("y", R/2)
        .style("fill", "#A9A9A9")
        .style("font-size", 12)
        .text(d=>d)

    // line generator 
    var line = d3.line()
      .x(d => xScale(d['date']))
      .y(d => yScale(d['premium']))

    renderChart(1) // inital chart render (set default to Bidding Exercise 1 data)

    // Update chart when radio button is selected
    d3.selectAll(("input[name='bidding_no']")).on('change', function(){
      updateChart(1)
    })

    function updateChart(bidding_no) {

      var resNew = res.filter(d=>d.bidding_no == parseInt(bidding_no))

      var res_nested = d3.nest()
        .key(d=>d['vehicle_class'])
        .entries(resNew)

      glines.select('.line') //select line path within line-group (which represents a vehicle category), then bind new data 
        .data(res_nested)
        .transition().duration(750)
        .attr('d', function(d) {
          return line(d.values)
        })

      mouseG.selectAll('.mouse-per-line')
        .data(res_nested)

      mouseG.on('mousemove', function () { 
          var mouse = d3.mouse(this)
          updateTooltipContent(mouse, res_nested)
        })
    }

    function renderChart(bidding_no) {

      var resNew = res.filter(d=>d.bidding_no == parseInt(bidding_no))

      var res_nested = d3.nest() // necessary to nest data so that keys represent each vehicle category
        .key(d=>d['vehicle_class'])
        .entries(resNew)

      // APPEND MULTIPLE LINES //
      var lines = svg.append('g')
        .attr('class', 'lines')

      glines = lines.selectAll('.line-group')
        .data(res_nested).enter()
        .append('g')
        .attr('class', 'line-group')

      glines  
        .append('path')
          .attr('class', 'line')  
          .attr('d', d => line(d.values))
          .style('stroke', (d, i) => color(i))
          .style('fill', 'none')
          .style('opacity', lineOpacity)
          .style('stroke-width', lineStroke)


      // APPEND CIRCLE MARKERS //
      //var gcircle = lines.selectAll("circle-group")
        //.data(res_nested).enter()
        //.append("g")
        //.attr('class', 'circle-group')

      //gcircle.selectAll("circle")
        //.data(d => d.values).enter()
        //.append("g")
        //.attr("class", "circle")  
        //.append("circle")
        //.attr("cx", d => xScale(d.date))
        //.attr("cy", d => yScale(d.premium))
        //.attr("r", 2)

      // CREATE HOVER TOOLTIP WITH VERTICAL LINE //
      tooltip = d3.select("#chart")
        .append("div")
        .attr('id', 'tooltip')
        .style('position', 'absolute')
        .style("background-color", "#D3D3D3")
        .style('padding', 6)
        .style('display', 'none')

      mouseG = svg.append("g")
        .attr("class", "mouse-over-effects");

      mouseG.append("path") // create vertical line to follow mouse
        .attr("class", "mouse-line")
        .style("stroke", "#A9A9A9")
        .style("stroke-width", lineStroke)
        .style("opacity", "0");

      lines = <any>document.getElementsByClassName('line');

      var mousePerLine = mouseG.selectAll('.mouse-per-line')
        .data(res_nested)
        .enter()
        .append("g")
        .attr("class", "mouse-per-line");

      mousePerLine.append("circle")
        .attr("r", 4)
        .style("stroke", function (d) {
          return color(d.key)
        })
        .style("fill", "none")
        .style("stroke-width", lineStroke)
        .style("opacity", "0");

      mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
        .attr('width', width) 
        .attr('height', height)
        .attr('fill', 'none')
        .attr('pointer-events', 'all')
        .on('mouseout', function () { // on mouse out hide line, circles and text
          d3.select(".mouse-line")
            .style("opacity", "0");
          d3.selectAll(".mouse-per-line circle")
            .style("opacity", "0");
          d3.selectAll(".mouse-per-line text")
            .style("opacity", "0");
          d3.selectAll("#tooltip")
            .style('display', 'none')

        })
        .on('mouseover', function () { // on mouse in show line, circles and text
          d3.select(".mouse-line")
            .style("opacity", "1");
          d3.selectAll(".mouse-per-line circle")
            .style("opacity", "1");
          d3.selectAll("#tooltip")
            .style('display', 'block')
        })
        .on('mousemove', function () { // update tooltip content, line, circles and text when mouse moves
          var mouse = d3.mouse(this)

          d3.selectAll(".mouse-per-line")
            .attr("transform", function (d, i) {
              var xDate = xScale.invert(mouse[0]) // use 'invert' to get date corresponding to distance from mouse position relative to svg
              var bisect = d3.bisector(function (d) { return d['date']; }).left // retrieve row index of date on parsed csv
              var idx = bisect(d['values'], xDate);

              d3.select(".mouse-line")
                .attr("d", function () {
                  var data = "M"   xScale(d['values'][idx].date)   ","   (height);
                  data  = " "   xScale(d['values'][idx].date)   ","   0;
                  return data;
                });
              return "translate("   xScale(d['values'][idx].date)   ","   yScale(d['values'][idx].premium)   ")";

            });

          updateTooltipContent(mouse, res_nested)

        })

      }

  function updateTooltipContent(mouse, res_nested) {

    let sortingObj = []
    res_nested.map(d => {
      var xDate = xScale.invert(mouse[0])
      var bisect = d3.bisector(function (d) { return d['date']; }).left
      var idx = bisect(d.values, xDate)
      sortingObj.push({key: d.values[idx].vehicle_class, premium: d.values[idx].premium, bidding_no: d.values[idx].bidding_no, year: d.values[idx].date.getFullYear(), month: monthNames[d.values[idx].date.getMonth()]})
    })

    sortingObj.sort(function(x, y){
       return d3.descending(x.premium, y.premium);
    })

    var sortingArr = sortingObj.map(d=> d.key)

    var res_nested1 = res_nested.slice().sort(function(a, b){
      return sortingArr.indexOf(a.key) - sortingArr.indexOf(b.key) // rank vehicle category based on price of premium
    })
    console.log(d3.event.pageX, d3.event.pageY)
    tooltip.html(sortingObj[0].month   "-"   sortingObj[0].year   " (Bidding No:"   sortingObj[0].bidding_no   ')')
      .style('display', 'block')
      .style('left', d3.event.pageX   20    "px")
      .style('top', d3.event.pageY - 20    "px")
      .style('font-size', 100)
      .selectAll()
      .data(res_nested1).enter() // for each vehicle category, list out name and price of premium
      .append('div')
      .style('color', d => {
        return color(d.key)
      })
      .style('font-size', 100)
      .html(d => {
        var xDate = xScale.invert(mouse[0])
        var bisect = d3.bisector(function (d) { return d['date']; }).left
        var idx = bisect(d.values, xDate)
        return d.key.substring(0, 3)   " "   d.key.slice(-1)   ": $"   d.values[idx].premium.toString()
      })
      tooltip.style('left', d3.event.pageX   20)
      .style('top', d3.event.pageY - 20)
  }
 

Я хочу, чтобы вывод был таким, как показано ниже, изображение-это просто пример графика с кистью и масштабированием
, что-то вроде этого…

введите описание изображения здесь