【问题标题】:Add vertical line to d3js bar chart将垂直线添加到 d3js 条形图
【发布时间】:2017-10-19 09:54:08
【问题描述】:

基于此example,我正在尝试复制条形图。然后我有两个数据集:一个用于条形图,一个用于单个价格。

我想要对这个单一价格做的是根据条形定位它。检查代码:

var price_data =  [
                              {'priceRangeStart':'4000000','numberOfProperties':100},
                              {'priceRangeStart':'4100000','numberOfProperties':256},
                              {'priceRangeStart':'4200000','numberOfProperties':773},
                              {'priceRangeStart':'4300000','numberOfProperties':334},
                              {'priceRangeStart':'4400000','numberOfProperties':587},
                              {'priceRangeStart':'4500000','numberOfProperties':400},
                              {'priceRangeStart':'4600000','numberOfProperties':700},
                              {'priceRangeStart':'4700000','numberOfProperties':150},
                              {'priceRangeStart':'4800000','numberOfProperties':229},
                              {'priceRangeStart':'4900000','numberOfProperties':500},
                              {'priceRangeStart':'5000000','numberOfProperties':125},
                              {'priceRangeStart':'5100000','numberOfProperties':170},
                              {'priceRangeStart':'5200000','numberOfProperties':290},
                              {'priceRangeStart':'5300000','numberOfProperties':660},
                              {'priceRangeStart':'5400000','numberOfProperties':450},
                              {'priceRangeStart':'5500000','numberOfProperties':359},
                              {'priceRangeStart':'5600000','numberOfProperties':740},
                              {'priceRangeStart':'5700000','numberOfProperties':894},
                              {'priceRangeStart':'5900000','numberOfProperties':547},
                              {'priceRangeStart':'6000000','numberOfProperties':1250}
                            ];

          var property_price = 5050000;


          var loadPriceComparisonChart = function (data, single_price) {

            // removing svg/tip artifacts
            d3.selectAll('.price-comparison-tip').remove();
            d3.select(elem[0]).select('svg').remove();

            // variables and helpers
            var margin = {top: 0, right: 20, bottom: 25, left: 0},
                width = 370,
                height = 200;

            var formatComma = d3.format(',');


            // responsive SVG
            // create responsive svg container for the graph
            var svg = d3.select(elem[0])
                  .append('div')
                  .classed('svg-price-comparison-container', true)
                  .append('svg')
                  .attr('preserveAspectRatio', 'none')
                  .attr('viewBox', '0 0 370 225')
                  //class to make it responsive
                  .classed('svg-content-responsive', true);

            // creating x and y scales
            var x = d3.scaleBand().rangeRound([0, width]).paddingOuter(0.5).paddingInner(0.6),
                x1 = d3.scaleBand().rangeRound([0, width]),
                y = d3.scaleLinear().rangeRound([height, 0]),
                y1 = d3.scaleLinear().rangeRound([0, (height - 100)]); //

            // creating the group object
            var g = svg.append('g')
                .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // processing the data
            var new_dataset = data;
            var transformation = [];

            transformation = new_dataset.map(el => (
              {priceRangeStart: el.priceRangeStart, numberOfProperties: el.numberOfProperties}
            ));

            transformation.forEach(function(d) {
              d.priceRangeStart = +d.priceRangeStart;
              d.numberOfProperties = +d.numberOfProperties;
              return d;
            });

            // using lodash
            var minValue = _.first(transformation).priceRangeStart;
            var maxValue = _.last(transformation).priceRangeStart;

            // get max number of properties value
            var maxNrProperties = d3.max(transformation, function(d) {return d.numberOfProperties;});

            // x and y value domains
            x.domain(transformation.map(function(d) {console.log(d.priceRangeStart);return d.priceRangeStart;}));
            x1.domain(transformation.map(function(d) {return d.priceRangeStart;}));
            y.domain([0, ((d3.max(transformation, function(d) {return d.numberOfProperties;})) + (1.15 * maxNrProperties))]);
            y1.domain([0, (d3.max(transformation, function(d) {return d.numberOfProperties;}))]);

            // create x axis (for guiding purposes) and then hide it
            g.append('g')
                .attr('transform', 'translate(0,' + height + ')')
                .attr('class', 'axis axis--x')
                .call(d3.axisBottom(x))
                .style('opacity', 0)
                .selectAll('text').remove();

            // text for min price value and translate it to the start position of the first bar
            g.append('text')
              .attr('transform', 'translate(' + (x(_.first(transformation).priceRangeStart)) + ' ,' + (height * 1.1) + ')')
              .style('text-anchor', 'start')
              .style('font-size', '12px')
              .text('CHF ' + formatComma(minValue));

            // text for max price value and translate it to the end position of the last bar
            g.append('text')
              .attr('transform', 'translate(' + (x(_.last(transformation).priceRangeStart) + x.bandwidth()) + ' ,' + (height * 1.1) + ')')
              .style('text-anchor', 'end')
              .style('font-size', '12px')
              .text('CHF ' + formatComma(maxValue));

            // apppend the grey bars to the graph and styling them
            g.selectAll('.bar')
              .data(transformation)
              .enter().append('rect')
                .attr('class', 'bar')
                .attr('x', function(d) {return x(d.priceRangeStart);})
                .attr('y', function(d) {return height - y1(maxNrProperties);})
                .attr('width', x.bandwidth())
                .attr('height', function(d) {return y1(maxNrProperties);})
                .style('fill', '#A4A9AD')
                .style('opacity', 0.2);

            // apppend the orange bars to the graph and styling them
            g.selectAll(null)
              .data(transformation)
              .enter().append('rect')
                .attr('class', 'bar')
                .attr('x', function(d) {return x(d.priceRangeStart);})
                .attr('y', function(d) {return y(d.numberOfProperties);})
                .attr('width', x.bandwidth())
                .attr('height', function(d) {return height - y(d.numberOfProperties);})
                .style('fill', '#EDAA00')
                .style('opacity', 1);

            // append dashed line to show single property price position
            g.append('line')
              .attr('class', 'zero')
              .attr('x1', function(d) {return x1(single_price);})
              .attr('y1', function(d) {return  (y1(maxNrProperties) - 10);})
              .attr('x2', x(0))
              .attr('y2', function(d) {return height;})
              .style('stroke', '#003A5D')
              .style('stroke-width', 1)
              .style('stroke-dasharray', 6)
              .attr('transform', 'translate(30,0)'); 

如您所见,5050000 的单一价格应该位于 5000000 和 5100000 范围的柱之间。该单一价格应该是一条垂直虚线,而不是另一个柱,正如您在代码中看到的那样。问题是我无法正确定位它。由于某种原因,我无法使用我为栏设置的 x 域。

最后,它应该看起来像这样: 有什么想法吗?

【问题讨论】:

    标签: javascript d3.js graph charts


    【解决方案1】:

    当您将这些值用作分类(定性)变量时,您会使事情变得复杂。不过,有一个解决办法:

    您可以使用d3.bisectLeftx 规模域中获得低于该单一价格的值:

    var lineIndex = d3.bisectLeft(x.domain(), property_price);
    

    然后,使用该索引,获取行的x 位置:

    .attr("x1", x(x.domain()[lineIndex]))
    .attr("x2", x(x.domain()[lineIndex]))
    

    这是您的更改代码:

    var data = [{
      'priceRangeStart': '4000000',
      'numberOfProperties': 100
    }, {
      'priceRangeStart': '4100000',
      'numberOfProperties': 256
    }, {
      'priceRangeStart': '4200000',
      'numberOfProperties': 773
    }, {
      'priceRangeStart': '4300000',
      'numberOfProperties': 334
    }, {
      'priceRangeStart': '4400000',
      'numberOfProperties': 587
    }, {
      'priceRangeStart': '4500000',
      'numberOfProperties': 400
    }, {
      'priceRangeStart': '4600000',
      'numberOfProperties': 700
    }, {
      'priceRangeStart': '4700000',
      'numberOfProperties': 150
    }, {
      'priceRangeStart': '4800000',
      'numberOfProperties': 229
    }, {
      'priceRangeStart': '4900000',
      'numberOfProperties': 500
    }, {
      'priceRangeStart': '5000000',
      'numberOfProperties': 125
    }, {
      'priceRangeStart': '5100000',
      'numberOfProperties': 170
    }, {
      'priceRangeStart': '5200000',
      'numberOfProperties': 290
    }, {
      'priceRangeStart': '5300000',
      'numberOfProperties': 660
    }, {
      'priceRangeStart': '5400000',
      'numberOfProperties': 450
    }, {
      'priceRangeStart': '5500000',
      'numberOfProperties': 359
    }, {
      'priceRangeStart': '5600000',
      'numberOfProperties': 740
    }, {
      'priceRangeStart': '5700000',
      'numberOfProperties': 894
    }, {
      'priceRangeStart': '5900000',
      'numberOfProperties': 547
    }, {
      'priceRangeStart': '6000000',
      'numberOfProperties': 1250
    }];
    
    var property_price = 5050000;
    
    // variables and helpers
    var margin = {
        top: 0,
        right: 20,
        bottom: 25,
        left: 0
      },
      width = 370,
      height = 200;
    
    var formatComma = d3.format(',');
    
    
    // responsive SVG
    // create responsive svg container for the graph
    var svg = d3.select("body")
      .append('svg')
      .attr('preserveAspectRatio', 'none')
      .attr('viewBox', '0 0 370 225')
      //class to make it responsive
      .classed('svg-content-responsive', true);
    
    // creating x and y scales
    var x = d3.scaleBand().rangeRound([0, width]).paddingOuter(0.5).paddingInner(0.6),
      x1 = d3.scaleBand().rangeRound([0, width]),
      y = d3.scaleLinear().rangeRound([height, 0]),
      y1 = d3.scaleLinear().rangeRound([0, (height - 100)]); //
    
    // creating the group object
    var g = svg.append('g')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
    
    // processing the data
    var new_dataset = data;
    var transformation = [];
    
    transformation = new_dataset.map(el => ({
      priceRangeStart: el.priceRangeStart,
      numberOfProperties: el.numberOfProperties
    }));
    
    transformation.forEach(function(d) {
      d.priceRangeStart = +d.priceRangeStart;
      d.numberOfProperties = +d.numberOfProperties;
      return d;
    });
    
    // using lodash
    var minValue = _.first(transformation).priceRangeStart;
    var maxValue = _.last(transformation).priceRangeStart;
    
    // get max number of properties value
    var maxNrProperties = d3.max(transformation, function(d) {
      return d.numberOfProperties;
    });
    
    // x and y value domains
    x.domain(transformation.map(function(d) {
      return d.priceRangeStart;
    }));
    x1.domain(transformation.map(function(d) {
      return d.priceRangeStart;
    }));
    y.domain([0, ((d3.max(transformation, function(d) {
      return d.numberOfProperties;
    })) + (1.15 * maxNrProperties))]);
    y1.domain([0, (d3.max(transformation, function(d) {
      return d.numberOfProperties;
    }))]);
    
    // create x axis (for guiding purposes) and then hide it
    g.append('g')
      .attr('transform', 'translate(0,' + height + ')')
      .attr('class', 'axis axis--x')
      .call(d3.axisBottom(x))
      .style('opacity', 0)
      .selectAll('text').remove();
    
    // text for min price value and translate it to the start position of the first bar
    g.append('text')
      .attr('transform', 'translate(' + (x(_.first(transformation).priceRangeStart)) + ' ,' + (height * 1.1) + ')')
      .style('text-anchor', 'start')
      .style('font-size', '12px')
      .text('CHF ' + formatComma(minValue));
    
    // text for max price value and translate it to the end position of the last bar
    g.append('text')
      .attr('transform', 'translate(' + (x(_.last(transformation).priceRangeStart) + x.bandwidth()) + ' ,' + (height * 1.1) + ')')
      .style('text-anchor', 'end')
      .style('font-size', '12px')
      .text('CHF ' + formatComma(maxValue));
    
    // apppend the grey bars to the graph and styling them
    g.selectAll('.bar')
      .data(transformation)
      .enter().append('rect')
      .attr('class', 'bar')
      .attr('x', function(d) {
        return x(d.priceRangeStart);
      })
      .attr('y', function(d) {
        return height - y1(maxNrProperties);
      })
      .attr('width', x.bandwidth())
      .attr('height', function(d) {
        return y1(maxNrProperties);
      })
      .style('fill', '#A4A9AD')
      .style('opacity', 0.2);
    
    // apppend the orange bars to the graph and styling them
    g.selectAll(null)
      .data(transformation)
      .enter().append('rect')
      .attr('class', 'bar')
      .attr('x', function(d) {
        return x(d.priceRangeStart);
      })
      .attr('y', function(d) {
        return y(d.numberOfProperties);
      })
      .attr('width', x.bandwidth())
      .attr('height', function(d) {
        return height - y(d.numberOfProperties);
      })
      .style('fill', '#EDAA00')
      .style('opacity', 1);
    
    var lineIndex = d3.bisectLeft(x.domain(), property_price);
    
    var line = svg.append("line")
      .attr("x1", x(x.domain()[lineIndex]) - x.step() / 2 + x.bandwidth() / 2)
      .attr("x2", x(x.domain()[lineIndex]) - x.step() / 2 + x.bandwidth() / 2)
      .attr("y1", height - 100)
      .attr("y2", height)
      .style("stroke", "gray")
      .style("stroke-width", 2)
      .style("stroke-dasharray", "2,2")
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

    【讨论】:

    • 你又来了。你是某种 D3 魔术师,muito obrigado! (我来自异国情调的葡萄牙,说着难以理解的葡萄牙语:D)我不明白你提到的部分我把事情复杂化了。你能详细说明一下吗?
    • 当然:在条形图中,列表示分类变量:国家、水果、团队、人员等...但是,您使用的是定量变量来表示列。所以,这更像是一个直方图而不是条形图。 Até a proxima!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 1970-01-01
    • 2019-09-07
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    相关资源
    最近更新 更多