【问题标题】:Implement an SVG mask on a RECT with D3 (in Javascript)?使用 D3(在 Javascript 中)在 RECT 上实现 SVG 掩码?
【发布时间】:2015-02-18 20:48:15
【问题描述】:

我正在尝试在 D3 中实现一个 SVG 掩码,类似于这个非常简单的jsfiddle example,但我一定在翻译中丢失了一些东西。我的实现都发生在一个呈现图形的类中。我正在尝试应用掩码来定义图形的边界,这样当数据超过这些边界时,图形就会被整齐地剪裁。当我应用蒙版时,图表的条形完全消失。据我所知,面具在正确的位置。帮助!

这是我在 init() 函数中定义掩码的地方:

    // Add an SVG element with the desired dimensions and margin.
    this.graph = d3.select(this.config.id).append("svg:svg")
                        .attr("width", this.width + this.m[1] + this.m[3])
                        .attr("height", this.height + this.m[0] + this.m[2])                            
                    .append("svg:g")
                        .attr("transform", "translate(" + this.m[3] + "," + this.m[0] + ")");

    var maskWidth  = 640;
    var maskHeight = 321;

    this.graph.append('svg:defs')      <------  I START DEFINING IT HERE !
        .call(function (defs) {
          // Appending the mask
          defs.append('svg:mask')
            .attr('id', 'mask')
            .attr('width', maskWidth)
            .attr('height', maskHeight)
            .attr('x', 0)
            .attr('y', 0)
            .call(function(mask) {
              mask.append('svg:rect')
                .attr('width', maskWidth)
                .attr('height', maskHeight)
                .attr('fill', '#ffffff')
            });
        });

这是在我尝试应用掩码的图表上绘制条形的方法(请参见最后一行):

addBars: function (data){
                var numberOfBars = Math.floor(this.xMaximum);
                var barWidth = this.width/numberOfBars;

                // Generate a histogram using twenty uniformly-spaced bins.
                var histogramData = d3.layout.histogram()
                    .bins(this.xScale.ticks(numberOfBars))
                    (data);    

                //console.trace('typeof: '+typeof this.xScale);
                var xScale = this.xScale;
                var yScale = this.yScale;
                var height = this.height;

                this.bars = this.graph.selectAll("bar")
                        .data(histogramData, function(d){ return d;})
                    .enter()
                    .append("rect")
                        .attr("class","bar")
                        .attr("fill","steelblue")
                        .attr("transform", function(d, i) { 
                            var yOffset = height;
                            return "translate(" + (i * barWidth - barWidth/2) + ","+yOffset+")";
                            })
                        .attr("y", function(d,i) { 
                            var yPosition = yScale(d.length)- height;
                            return (yScale(d.length)-height); 
                            })
                        .attr("height", function(d) { 
                            return height - yScale(d.length);
                            })
                        .attr("width", barWidth - 1)
                        .attr('mask', 'url(#mask)');   <---- OVER HERE  !!!!
    },

这是 Chrome 开发人员工具中生成的 HTML 的链接(我已经突出显示了 &lt;defs&gt; 和应该被屏蔽的图形栏之一):Chrome Developer Tools Dynamic HTML

据我所知,一切看起来都不错。这使我相信蒙版与栏未对齐,导致栏不可见。但是,在开发人员工具中,当我将鼠标悬停在 &lt;rect&gt; 元素上时,它会将其显示为覆盖图形条,因此这似乎不是对齐问题。任何帮助将不胜感激。

最后,我为我的应用程序中使用的类制作了一个 jsfiddle(请参阅 cmets 的链接。)。下面也是绘制图形的整个类,以防万一在上下文中查看代码会有所帮助:

// HistogramGrapher class - constructor
var HistogramGrapher = function() {

    // assign default properties
    this.config = {
        id: "",
        xAxisLabel: "xAxis",
        yAxisLabel: "yAxis",
        width: 1000,
        height: 400,
        title: "Title",
        mean: 20
    };

    // define variables
    this.m = [40, 80, 40, 80]; // margins
    this.width; // width
    this.height; // height
    this.xAxisLabel;
    this.yAxisLabel;
    this.graph;
    this.bars; 
    this.lines;
    this.xScale;
    this.xScaleInvert;
    this.xAxis;
    this.yScale;
    this.yScaleInvert;
    this.yAxis;
    this.yMaximum = 25;
    this.xMaximum = 2 * this.config.mean;
}


// methods for this class
HistogramGrapher.prototype = {

    init: function (options) {
        // copy properties of `options` to `config`. Will overwrite existing ones.
        for(var prop in options) {
            if(options.hasOwnProperty(prop)){
                this.config[prop] = options[prop];
            }
        }

        // update variables
        this.updateWidth(this.config.width);
        this.updateHeight(this.config.height);
        this.updateXMaximum(this.config.mean);

        // X scale will fit all values from datay[] within pixels 0-w
        this.xScale = d3.scale.linear()
                        .domain([0, this.xMaximum])
                        .range([0, this.width]);

        this.xScaleInvert = d3.scale.linear()
                        .range([0, this.xMaximum])
                        .domain([0, this.width]);

        // Y scale 
        this.yScale = d3.scale.linear()
                        .domain([0, this.yMaximum])
                        .range([this.height,0]);

        this.yScaleInvert = d3.scale.linear()
                        .range([0, this.yMaximum])
                        .domain([this.height,0]);


        // Add an SVG element with the desired dimensions and margin.
        this.graph = d3.select(this.config.id).append("svg:svg")
                            .attr("width", this.width + this.m[1] + this.m[3])
                            .attr("height", this.height + this.m[0] + this.m[2])                            
                        .append("svg:g")
                            .attr("transform", "translate(" + this.m[3] + "," + this.m[0] + ")");

        var maskWidth  = 640;
        var maskHeight = 321;

        this.graph.append('svg:defs')
            .call(function (defs) {
              // Appending the mask
              defs.append('svg:mask')
                .attr('id', 'mask')
                .attr('width', maskWidth)
                .attr('height', maskHeight)
                .attr('x', 0)
                .attr('y', 0)
                .call(function(mask) {
                  mask.append('svg:rect')
                    .attr('width', maskWidth)
                    .attr('height', maskHeight)
                    .attr('fill', '#ffffff')
                });
            });



        // create xAxis
        this.xAxis = d3.svg.axis().scale(this.xScale)
            .tickSize(-this.height)
            .tickSubdivide(true);

        // create yAxis
        this.yAxis = d3.svg.axis().scale(this.yScale)
            .tickSize(-this.width)
            .tickSubdivide(true)
            .orient("left");

        // Add the x-axis label.
        this.graph.append("text")
            .attr("class", "x label")
            .attr("text-anchor", "end")
            .attr("x", this.width)
            .attr("y", this.height + 25)
            .text(this.config.xAxisLabel);

        // Add the y-axis label.
        this.graph.append("text")
            .attr("class", "y label")
            .attr("text-anchor", "end")
            .attr("y", -30)
            .attr("dy", ".75em")
            .attr("transform", "rotate(-90)")
            .text(this.config.yAxisLabel);

        // add Title
        this.graph.append("text")
            .attr("x", this.width/2 )             
            .attr("y", -20  )               
            .attr("text-anchor", "middle")  
            .style("font-size", "12px")                 
            .text(this.config.title);

        // Add the x-axis.
        this.graph.append("svg:g")
              .attr("class", "x axis")
              .attr("transform", "translate(0," + this.height + ")")
              .call(this.xAxis);

        // Add the y-axis.
        this.graph.append("svg:g")
              .attr("class", "y axis")
              .call(this.yAxis);        
    },      

    updateWidth: function(width){
            this.width = width - this.m[1] - this.m[3];
    },

    updateHeight: function(height){
            this.height = height - this.m[0] - this.m[2]; // height 
    },  

    updateXMaximum: function(mean){
        this.xMaximum = 2.5 * mean;
    },

    addBars: function (data){
                var numberOfBars = Math.floor(this.xMaximum);
                var barWidth = this.width/numberOfBars;

                // Generate a histogram using twenty uniformly-spaced bins.
                var histogramData = d3.layout.histogram()
                    .bins(this.xScale.ticks(numberOfBars))
                    (data);    

                //console.trace('typeof: '+typeof this.xScale);
                var xScale = this.xScale;
                var yScale = this.yScale;
                var height = this.height;

                this.bars = this.graph.selectAll("bar")
                        .data(histogramData, function(d){ return d;})
                    .enter()
                    .append("rect")
                        .attr("class","bar")
                        .attr("fill","steelblue")
                        .attr("transform", function(d, i) { 
                            var yOffset = height;
                            return "translate(" + (i * barWidth - barWidth/2) + ","+yOffset+")";
                            })
                        .attr("y", function(d,i) { 
                            var yPosition = yScale(d.length)- height;
                            return (yScale(d.length)-height); 
                            })
                        .attr("height", function(d) { 
                            return height - yScale(d.length);
                            })
                        .attr("width", barWidth - 1)
                        .attr('mask', 'url(#mask)');
    },

    addLine: function (data){  // the data must be in the form " [ {'x':x1, 'y':y1} , {'x':x2, 'y':y2} , {'x':x3, 'y':y3} ... ]
        var xScale = this.xScale;
        var yScale = this.yScale;
        var height = this.height;

        // create a line function that can convert data[] into x and y points
        var lineFunction = d3.svg.line()
            // assign the X function to plot our line as we wish
            .x(function(d) { return xScale(d.x); })
            .y(function(d) { return yScale(d.y); })
            .interpolate("linear");

        this.lines = this.graph.append("path")
                        .attr("d", lineFunction(data))
                        .attr("class", "line")
                        .attr("stroke", "green")
                        .attr("stroke-width", 2)
                        .attr("fill","none");

    },

    clear: function () {
        var bars = d3.selectAll(".bar").remove();
        var lines = d3.selectAll(".line").remove();
    },

    getxScale: function () {
        return this.xScale;
    },

    getxScaleInvert: function () {
        return this.xScaleInvert;
    }
}

【问题讨论】:

  • 一个 jsfiddle 在这种情况下会很有帮助,因为代码不是那么苗条:)
  • @tomtomtom 您想看看 jsfiddle 中使用的图形类吗?看到图形与掩码一起消失了吗?
  • 是的,能够看到实际发生的事情肯定会有所帮助
  • @tomtomtom 我非常感谢您想要深入了解完整代码 - 我将其添加到 new jsfiddle。请参阅第 191 行了解应用蒙版的位置。有很多东西要消化,所以如果您有问题,请提出,我会澄清或修改代码以简化它。谢谢!
  • @tomtomtom 我应该澄清一下...在 jsfiddle 中,掩码当前已被注释掉,并且会出现条形。如果您取消注释蒙版以应用它,这些条将消失。多次运行模拟后,条形最终将高于图形区域。那是我想要剪掉它们的时候。

标签: javascript html css svg d3.js


【解决方案1】:

好的,我看到了发生了什么。您应该通过将剪贴蒙版附加到图形区域来将剪贴蒙版应用于条形和线条:

//clipping mask
yourSvg.append("clipPath")
    .attr("id", "chart-area")
    .append("rect")
    .attr("x", yourXcoordinates)
    .attr("y", yourYcoordinates)
    .attr("width", 333) //this was the width provided by the webinspector 
    .attr("height", 649) //this was the height provided by the webinspector;

然后,当您绘制线条和条形图时,将其添加到两个生成器中

.attr("clip-path", "url(#chart-area)")

这应该会给你你正在寻找的剪辑。基本上它所做的就是剪掉该矩形区域之外的所有东西,所以如果你正确绘制它,它应该会剪掉不需要的东西

【讨论】:

  • 成功了一半!它适用于在线,但不适用于酒吧。你能看一下this jsfiddle 中的条形图(第 199 行)吗?对于条形图,首先我创建条形图:this.bars = this.graph.selectAll("bar").data(foo) .enter() 然后附加条形图并添加掩码 .append("rect").attr("clip-path", "url(#mask)") 但有些地方不对。
  • 这很奇怪,我看到您可以隐藏线条或条形,但不能同时隐藏两者。由于我有点迷失在您的代码中,我建议您执行以下操作:设置一个“g”元素,为其提供剪辑路径,并在其中绘制元素,包括线条和矩形,这它应该工作的方式!我相信正因为如此,即使使用图形程序,您也不能使用相同的掩码来掩盖多个项目,除非它们被分组,并且由于我们正在使用 svg,因此该原则也应该适用
猜你喜欢
  • 2019-03-10
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多