放大和放大时如何显示全文缩小时截断它

时间:2016-12-02 12:11:54

标签: javascript css d3.js

我正在用d3.js创建一个树形图表,它运行正常......但我希望文本对缩放作出反应,这是JSFiddle

请查看第一个节点......它有很多字符(在我的情况下,最大值为255)

放大或缩小时,我的文字保持不变,但我希望全部放大。



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>
&#13;
&#13;
&#13;

3 个答案:

答案 0 :(得分:15)

我在此fiddle

中提供了您的要求样本

可能需要进行一些调整才能将文本定位在垂直中间;但这可以成为您工作的基础。计算在函数wrap()中完成,并在页面加载和缩放时调用。

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);
  });
}

另请注意,我已将样式dominant-baseline: hanging;添加到.person

答案 1 :(得分:7)

this jsfiddle中的代码试图解决您在非常大的树形图表中遇到的性能问题。在缩放事件处理程序中使用setTimeout设置延迟,以允许以全速&#34;进行缩放,而无需调整文本大小。一旦缩放停止一小段时间,文本将根据新的缩放重新排列:

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);
    })

延迟(以毫秒为单位)取决于树中的节点数。您可以尝试使用数学表达式来查找树中所需的各种节点计数的最佳参数。

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

您还可以在calcFontSize中设置参数以满足您的需求:

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

节点的初始化略有修改:

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;
    });

文本在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);
    });
}

答案 2 :(得分:2)

如果我们有大量文本,文本换行可能是过程密集型的。为了解决my first answer中出现的问题,由于预渲染,此new version提高了性能。

此脚本在DOM之外创建一个元素,并将所有节点和边存储在其中。然后它会检查哪些元素是可见的,从DOM中删除它们,并在适当的时候添加它们。

我正在使用jQuery data()来选择元素。在我的小提琴例子中,有120个节点。但它应该可以更多地工作,因为渲染的唯一节点是屏幕上的节点。

我改变了缩放行为,以便缩放以鼠标光标为中心,并且惊讶地发现平移/缩放也适用于iOS。

See it in action.

<强>更新

我应用了超时(ConnorsFan的解决方案),因为它有很大的不同。另外,我添加了一个应该重新包装文本的最小比例。

$(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);
});
相关问题