2016-04-28 4 views
4

Alle:ausblenden einige Teile eineine <path> erzeugen mit einer Kurve Interpolation

Ich versuche, einen benutzerdefinierten Liniengenerator wie D3 Linie() zu machen, aber mit der Fähigkeit, Liniensegmente Stil anpassen, wenn Daten fehlen (wie (gestrichelte Linie verwenden)

Eine Sache, die ich nicht umsetzen konnte, ist ihre .interpolate() - Funktion. Die Mathematik scheint kompliziert zu sein, was ich versuche zu tun, ist einfach vorhandene D3-Linien-Funktion zu verwenden, um diese fortlaufenden Segmente zu zeichnen und sie mit der gestrichelten Linie zu verbinden, aber ich kann nicht herausfinden, wie interpolierte Linie zu generieren?

Im folgenden Codebeispiel, kann u die gestrichelte Linie sehen nicht genau die durchgezogene Linie überlappen:

var data = []; 
 

 
for(var i=0; i<20; i++){ 
 
    if(i>0 && (i%4==0)){ 
 
     data.push(null); 
 
    }else { 
 
     data.push({x:i, y:Math.random(i)}) 
 
    } 
 
} 
 

 
var x = d3.scale.linear(); 
 
var y = d3.scale.linear(); 
 
x.domain([0, 19]) 
 
     .range([10, 390]) 
 
y.domain([0, 1]) 
 
     .range([10, 360]); 
 

 
var svg = d3.select("body") 
 
      .append("svg") 
 
      .attr("width", 400) 
 
      .attr("height", 400); 
 
var lg = svg.append("g") 
 
      .classed("lineGroup", true); 
 
var xg = svg.append("g") 
 
    .classed("xaxis", true) 
 
    .attr("transform", function(){ 
 
     return "translate(0, 380)"; 
 
    }); 
 
var line = d3.svg.line() 
 
      .interpolate("monotone") 
 
      .x(function(d) { return x(d.x); }) 
 
      .y(function(d) { return y(d.y); }); 
 
line.defined(function(d) { return d!=null; }); 
 

 
lg.append("path") 
 
    .classed("shadowline", true) 
 
    .attr("d", function(){ 
 
     return line(data.filter(function(d){return d!=null;})); 
 
    }) 
 
    .style("fill", "none") 
 
    .style("stroke", "steelblue") 
 
    .style("stroke-width", "3px") 
 
    .attr("stroke-dasharray", "5,5"); 
 
lg.append("path") 
 
    .attr("d", function(){ 
 
     return line(data); 
 
    }) 
 
    .style("fill", "none") 
 
    .style("stroke", "steelblue") 
 
    .style("stroke-width", "3px"); 
 
lg.selectAll("circle") 
 
    .data(data.filter(function(d){return d!=null;})) 
 
    .enter() 
 
    .append("circle") 
 
    .style("fill", "orange") 
 
    .style("stroke", "red") 
 
    .style("stroke-width", "3px") 
 
    .attr("r",5) 
 
    .attr("cx", function(d){return x(d.x);}) 
 
    .attr("cy", function(d){return y(d.y);}) 
 
var xAxis = d3.svg.axis() 
 
        .scale(x) 
 
        .orient("bottom"); 
 
xg.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Hilfe? Danke

+0

können Sie ein Beispiel einrichten .. wo ist Ihre Datenpunkte und wie die Zeile aussehen sollte.Wenn sollte es gestrichelt werden. Derzeit ist Ihre Frage sehr allgemein gehalten. – Cyril

+0

@Cyril THanks, ich habe ein konkretes Beispiel hinzugefügt, was ich möchte ist, dass diese durchgezogene Linie Kurve interpoliert, um die gestrichelte Linie zu überlappen – Kuan

Antwort

2

Ich bin mit einem seltsamen Algorithmus gekommen, um einige Teile Ihrer Linie zu verstecken, zuerst müssen Sie erkennen, dass der Interpolationsalgorithmus, den Sie gewählt haben, analyzing the previous and next points of any t between the previous and next point funktioniert, deshalb sogar, wenn Sie nur Segment des erzeugen möchten Pfad Sie den gleichen Interpolationsalgorithmus sonst die ersten/letzten Punkte werden nicht die erforderliche Kurve

in diesem Sinne mein Algorithmus zu lösen Ihr Problem verwenden müssen als

  • folgt, ist die solide machen Pfad
  • einige Segmente dieses festen Weges machen, jedoch mit einem weißen Strich, so dass es wie eine Maske wirkt
  • den gestrichelten Pfad macht

Implementation

  • zuerst den festen Pfad mit der gewünschten Interpolation rendern
  • in den Daten finden Sie die Extreme aller Lücken zgaps([0, 1, null, 3, 4, null, 5]) wird in diese Punkte umgewandelt, dies beinhaltet eine umfassende Brute-Force-Prüfung, da es keine API gibt, um die Länge vom Ursprung eines Pfades bis zu einem bestimmten Punkt, der darauf liegt, seit Ihrer Daten erhöhen sich auf x Ich bin binäre Suche, aber für den allgemeinen Fall, wie ich schon sagte, müssen Sie eine Brute-Force-Prüfung
  • machen eine Vielzahl von Proben zwischen den Lücken Endpunkte (als Weglängen gesehen) mit path. getPointAtLength und schließlich rendern ein Pfad für jede Sammlung von Punkten, ist der Trick, um einen weißen Strich
den gestrichelte Pfad
  • einzustellen machen

    HINWEIS: änderte ich die Interpolation Funktion ‚Kardinal‘, so dass Kurven viel mehr beachteten sind und Sie die Masken in Aktion

    var data = []; 
     
    
     
    for(var i=0; i<20; i++){ 
     
        if(i>0 && (i%4==0)){ 
     
         data.push(null); 
     
        }else { 
     
         data.push({x:i, y:Math.random(i)}) 
     
        } 
     
    } 
     
    
     
    var x = d3.scale.linear(); 
     
    var y = d3.scale.linear(); 
     
    x.domain([0, 19]) 
     
         .range([10, 390]) 
     
    y.domain([0, 1]) 
     
         .range([10, 360]); 
     
    
     
    var svg = d3.select("body") 
     
          .append("svg") 
     
          .attr("width", 400) 
     
          .attr("height", 400); 
     
    var lg = svg.append("g") 
     
          .classed("lineGroup", true); 
     
    var xg = svg.append("g") 
     
        .classed("xaxis", true) 
     
        .attr("transform", function(){ 
     
         return "translate(0, 380)"; 
     
        }); 
     
    var line = d3.svg.line() 
     
          .interpolate("cardinal") 
     
          .x(function(d) { return x(d.x); }) 
     
          .y(function(d) { return y(d.y); }); 
     
    
     
    function lineFiltered(data) { 
     
        return line(data.filter(function (d) { return !!d })) 
     
    } 
     
    
     
    var basePath = lg.append("path") 
     
        .attr("d", function() { return lineFiltered(data) }) 
     
        .style("fill", "none") 
     
        .style("stroke", "steelblue") 
     
        .style("stroke-width", "3px"); 
     
    
     
    function getPathLengthAtPoint(path, point, samples) { 
     
        // binary search to find the length of a path closest to point 
     
        samples = samples || 100 
     
        var lo = 0, hi = path.getTotalLength() 
     
        var res = 0 
     
        for (var i = 0; i < samples; i += 1) { 
     
        var mid = lo + (hi - lo)/2 
     
        var pMid = path.getPointAtLength(mid) 
     
        if (pMid.x < x(point.x)) { 
     
         res = lo = mid 
     
        } else { 
     
         hi = mid 
     
        } 
     
        } 
     
        return res 
     
    } 
     
    
     
    // gets endpoints from where there's a gap 
     
    // it assumes that a gap has only length 1 
     
    function getGapsEndPoints(data) { 
     
        var j = 0 
     
        var gaps = [] 
     
        for (var i = 0; i < data.length; i += 1) { 
     
        if (typeof data[i] !== 'number') { 
     
         gaps.push([data[i - 1], data[i + 1]]) 
     
        } 
     
        } 
     
        return gaps 
     
    } 
     
    
     
    // generates multiple points per path 
     
    function generatePaths(data, path, samples) { 
     
        samples = samples || 50 
     
        return data.map(function (d) { 
     
        var lo = d[0], hi = d[1] 
     
        var points = [] 
     
        for (var i = 0; i <= samples; i += 1) { 
     
         var point = path.getPointAtLength(lo + i/samples * (hi - lo)) 
     
         points.push({ 
     
         x: x.invert(point.x), 
     
         y: y.invert(point.y) 
     
         }) 
     
        } 
     
        return points 
     
        }) 
     
    } 
     
    
     
    
     
    var missingData = data.map(function (d) { 
     
        return d && getPathLengthAtPoint(basePath.node(), d) 
     
    }) 
     
    missingData = getGapsEndPoints(missingData) 
     
    missingData = generatePaths(missingData, basePath.node()) 
     
    
     
    // finally create the mask paths using the same line generator 
     
    lg.selectAll('path.mask').data(missingData) 
     
        .enter().append('path').classed('mask', true) 
     
        .attr('d', lineFiltered) 
     
        .style("fill", "none") 
     
        .style("stroke", "white") 
     
        .style("stroke-width", "3px") 
     
    
     
    lg.append("path") 
     
        .classed("shadowline", true) 
     
        .attr("d", function() { return lineFiltered(data) }) 
     
        .style("fill", "none") 
     
        .style("stroke", "steelblue") 
     
        .style("stroke-width", "3px") 
     
        .attr("stroke-dasharray", "5,5"); 
     
    
     
    lg.selectAll("circle") 
     
        .data(data.filter(function(d){return d!=null;})) 
     
        .enter() 
     
        .append("circle") 
     
        .style("fill", "orange") 
     
        .style("stroke", "red") 
     
        .style("stroke-width", "3px") 
     
        .attr("r",5) 
     
        .attr("cx", function(d){return x(d.x);}) 
     
        .attr("cy", function(d){return y(d.y);}) 
     
    var xAxis = d3.svg.axis() 
     
            .scale(x) 
     
            .orient("bottom"); 
     
    xg.call(xAxis);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

  • 0

    Hier ist mein Versuch zu sehen:

    Stellen Sie sich ein Array wie dieses:

    [1,2,3, null, 4,5, null,null, 8,9]

    sie in zwei Array Break-Gruppen

    datachunks = [[1,2,3],[4,5][8,9]

    brokenDataChunks = [[3,4][5,8]]

    nun den datachunks wie diese zeichnen:

    datachunks.forEach(function(dc){ 
        lg.append("path") 
         .attr("d", function(){ 
          return line(dc); 
         }) 
         .style("fill", "none") 
         .style("stroke", "steelblue") 
         .style("stroke-width", "3px") 
    }) 
    

    nun den brokenDataChunks wie diese zeichnen:

    brokendatachunks.forEach(function(dc){ 
        lg.append("path") 
         .classed("shadowline", true) 
         .attr("d", function(){ 
          return line(dc); 
         }) 
         .style("fill", "none") 
         .style("stroke", "red") 
         .style("stroke-width", "3px") 
         .attr("stroke-dasharray", "5,5"); 
    }) 
    

    Die größte Herausforderung war es, das Array zu erhalten in diese Weise aufgeteilt:

    var datachunks = [];//hold data chunks for the blue line connected one 
    var brokendatachunks = [];//hold data chunks for the red dashed line disconnected ones 
    var tempconnected =[]; 
    var tempbroken =[]; 
    data.forEach(function(d, i){ 
        if(d){//if not null 
        tempconnected.push(d); //push in tempconnected 
        if (tempbroken.length > 0){ 
         tempbroken.push(d);//if broken was detected before 
         brokendatachunks.push(tempbroken);//add this array into brokendatachunks. 
         tempbroken = [];//set the new value in temp broken to get new set of values 
        } 
        } else { 
        if(data[i-1]) 
         tempbroken.push(data[i-1]);//push previous value don't want to insert null here. 
        datachunks.push(tempconnected); 
        tempconnected = []; 
        } 
    }); 
    if (tempconnected.length > 0){ 
        datachunks.push(tempconnected); 
    } 
    
    if (tempbroken.length > 0) 
        brokendatachunks.push(tempbroken); 
    } 
    

    Arbeits Code here

    komplexer Fall mit vielen gebrochenen Punkten dazwischen here In komplexem Fall, dass ich wie diese

    stellte Punkt Generation habe
    for(var i=0; i<20; i++){ 
        if(i>0 && (i%4==0 || i%3 ==0)){ 
         data.push(null); 
        }else { 
         data.push({x:i, y:Math.random(i)}) 
        } 
    } 
    
    Verwandte Themen