2016-12-02 7 views
16

Tworzę drzewo wykresu z d3.js, działa dobrze ... ale chcę, aby tekst reagował na powiększanie, Oto JSFiddle.Jak wyświetlić pełny tekst podczas powiększania i przycinania go podczas pomniejszania

Proszę spojrzeć na pierwszym węźle ... ma wiele znaków (w moim przypadku będzie max 255)

Po powiększeniu lub na zewnątrz, mój tekst pozostaje taka sama, ale chcę zobaczyć wszystko na zoom w .

var json = { 
 
    "name": "Maude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude Charlotte Licia FernandezMaude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude asdlkhkjh asd asdsd", 
 
    "id": "06ada7cd-3078-54bc-bb87-72e9d6f38abf", 
 
    "_parents": [{ 
 
    "name": "Janie Clayton Norton", 
 
    "id": "a39bfa73-6617-5e8e-9470-d26b68787e52", 
 
    "_parents": [{ 
 
     "name": "Pearl Cannon", 
 
     "id": "fc956046-a5c3-502f-b853-d669804d428f", 
 
     "_parents": [{ 
 
     "name": "Augusta Miller", 
 
     "id": "fa5b0c07-9000-5475-a90e-b76af7693a57" 
 
     }, { 
 
     "name": "Clayton Welch", 
 
     "id": "3194517d-1151-502e-a3b6-d1ae8234c647" 
 
     }] 
 
    }, { 
 
     "name": "Nell Morton", 
 
     "id": "06c7b0cb-cd21-53be-81bd-9b088af96904", 
 
     "_parents": [{ 
 
     "name": "Lelia Alexa Hernandez", 
 
     "id": "667d2bb6-c26e-5881-9bdc-7ac9805f96c2" 
 
     }, { 
 
     "name": "Randy Welch", 
 
     "id": "104039bb-d353-54a9-a4f2-09fda08b58bb" 
 
     }] 
 
    }] 
 
    }, { 
 
    "name": "Helen Donald Alvarado", 
 
    "id": "522266d2-f01a-5ec0-9977-622e4cb054c0", 
 
    "_parents": [{ 
 
     "name": "Gussie Glover", 
 
     "id": "da430aa2-f438-51ed-ae47-2d9f76f8d831", 
 
     "_parents": [{ 
 
     "name": "Mina Freeman", 
 
     "id": "d384197e-2e1e-5fb2-987b-d90a5cdc3c15" 
 
     }, { 
 
     "name": "Charlotte Ahelandro Martin", 
 
     "id": "ea01728f-e542-53a6-acd0-6f43805c31a3" 
 
     }] 
 
    }, { 
 
     "name": "Jesus Christ Pierce", 
 
     "id": "bfd1612c-b90d-5975-824c-49ecf62b3d5f", 
 
     "_parents": [{ 
 
     "name": "Donald Freeman Cox", 
 
     "id": "4f910be4-b827-50be-b783-6ba3249f6ebc" 
 
     }, { 
 
     "name": "Alex Fernandez Gonzales", 
 
     "id": "efb2396d-478a-5cbc-b168-52e028452f3b" 
 
     }] 
 
    }] 
 
    }] 
 
}; 
 

 
var boxWidth = 250, 
 
    boxHeight = 100; 
 

 
// Setup zoom and pan 
 
var zoom = d3.behavior.zoom() 
 
    .scaleExtent([.1, 1]) 
 
    .on('zoom', function() { 
 
    svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); 
 
    }) 
 
    // Offset so that first pan and zoom does not jump back to the origin 
 
    .translate([600, 600]); 
 

 
var svg = d3.select("body").append("svg") 
 
    .attr('width', 1000) 
 
    .attr('height', 500) 
 
    .call(zoom) 
 
    .append('g') 
 
    // Left padding of tree so that the whole root node is on the screen. 
 
    // TODO: find a better way 
 
    .attr("transform", "translate(150,200)"); 
 

 
var tree = d3.layout.tree() 
 
    // Using nodeSize we are able to control 
 
    // the separation between nodes. If we used 
 
    // the size parameter instead then d3 would 
 
    // calculate the separation dynamically to fill 
 
    // the available space. 
 
    .nodeSize([100, 200]) 
 
    // By default, cousins are drawn further apart than siblings. 
 
    // By returning the same value in all cases, we draw cousins 
 
    // the same distance apart as siblings. 
 
    .separation(function() { 
 
    return .9; 
 
    }) 
 
    // Tell d3 what the child nodes are. Remember, we're drawing 
 
    // a tree so the ancestors are child nodes. 
 
    .children(function(person) { 
 
    return person._parents; 
 
    }); 
 

 
var nodes = tree.nodes(json), 
 
    links = tree.links(nodes); 
 

 
// Style links (edges) 
 
svg.selectAll("path.link") 
 
    .data(links) 
 
    .enter().append("path") 
 
    .attr("class", "link") 
 
    .attr("d", elbow); 
 

 
// Style nodes  
 
var node = svg.selectAll("g.person") 
 
    .data(nodes) 
 
    .enter().append("g") 
 
    .attr("class", "person") 
 
    .attr("transform", function(d) { 
 
    return "translate(" + d.y + "," + d.x + ")"; 
 
    }); 
 

 
// Draw the rectangle person boxes 
 
node.append("rect") 
 
    .attr({ 
 
    x: -(boxWidth/2), 
 
    y: -(boxHeight/2), 
 
    width: boxWidth, 
 
    height: boxHeight 
 
    }); 
 

 
// Draw the person's name and position it inside the box 
 
node.append("text") 
 
    .attr("text-anchor", "start") 
 
    .attr('class', 'name') 
 
    .text(function(d) { 
 
    return d.name; 
 
    }); 
 

 
// Text wrap on all nodes using d3plus. By default there is not any left or 
 
// right padding. To add padding we would need to draw another rectangle, 
 
// inside of the rectangle with the border, that represents the area we would 
 
// like the text to be contained in. 
 
d3.selectAll("text").each(function(d, i) { 
 
    d3plus.textwrap() 
 
    .container(d3.select(this)) 
 
    .valign("middle") 
 
    .draw(); 
 
}); 
 

 

 
/** 
 
* Custom path function that creates straight connecting lines. 
 
*/ 
 
function elbow(d) { 
 
    return "M" + d.source.y + "," + d.source.x + "H" + (d.source.y + (d.target.y - d.source.y)/2) + "V" + d.target.x + "H" + d.target.y; 
 
}
body { 
 
    text-align: center; 
 
} 
 
svg { 
 
    margin-top: 32px; 
 
    border: 1px solid #aaa; 
 
} 
 
.person rect { 
 
    fill: #fff; 
 
    stroke: steelblue; 
 
    stroke-width: 1px; 
 
} 
 
.person { 
 
    font: 14px sans-serif; 
 
} 
 
.link { 
 
    fill: none; 
 
    stroke: #ccc; 
 
    stroke-width: 1.5px; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3plus/1.8.0/d3plus.min.js"></script>

+1

nadal nie jest jasne, co chcesz osiągnąć :( –

+0

@pritishvaidya Co dokładnie jest mylące :) – Mathematics

+0

chcesz, aby tekst nie został obcięty przy powiększaniu? ponieważ przy d3 widoczny rozmiar czcionki zmienia się podczas powiększania, czy spodziewasz się, że tekst znajdzie się poza polem? – softwarenewbie7331

Odpowiedz

6

Kod w this jsfiddle jest próbą rozwiązania problemów z wydajnością, które masz z bardzo dużych wykresów drzew. Opóźnienie jest ustawiane za pomocą setTimeout w procedurze obsługi zdarzeń zbliżenia, aby umożliwić powiększanie przy "pełnej szybkości" bez zmiany rozmiaru tekstu. Gdy zoom zatrzymuje się na krótki czas, tekst jest uporządkowane według nowej skalowania:

var scaleValue = 1; 
var refreshTimeout; 
var refreshDelay = 0; 

var zoom = d3.behavior.zoom() 
    .scaleExtent([.1, 1.5]) 
    .on('zoom', function() { 
     svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); 
     scaleValue = d3.event.scale; 
     if (refreshTimeout) { 
      clearTimeout(refreshTimeout); 
     } 
     refreshTimeout = setTimeout(function() { 
      wrapText(); 
     }, refreshDelay); 
    }) 

Opóźnienie (w milisekundach) zależy od liczby węzłów w drzewie. Można eksperymentować z wyrażeniem matematycznym, aby znaleźć najlepsze parametry dla szeroka gama węźle liczy, że można się spodziewać w swoim drzewie.

// Calculate the refresh delay 
refreshDelay = Math.pow(node.size(), 0.5) * 2.0; 

Można również ustawić parametry w calcFontSize do swoich potrzeb:

// Calculate the font size for the current scaling 
var calcFontSize = function() { 
    return Math.min(24, 10 * Math.pow(scaleValue, -0.25)) 
} 

inicjalizacji węzłów został nieznacznie zmodyfikowany:

node.append("rect") 
    .attr({ 
     x: 0, 
     y: -(boxHeight/2), 
     width: boxWidth, 
     height: boxHeight 
    }); 

node.append("text") 
    .attr("text-anchor", "start") 
    .attr("dominant-baseline", "middle") 
    .attr('class', 'name') 
    .text(function (d) { 
     return d.name; 
    }); 

a tekst jest przetwarzany wrapText:

// Adjust the font size to the zoom level and wrap the text in the container 
var wrapText = function() { 
    d3.selectAll("text").each(function (d, i) { 
     var $text = d3.select(this); 
     if (!$text.attr("data-original-text")) { 
      // Save original text in custom attribute 
      $text.attr("data-original-text", $text.text()); 
     } 
     var content = $text.attr("data-original-text"); 
     var tokens = content.split(/(\s)/g); 
     var strCurrent = ""; 
     var strToken = ""; 
     var box; 
     var lineHeight; 
     var padding = 4; 
     $text.text("").attr("font-size", calcFontSize()); 
     var $tspan = $text.append("tspan").attr("x", padding).attr("dy", 0); 
     while (tokens.length > 0) { 
      strToken = tokens.shift(); 
      $tspan.text((strCurrent + strToken).trim()); 
      box = $text.node().getBBox(); 
      if (!lineHeight) { 
       lineHeight = box.height; 
      } 
      if (box.width > boxWidth - 2 * padding) { 
       $tspan.text(strCurrent.trim()); 
       if (box.height + lineHeight < boxHeight) { 
        strCurrent = strToken; 
        $tspan = $text.append("tspan").attr("x", padding).attr("dy", lineHeight).text(strCurrent.trim()); 
       } else { 
        break; 
       } 
      } 
      else { 
       strCurrent += strToken; 
      } 
     } 
     $text.attr("y", -(box.height - lineHeight)/2); 
    }); 
} 
+0

Zmodyfikowałem kod, aby poprawić wydajność i zmniejszyć odświeżanie opóźnienie. – ConnorsFan

15

zrobiłam próbkę swojego obowiązku w tym fiddle

I t może wymagać trochę więcej poprawek, aby umieścić tekst w pionie w środku; ale to może być podstawa do pracy. Obliczenia wykonywane są w funkcji wrap() i wywoływania podczas ładowania strony i powiększania.

function wrap() { 
    var texts = d3.selectAll("text"), 
    lineHeight = 1.1, // ems 
    padding = 2, // px 
    fSize = scale > 1 ? fontSize/scale : fontSize, 
    // find how many lines can be included 
    lines = Math.floor((boxHeight - (2 * padding))/(lineHeight * fSize)) || 1; 
    texts.each(function(d, i) { 
    var text = d3.select(this), 
     words = d.name.split(/\s+/).reverse(), 
     word, 
     line = [], 
     lineNumber = 0, 
     tspan = text.text(null).append("tspan").attr("dy", "-0.5em").style("font-size", fSize + "px"); 
    while ((word = words.pop())) { 
     line.push(word); 
     tspan.text(line.join(" ")); 
     // check if the added word can fit in the box 
     if ((tspan.node().getComputedTextLength() + (2 * padding)) > boxWidth) { 
     // remove current word from line 
     line.pop(); 
     tspan.text(line.join(" ")); 
     lineNumber++; 
     // check if a new line can be placed 
     if (lineNumber > lines) { 
      // left align text of last line 
      tspan.attr("x", (tspan.node().getComputedTextLength() - boxWidth)/2 + padding); 
      --lineNumber; 
      break; 
     } 
     // create new line 
     tspan.text(line.join(" ")); 
     line = [word]; // place the current word in new line 
     tspan = text.append("tspan") 
      .style("font-size", fSize + "px") 
      .attr("dy", "1em") 
      .text(word); 
     } 
     // left align text 
     tspan.attr("x", (tspan.node().getComputedTextLength() - boxWidth)/2 + padding); 
    } 
    // align vertically inside the box 
    text.attr("text-anchor", "middle").attr("y", padding - (lineHeight * fSize * lineNumber)/2); 
    }); 
} 

Należy również pamiętać, że Dodałem styl dominant-baseline: hanging; do .person klasa

+0

Dziękuję za odpowiedź ... wygląda na to, czego potrzebuję, ale to sprawiło, że jest bardzo kłopotliwa i ciężko z nim pracować :( – Mathematics

+3

Jakie są główne błędy zauważone za pomocą funkcji wrap? Może mógłbym pomóc w naprawieniu tych błędów? –

+0

Jednym z głównych problemów jest to, że ekran zajmuje zbyt dużo czasu, aby zareagować na powiększanie w porównaniu z wcześniejszym. Powód jest oczywisty, ponieważ jest powolny, ale obawiam się, że spełni on moje wymagania: ( – Mathematics

2

Zawijanie tekstu może być intensywny proces, jeśli mamy dużo tekstu. Aby rozwiązać te problemy, występujące w wersji my first answer, poprawiono wydajność dzięki wstępnemu renderowaniu.

Ten skrypt tworzy element znajdujący się poza DOM i przechowuje w nim wszystkie węzły i krawędzie. Następnie sprawdza, które elementy będą widoczne, usuwając je z DOM i dodając je, jeśli jest to konieczne.

Korzystam z jQuery dla data() i do wybierania elementów. W moim przykładzie na skrzypcach jest 120 węzłów. Ale powinno działać podobnie do znacznie więcej, ponieważ jedynymi renderowanymi węzłami są te na ekranie.

Zmieniłem zachowanie powiększenia, dzięki czemu obiektyw jest na środku kursora myszy i był zaskoczony, że pan/zoom działa na iOS, jak również.

See it in action.

UPDATE

Zgłosiłem timeout (roztwór ConnorsFan'S), jak to robi dużą różnicę. Dodatkowo dodałem minimalną skalę, dla której tekst powinien być ponownie zawinięty.

$(function() { 

    var viewport_width = $(window).width(), 
     viewport_height = $(window).height(), 
     node_width = 120, 
     node_height = 60, 
     separation_width = 100, 
     separation_height = 55, 
     node_separation = 0.78, 
     font_size = 20, 
     refresh_delay = 200, 
     refresh_timeout, 

     zoom_extent = [0.5, 1.15], 

     // Element outside DOM, to calculate pre-render 
     buffer = $("<div>"); 

    // Parse "transform" attribute 
    function parse_transform(input_string) { 
     var transformations = {}, 
      matches, seek; 
     for (matches in input_string = input_string.match(/(\w+)\(([^,)]+),?([^)]+)?\)/gi)) { 
      seek = input_string[matches].match(/[\w.\-]+/g), transformations[seek.shift()] = seek; 
     } 
     return transformations; 
    } 

    // Adapted from ConnorsFan's answer 
    function get_font_size(scale) { 
     fs = ~~Math.min(font_size, 15 * Math.pow(scale, -0.25)); 
     fs = ~~(((font_size/scale) + fs)/2) 
     return [fs, fs] 
    } 

    // Use d3plus to wrap the text 
    function wrap_text(scale) { 
     if (scale > 0.75) { 
      $("svg > g > g").each(function(a, b) { 
       f = $(b); 
       $("text", f) 
        .text(f.data("text")); 
      }); 
      d3.selectAll("text").each(function(a, b) { 
       d3_el = d3.select(this); 

       d3plus.textwrap() 
        .container(d3_el) 
        .align("center") 
        .valign("middle") 
        .width(node_width) 
        .height(node_height) 
        .valign("middle") 
        .resize(!0) 
        .size(get_font_size(scale)) 
        .draw(); 
      }); 
     } 
    } 

    // Handle pre-render (remove elements that leave viewport, add them back when appropriate) 
    function pre_render() { 
     buffer.children("*") 
      .each(function(i, el) { 
       d3.transform(d3.select(el).attr("transform")); 
       var el_path = $(el)[0], 
        svg_wrapper = $("svg"), 
        t = parse_transform($("svg > g")[0].getAttribute("transform")), 

        element_data = $(el_path).data("coords"), 

        element_min_x = ~~element_data.min_x, 
        element_max_x = ~~element_data.max_x, 
        element_min_y = ~~element_data.min_y, 
        element_max_y = ~~element_data.max_y, 

        svg_wrapper_width = svg_wrapper.width(), 
        svg_wrapper_height = svg_wrapper.height(), 

        s = parseFloat(t.scale), 
        x = ~~t.translate[0], 
        y = ~~t.translate[1]; 

       if (element_min_x * s + x <= svg_wrapper_width && 
        element_min_y * s + y <= svg_wrapper_height && 
        0 <= element_max_x * s + x && 
        0 <= element_max_y * s + y) { 

        if (0 == $("#" + $(el).prop("id")).length) { 

         if (("n" == $(el).prop("id").charAt(0))) { 
          // insert nodes above edges 
          $(el).clone(1).appendTo($("svg > g")); 
          wrap_text(scale = t.scale); 
         } else { 
          // insert edges 
          $(el).clone(1).prependTo($("svg > g")); 
         } 
        } 
       } else { 

        id = $(el).prop("id"); 
        $("#" + id).remove(); 
       } 
      }); 
    } 
    d3.scale.category20(); 
    var link = d3.select("body") 
     .append("svg") 
     .attr("width", viewport_width) 
     .attr("height", viewport_height) 
     .attr("pointer-events", "all") 
     .append("svg:g") 
     .call(d3.behavior.zoom().scaleExtent(zoom_extent)), 
     layout_tree = d3.layout.tree() 
     .nodeSize([separation_height * 2, separation_width * 2]) 
     .separation(function() { 
      return node_separation; 
     }) 
     .children(function(a) { 
      return a._parents; 
     }), 
     nodes = layout_tree.nodes(json), 
     edges = layout_tree.links(nodes); 

    // Style links (edges) 
    link.selectAll("path.link") 
     .data(edges) 
     .enter() 
     .append("path") 
     .attr("class", "link") 
     .attr("d", function(a) { 
      return "M" + a.source.y + "," + a.source.x + "H" + ~~(a.source.y + (a.target.y - a.source.y)/2) + "V" + a.target.x + "H" + a.target.y; 
     }); 

    // Style nodes 
    var node = link.selectAll("g.person") 
     .data(nodes) 
     .enter() 
     .append("g") 
     .attr("transform", function(a) { 
      return "translate(" + a.y + "," + a.x + ")"; 
     }) 
     .attr("class", "person"); 

    // Draw the rectangle person boxes 
    node.append("rect") 
     .attr({ 
      x: -(node_width/2), 
      y: -(node_height/2), 
      width: node_width, 
      height: node_height 
     }); 

    // Draw the person's name and position it inside the box 
    node_text = node.append("text") 
     .attr("text-anchor", "start") 
     .text(function(a) { 
      return a.name; 
     }); 

    // Text wrap on all nodes using d3plus. By default there is not any left or 
    // right padding. To add padding we would need to draw another rectangle, 
    // inside of the rectangle with the border, that represents the area we would 
    // like the text to be contained in. 
    d3.selectAll("text") 
     .each(function(a, b) { 
      d3plus.textwrap() 
       .container(d3.select(this)) 
       .valign("middle") 
       .resize(!0) 
       .size(get_font_size(1)) 
       .draw(); 
     }); 

    // START Create off-screen render 

    // Append node edges to memory, to allow pre-rendering 
    $("svg > g > path") 
     .each(function(a, b) { 
      el = $(b)[0]; 
      if (d = $(el) 
       .attr("d")) { 
       // Parse d parameter from rect, in the format found in the d3 tree dom: M0,0H0V0V0 
       for (var g = d.match(/([MLQTCSAZVH])([^MLQTCSAZVH]*)/gi), c = g.length, h, k, f, l, m = [], e = [], n = 0; n < c; n++) { 
        command = g[n], void 0 !== command && ("M" == command.charAt(0) ? (coords = command.substring(1, command.length), m.push(~~coords.split(",")[0]), e.push(~~coords.split(",")[1])) : "V" == command.charAt(0) ? e.push(~~command.substring(1, command.length)) : "H" == command.charAt(0) && m.push(~~command.substring(1, command.length))); 
       } 
       0 < m.length && (h = Math.min.apply(this, m), f = Math.max.apply(this, m)); 
       0 < e.length && (k = Math.min.apply(this, e), l = Math.max.apply(this, e)); 
       $(el).data("position", a); 
       $(el).prop("id", "e" + a); 
       $(el).data("coords", { 
        min_x: h, 
        min_y: k, 
        max_x: f, 
        max_y: l 
       }); 
       // Store element coords in memory 
       hidden_element = $(el).clone(1); 
       buffer.append(hidden_element); 
      } 
     }); 

    // Append node elements to memory 
    $("svg > g > g").each(function(a, b) { 
     el = $("rect", b); 
     transform = b.getAttribute("transform"); 
     null !== transform && void 0 !== transform ? (t = parse_transform(transform), tx = ~~t.translate[0], ty = ~~t.translate[1]) : ty = tx = 0; 
     // Calculate element area 
     el_min_x = ~~el.attr("x"); 
     el_min_y = ~~el.attr("y"); 
     el_max_x = ~~el.attr("x") + ~~el.attr("width"); 
     el_max_y = ~~el.attr("y") + ~~el.attr("height"); 
     $(b).data("position", a); 
     $(b).prop("id", "n" + a); 
     $(b).data("coords", { 
      min_x: el_min_x + tx, 
      min_y: el_min_y + ty, 
      max_x: el_max_x + tx, 
      max_y: el_max_y + ty 
     }); 
     text_el = $("text", $(b)); 
     0 < text_el.length && $(b).data("text", d3.select(text_el[0])[0][0].__data__.name); 

     // Store element coords in memory 
     hidden_element = $(b).clone(1); 
     // store node in memory 
     buffer.append(hidden_element); 
    }); 

    // END Create off-screen render 

    d3_svg = d3.select("svg"); 
    svg_group = d3.select("svg > g"); 

    // Setup zoom and pan 
    zoom = d3.behavior.zoom() 
     .on("zoom", function() { 
      previous_transform = $("svg > g")[0].getAttribute("transform"); 
      svg_group.style("stroke-width", 1.5/d3.event.scale + "px"); 
      svg_group.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); 
      pre_render(); 

      if (previous_transform !== null) { 
       previous_transform = parse_transform(previous_transform); 
       if (previous_transform.scale != d3.event.scale) { 

        // ConnorsFan's solution 
        if (refresh_timeout) { 
         clearTimeout(refresh_timeout); 
        } 
        scale = d3.event.scale; 
        refresh_timeout = setTimeout(function() { 
         wrap_text(scale = scale); 
        }, refresh_delay, scale); 

       } 
      } 
     }); 
    // Apply initial zoom/pan 
    d3_svg.call(zoom); 
}); 
+0

dziękuję za wysiłek, ale wygląda na zbyt powolny w użyciu: * (, +1 mimo wszystko ... ale jeśli mógłbyś poprawić wydajność, bo będę w stanie zwiększyć do 50 - 10 000 węzłów w przyszłości ... – Mathematics

+0

@Mathematics Widzę! W jaki sposób mógłbym dostać się do niektórych przykładowych danych z kilkoma tysiącami węzłów? Byłby to dobry sposób dla mnie, aby zobaczyć, czy mogę to wypracować.Możesz może umieścić go na pastebin.com? Dzięki! –

+1

@ Mathematics nie martw się o dostarczenie danych! Udało mi się stworzyć przykładowy zestaw danych i zobaczyć problemy! Dam ci znać, jeśli wymyślę sposób na poprawę wydajności –

Powiązane problemy