2017-05-03 139 views
1

我有散点图显示基于提供的数据的趋势线/平均线。唯一的问题是趋势线流入y轴标签(见图片)。如何在d3散点图上剪切趋势线?

这是我的d3代码,如何“修剪”趋势线以仅适合图表的绘制区域?

scatterPlot.js

jQuery.sap.require("sap/ui/thirdparty/d3"); 
jQuery.sap.declare("pricingTool.ScatterPlot"); 

sap.ui.core.Element.extend("pricingTool.ScatterPlotItem", { metadata : { 
    properties : { 
     "quarter" : {type : "string", group : "Misc", defaultValue : null}, 
     "values" : {type : "object", group : "Misc", defaultValue : null} 
    } 
}});  

sap.ui.core.Control.extend("pricingTool.ScatterPlot", { 
    metadata : { 
     properties: { 
      "title": {type : "string", group : "Misc", defaultValue : "ScatterPlot Title"} 
     }, 
     aggregations : { 
      "items" : { type: "pricingTool.ScatterPlotItem", multiple : true, singularName : "item"} 
     }, 
     defaultAggregation : "items", 
     events: { 
      "onPress" : {}, 
      "onChange":{}  
     }   
    }, 

    init: function() { 
     //console.log("vizConcept.ScatterPlot.init()"); 
     this.sParentId = ""; 
    }, 

    createScatterPlot : function() { 
     //console.log("vizConcept.ScatterPlot.createScatterPlot()"); 
     var oScatterPlotLayout = new sap.m.VBox({alignItems:sap.m.FlexAlignItems.Center,justifyContent:sap.m.FlexJustifyContent.Center}); 
     var oScatterPlotFlexBox = new sap.m.FlexBox({height:"auto",alignItems:sap.m.FlexAlignItems.Center}); 
     /* ATTENTION: Important 
     * This is where the magic happens: we need a handle for our SVG to attach to. We can get this using .getIdForLabel() 
     * Check this in the 'Elements' section of the Chrome Devtools: 
     * By creating the layout and the Flexbox, we create elements specific for this control, and SAPUI5 takes care of 
     * ID naming. With this ID, we can append an SVG tag inside the FlexBox 
     */ 

     this.sParentId=oScatterPlotFlexBox.getIdForLabel(); 
     oScatterPlotLayout.addItem(oScatterPlotFlexBox); 

     return oScatterPlotLayout; 
    }, 


    /** 
    * The renderer render calls all the functions which are necessary to create the control, 
    * then it call the renderer of the vertical layout 
    * @param oRm {RenderManager} 
    * @param oControl {Control} 
    */ 
    renderer: function(oRm, oControl) { 
     var layout = oControl.createScatterPlot(); 

     oRm.write("<div"); 
     oRm.writeControlData(layout); // writes the Control ID and enables event handling - important! 
     oRm.writeClasses(); // there is no class to write, but this enables 
     // support for ColorBoxContainer.addStyleClass(...) 

     oRm.write(">"); 
     oRm.renderControl(layout); 
     oRm.addClass('verticalAlignment'); 
     oRm.write("</div>"); 
    }, 

    onAfterRendering: function(){ 
     //console.log("vizConcept.ScatterPlot.onAfterRendering()"); 
     //console.log(this.sParentId); 
     var cItems = this.getItems(); 
     var data = []; 
     for (var i=0;i<cItems.length;i++){ 
      var oEntry = {}; 
      for (var j in cItems[i].mProperties) { 
       oEntry[j]=cItems[i].mProperties[j]; 
      } 
      data.push(oEntry); 
     } 
     $("svg").last().remove(); 

     /* 
     * ATTENTION: See .createScatterPlot() 
     * Here we're picking up a handle to the "parent" FlexBox with the ID we got in .createScatterPlot() 
     * Now simply .append SVG elements as desired 
     * EVERYTHING BELOW THIS IS PURE D3.js 
     */ 

     var margin = { 
       top: 25, 
       right: 30, 
       bottom: 80, 
       left: 90 
      }, 
      width = 600 - margin.left - margin.right, 
       height = 300 - margin.top - margin.bottom; 

       var tableData = data[0].values; 
       var dates = []; 


       for(var i = 0; i<tableData.length; i++){ 
        dates[i] = new Date(tableData[i].date); 
        dates.sort(function(a,b) { 
         return a -b; 
        }) 
       } 

       var minDate = dates[0], 
        maxDate = dates[dates.length-1]; 

      //test// 
      var year = new Date(dates[0]); 
      minDate.setMonth(year.getMonth(), -2); 

      var year = new Date(dates[dates.length-1]); 
      console.log(year); 
      //maxDate.setMonth(year.getMonth(), 6); 
      console.log(maxDate); 

      //end test// 


      // Our X scale 
      //var x = d3.scale.linear() 
      var x = d3.time.scale() 
       .domain([minDate, maxDate]) 
       .range([0, width]); 

      // Our Y scale 
      var y = d3.scale.linear() 
       .range([height, 0]); 

      // Our color bands 
      var color = d3.scale.ordinal() 
       .range(["#000", "#004460", "#0070A0", "#008BC6", "#009DE0", "#45B5E5", "8CCDE9", "#DAEBF2"]); //"#00A6ED", 

      // Use our X scale to set a bottom axis 
      var xAxis = d3.svg.axis() 
       .scale(x) 
       .orient("bottom") 
       .ticks(8) 
       .tickFormat(d3.time.format("%b-%Y")); 

      // Same for our left axis 
      var yAxis = d3.svg.axis() 
       .scale(y) 
       .orient("left"); 

      var tip = d3.select("body").append("div") 
       .attr("class", "sctooltip") 
       .style("position", "absolute") 
       .style("text-align", "center") 
       .style("width", "80px") 
       .style("height", "42px") 
       .style("padding", "2px") 
       .style("font", "11px sans-serif") 
       .style("background", "#F0F0FF") 
       .style("border", "0px") 
       .style("border-radius", "8px") 
       .style("pointer-events", "none") 
       .style("opacity", 0); 

     var vis = d3.select("#" + this.sParentId); 
     var svg = vis.append("svg") 
     .attr("width", width + margin.left + margin.right) 
     .attr("height", height + margin.top + margin.bottom) 
     .style("background-color","white") 
     .style("font", "12px sans-serif") 
     .append("g") 
     .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

     x.domain([minDate, maxDate]); 

     // Our Y domain is from zero to our highest total 
     y.domain([0, d3.max(data, function (d) { 
      var max = d3.max(d.values, function (dd){ 
       return(+dd.price); 
      }) 
      return max; 
     })]); 
     var totalval = 0; 
     var totalval2 = 0; 
     var values = 0; 

     data.forEach(function (d) { 
      d.values.forEach(function (dd){ 
       values +=1; 
       totalval += +dd.date; 
       totalval2 += +dd.price; 
      }); 
     }); 

     var priceAverage = totalval2/values; 

     var average = totalval/totalval2; 

     var value = data[0].values[0].price; 

     var line_data = [ 
         {"x": 0, "y": y.domain()[0]}, 
         {"x": y.domain()[1]*average, "y": y.domain()[1]} 
         ]; 


     var avgline = d3.svg.line() 
      .x(function(d){ return x(d.x); }) 
      .y(function(d){ return y(d.y); }) 
      .interpolate("linear"); 

     svg.append("g") 
      .attr("class", "x axis") 
      .style("fill", "none") 
      .style("stroke", "grey") 
      .style("shape-rendering", "crispEdges") 
      .attr("transform", "translate(0," + height + ")") 
      .call(xAxis) 
      .selectAll("text") 
      .style("text-anchor", "end") 
      .attr("dx", "-.8em") 
      .attr("dy", ".15em") 
      .attr("transform", "rotate(-25)"); 

     svg.append("g") 
      .attr("class", "y-axis") 
      .style("fill", "none") 
      .style("stroke", "grey") 
      .style("shape-rendering", "crispEdges") 
      .call(yAxis); 

     //average line 
     svg.append("path") 
      .attr("class", "avgline") 
      .style("stroke", "#000") 
      .style("stroke-width", "1px") 
      .style("stroke-dasharray", ("4, 4")) 
      .attr("d", avgline(line_data)); 

     var plot = svg.selectAll(".values") //changed this from quarter 
      .data(data) 
      .enter().append("g"); 

     plot.selectAll("dot") 
      .data(function (d) { 
      return d.values; 
     }) 
      .enter().append("circle") 
      .attr("class", "dot") 
      .attr("r", 5) 
      .attr("cx", function (d){ 
       return x(d.date); 
      }) 
      .attr("cy", function (d) { 
      return y(d.price); 
     }) 
      .style("stroke", "#004460") 
      .style("fill", function (d) { 
      return color(d.name); 
     }) 
      .style("opacity", .9) 
      .style("visibility", function(d){ 
       if(+d.date != 0){ 
        return "visible"; 
       }else{ 
        return "hidden"; 
       } 
      }) 
      .style("pointer-events", "visible") 
      .on("mouseover", function(d){ 
        tip.transition() 
         .duration(200) 
         .style("opacity", .8); 
        tip.html(d.name + "<br/>" + d.quarter + "<br />" + "Avg. " +(+d.date/+d.price).toFixed(2)) 
         .style("left", (d3.event.pageX-40) + "px") 
         .style("top", (d3.event.pageY-50) + "px"); 

       }) 
       .on("mouseout", function(d){ 
        tip.transition() 
         .duration(500) 
         .style("opacity", 0); 
       });; 

     // var legend = svg.selectAll(".legend") 
     // .data(color.domain()) 
     // .enter().append("g") 
     // .attr("class", "legend") 
     // .attr("transform", function (d, i) { 
     // return "translate(0," + i * 16 + ")"; 
     // }); 

     // legend.append("rect") 
     // .attr("x", width - 12) 
     // .attr("width", 12) 
     // .attr("height", 12) 
     // .style("fill", color); 

     // legend.append("text") 
     // .attr("x", width - 24) 
     // .attr("y", 6) 
     // .attr("dy", ".35em") 
     // .style("text-anchor", "end") 
     // .style("font", "11px sans-serif") 
     // .text(function (d) { 
     // return d; 
     // }); 


     //y-axis label 
     svg.append("text") 
     .attr("transform", "rotate(-90)") 
     .attr("x", - (height/2)) 
     .attr("y", 10 - margin.left) 
     .attr("dy", "1em") 
     .style("text-anchor", "middle") 
     .style("font", "16px sans-serif") 
     .text("PPI Cost($)"); 

     //x-axis label 
     svg.append("text") 
      .attr("transform", "translate("+(width/2) +","+ (height +margin.top +50)+")") 
      .style("text-anchor", "middle") 
      .style("font", "16px sans-serif") 
      .text("Purchase Date"); 


     var avglabel = svg.append("g") 
      .attr("transform", "translate(" + (width-40) + ",140)"); 
     avglabel.append("text") 
      .style("text-anchor", "middle") 
      .text("Average: $" + priceAverage.toFixed(2)); 
    } 
}); 

how can I trim the trendline/avgline?

+0

为什么在'line_data'的第一个对象中设置'x:0'? –

+0

只是一个猜测! –

+0

不介意我上面的评论,刚才我看到你的行生成器中实际上已经有了'x'的比例。我错过了。这就是为什么我喜欢给尺度更大的名字,比如'xScale'。 –

回答

0

尝试x值设置为图形的起点和终点:

var line_data = [ 
    {x: x.domain()[0], y: average}, 
    {x: x.domain()[1], y: average} 
]; 

另外,您可以直接设置路径字符串无需使用路径生成器:

svg.append("path") 
    .attr("class", "avgline") 
    .style("stroke", "#000") 
    .style("stroke-width", "1px") 
    .style("stroke-dasharray", ("4, 4")) 
    .attr("d", [ 
     "M", x.range()[0], y(average), 
     "H", x.range()[1] 
    ].join(' '))