如何在d3散点图图表上剪切趋势线?

时间:2017-05-03 20:22:59

标签: javascript d3.js sap sapui5 scatter-plot

我有一个散点图,根据提供的数据显示趋势线/平均线。唯一的问题是趋势线渗入y轴标签(见图)。

这是我的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?

1 个答案:

答案 0 :(得分: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(' '))