2015-02-12 2 views
1

Я пытаюсь реализовать SVG-маску в D3, подобно этому очень простому jsfiddle example, но я, должно быть, что-то потерял в переводе. Моя реализация выполняется в классе, который отображает график. Я пытаюсь применить маску для определения границ для графика, так что, когда данные превышают эти границы, график аккуратно обрезается. Когда я применяю маску, штрихи графика полностью исчезают. Насколько я могу сказать маску в нужном месте. ПОМОГИТЕ!Реализовать маску SVG на RECT с D3 (в Javascript)?

Вот где я определяю маску в моей 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 !!!! 
    }, 

Вот ссылка на результирующей HTML в Chrome Developer Tools (я выделил <defs> и один из столбчатых диаграмм, которые должны быть замаскированы): Chrome Developer Tools Dynamic HTML

Насколько я могу судить, все выглядит хорошо. Это приводит меня к мысли, что маска неправильно выровнена с полосой, заставляя бар быть невидимым. Тем не менее, в инструментах разработчика, когда я нависаю над элементом <rect>, он показывает, что он накладывается на панели графиков, поэтому это не похоже на проблему выравнивания. Любая помощь будет оценена по достоинству.

Наконец, я сделал jsfiddle класса, который используется в моем приложении (см. Комментарии к ссылке.). Ниже также весь класс для построения графика, только в том случае, было бы полезно, чтобы увидеть код в контексте:

// 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; 
    } 
} 
+0

jsfiddle был бы полезен в этом случае, так как код не такой тонкий :) – tomtomtom

+0

@ tomtomtom Вы хотели бы видеть, что класс графического отображения используется в jsfiddle? видеть, что граф исчезает с помощью маски? – bologna

+0

да быть в состоянии увидеть, что на самом деле происходит, несомненно, будет полезно – tomtomtom

ответ

0

Хорошо, я видел, что происходит. Вы должны применить обтравочную маску для баров и линии путем добавления обтравочную маску к области графика:

//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)") 

, и это должно дать вам обрезание, которое вы ищете. В основном то, что он делает, - это закрепить все за пределами области этого прямоугольника, поэтому, если вы построите его правильно, он должен вырезать ненужные вещи.

+0

Это половина работы! Он работает на линии, но не в барах. Не могли бы вы взглянуть на бары (строка 199) в [this jsfiddle] (http://jsfiddle.net/bologna/njp2ubd3/)? Для баров сначала создаю панель: 'this.bars = this.graph.selectAll (" bar "). Data (foo) .enter()' затем добавьте панель и добавьте маску '.append ("Rect").attr ("clip-path", "url (#mask)") 'Но что-то не так. – bologna

+0

Странно, я вижу, что вы можете либо спрятать линию, либо бары, но не оба. Так как я потерял ваш код, я предлагаю вам сделать следующее: Настройте элемент «g», дайте ему путь клипа и запишите его внутри, как прямую, так и прямоугольники , таким образом он должен работать! Я считаю, что из-за этого даже с графическими программами вы не можете скрыть несколько элементов одной маской, если они не сгруппированы, и поскольку мы работаем с svg, этот принцип должен также применяться – tomtomtom

Смежные вопросы