【问题标题】:D3 force layout works in Chrome, but not in FirefoxD3 强制布局在 Chrome 中有效,但在 Firefox 中无效
【发布时间】:2015-04-17 15:19:20
【问题描述】:

我使用 D3 创建了一个强制布局(见下图)。但是,由于某种原因,它在 Firefox 中无法运行,而在 Chrome 中却可以正常运行。 Firefox 调试器中没有错误,但它只在浏览器右侧显示一行(好像强制布局永远不会更新)。我正在使用本地服务器对其进行调试,并在http://localhost:8888/ 浏览。

我一直在查看有关 stackoverflow 兼容性的不同帖子,但我似乎找不到与我的代码相关的任何内容。如果有人能给我一个关于首先调试什么的标题,那就太好了!

编辑:我在帖子底部以纯文本形式包含了指向数据和 csv 文件的链接。数据及代码:https://www.dropbox.com/s/ksh2qk1b5s9lfq5/Network%20View.zip?dl=0

这是 Firefox 控制台的输出:

mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create d3.js:553:4
SyntaxError: An invalid or illegal string was specified d3.js:562:0

铬:

火狐:

Index.html

<!DOCTYPE html>

<meta charset="utf-8">
<style>

.legend {                                                   
         font-size: 10px;                                         
      }                                                           
rect {                                                      
stroke-width: 2;                                          
}          

.node circle {
  stroke: white;
  stroke-width: 2px;
  opacity: 1.0;
}

line {
  stroke-width: 4px;
  stroke-opacity: 1.0;
  //stroke: "black"; 
}

body {
  /* Scaling for different browsers */
  -ms-transform: scale(1,1);
  -webkit-transform: scale(1,1);
  transform: scale(1,1);
}

svg{
    position:absolute;
    top:50%;
    left:0px;
}

</style>
<body>
<script type="text/javascript" src="d3.js"></script>
<script type="text/javascript" src="papaparse.js"></script> 
<script type="text/javascript" src="jquery.js"></script> 
<script type="text/javascript" src="networkview.js"></script>
</body>

networkview.js

var line_diff = 0.5;  // increase from zero if you want space between the call/text lines
var mark_offset = 10; // how many percent of the mark lines in each end are not used for the relationship between incoming/outgoing?
var mark_size = 5;    // size of the mark on the line

var legendRectSize = 9; // 18
var legendSpacing = 4; // 4
var recordTypes = [];
var legend;

var text_links_data, call_links_data;

// colors for the different parts of the visualization
recordTypes.push({
    text : "call",
    color : "#438DCA"
});

recordTypes.push({
    text : "text",
    color : "#70C05A"
});

recordTypes.push({
    text : "balance",
    color : "#245A76"
});

// Function for grabbing a specific property from an array
pluck = function (ary, prop) {
    return ary.map(function (x) {
        return x[prop]
    });
}

// Sums an array
sum = function (ary) {
    return ary.reduce(function (a, b) {
        return a + b
    }, 0);
}

maxArray = function (ary) {
        return ary.reduce(function (a, b) {
            return Math.max(a, b)
        }, -Infinity);
    }

minArray = function (ary) {
    return ary.reduce(function (a, b) {
        return Math.min(a, b)
    }, Infinity);
}

var data_links;
var data_nodes;

var results = Papa.parse("links.csv", {
        header : true,
        download : true,
        dynamicTyping : true,
        delimiter : ",",
        skipEmptyLines : true,
        complete : function (results) {
            data_links = results.data;
            dataLoaded();
        }
    });

var results = Papa.parse("nodes.csv", {
        header : true,
        download : true,
        dynamicTyping : true,
        delimiter : ",",
        skipEmptyLines : true,
        complete : function (results) {
            data_nodes = results.data;
            data_nodes.forEach(function (d, i) {
                d.size = (i == 0)? 200 : 30
                d.fill = (d.no_network_info == 1)? "#dfdfdf": "#a8a8a8"
            });
            dataLoaded();
        }
    });

function node_radius(d) {
    return Math.pow(40.0 * ((d.index == 0) ? 200 : 30), 1 / 3);
}
function node_radius_data(d) {
    return Math.pow(40.0 * d.size, 1 / 3);
}

function dataLoaded() {
    if (typeof data_nodes === "undefined" || typeof data_links === "undefined") {
        //console.log("Still loading")
    } else {
        CreateVisualizationFromData();
    }
}

function isConnectedToOtherThanMain(a) {
    var connected = false;
    for (i = 1; i < data_nodes.length; i++) {
        if (isConnected(a, data_nodes[i]) && a.index != i) {
            connected = true;
        }
    }
    return connected;
}

function isConnected(a, b) {
    return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}

function isConnectedAsSource(a, b) {
    return linkedByIndex[a.index + "," + b.index];
}

function isConnectedAsTarget(a, b) {
    return linkedByIndex[b.index + "," + a.index];
}

function isEqual(a, b) {
    return a.index == b.index;
}

function tick() {

    if (call_links_data.length > 0) {
        callLink
        .attr("x1", function (d) {
            return d.source.x - line_perpendicular_shift(d, 1)[0] + line_radius_shift_to_edge(d, 0)[0];
        })
        .attr("y1", function (d) {
            return d.source.y - line_perpendicular_shift(d, 1)[1] + line_radius_shift_to_edge(d, 0)[1];
        })
        .attr("x2", function (d) {
            return d.target.x - line_perpendicular_shift(d, 1)[0] + line_radius_shift_to_edge(d, 1)[0];
        })
        .attr("y2", function (d) {
            return d.target.y - line_perpendicular_shift(d, 1)[1] + line_radius_shift_to_edge(d, 1)[1];
        });
        callLink.each(function (d) {
            applyGradient(this, "call", d)
        });
    }

    if (text_links_data.length > 0) {
        textLink
        .attr("x1", function (d) {
            return d.source.x - line_perpendicular_shift(d, -1)[0] + line_radius_shift_to_edge(d, 0)[0];
        })
        .attr("y1", function (d) {
            return d.source.y - line_perpendicular_shift(d, -1)[1] + line_radius_shift_to_edge(d, 0)[1];
        })
        .attr("x2", function (d) {
            return d.target.x - line_perpendicular_shift(d, -1)[0] + line_radius_shift_to_edge(d, 1)[0];
        })
        .attr("y2", function (d) {
            return d.target.y - line_perpendicular_shift(d, -1)[1] + line_radius_shift_to_edge(d, 1)[1];
        });
        textLink.each(function (d) {
            applyGradient(this, "text", d)
        });

        node
        .attr("transform", function (d) {
            return "translate(" + d.x + "," + d.y + ")";
        });
    }



    if (force.alpha() < 0.05)
        drawLegend();
}

function getRandomInt() {
    return Math.floor(Math.random() * (100000 - 0));
}

function applyGradient(line, interaction_type, d) {
    var self = d3.select(line);

    var current_gradient = self.style("stroke")
        current_gradient = current_gradient.substring(4, current_gradient.length - 1);

    var new_gradient_id = "line-gradient" + getRandomInt();

    var from = d.source.size < d.target.size ? d.source : d.target;
    var to = d.source.size < d.target.size ? d.target : d.source;

    var mid_offset = 0;
    var standardColor = "";

    if (interaction_type == "call") {
        mid_offset = d.inc_calls / (d.inc_calls + d.out_calls);
        standardColor = "#438DCA";
    } else {
        mid_offset = d.inc_texts / (d.inc_texts + d.out_texts);
        standardColor = "#70C05A";
    }

    /* recordTypes_ID = pluck(recordTypes, 'text');
    whichRecordType = recordTypes_ID.indexOf(interaction_type);
    standardColor = recordTypes[whichRecordType].color;
 */
    mid_offset = mid_offset * 100;
    mid_offset = mid_offset * 0.6 + 20; // scale so it doesn't hit the ends

    lineLengthCalculation = function (x, y, x0, y0) {
        return Math.sqrt((x -= x0) * x + (y -= y0) * y);
    };

    lineLength = lineLengthCalculation(from.px, from.py, to.px, to.py);

    if (lineLength >= 0.1) {
        mark_size_percent = (mark_size / lineLength) * 100;

        defs.append("linearGradient")
        .attr("id", new_gradient_id)
        .attr("gradientUnits", "userSpaceOnUse")
        .attr("x1", from.px)
        .attr("y1", from.py)
        .attr("x2", to.px)
        .attr("y2", to.py)
        .selectAll("stop")
        .data([{
                    offset : "0%",
                    color : standardColor,
                    opacity : "1"
                }, {
                    offset : Math.round(mid_offset - mark_size_percent / 2) + "%",
                    color : standardColor,
                    opacity : "1"
                }, {
                    offset : Math.round(mid_offset - mark_size_percent / 2) + "%",
                    color : standardColor,
                    opacity : "1"
                }, {
                    offset : Math.round(mid_offset - mark_size_percent / 2) + "%",
                    color : "#245A76",
                    opacity : "1"
                }, {
                    offset : Math.round(mid_offset + mark_size_percent / 2) + "%",
                    color : "#245A76",
                    opacity : "1"
                }, {
                    offset : Math.round(mid_offset + mark_size_percent / 2) + "%",
                    color : standardColor,
                    opacity : "1"
                }, {
                    offset : Math.round(mid_offset + mark_size_percent / 2) + "%",
                    color : standardColor,
                    opacity : "1"
                }, {
                    offset : "100%",
                    color : standardColor,
                    opacity : "1"
                }
            ])
        .enter().append("stop")

        .attr("offset", function (d) {
            return d.offset;
        })
        .attr("stop-color", function (d) {
            return d.color;
        })
        .attr("stop-opacity", function (d) {
            return d.opacity;
        });

        self.style("stroke", "url(#" + new_gradient_id + ")")

        defs.select(current_gradient).remove();
    }
}

var linkedByIndex;

var width = $(window).width();
var height = $(window).height();

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var force;
var callLink;
var textLink;
var link;
var node;
var defs;
var total_interactions = 0;
var max_interactions = 0;

function CreateVisualizationFromData() {

    for (i = 0; i < data_links.length; i++) {
        total_interactions += data_links[i].inc_calls + data_links[i].out_calls + data_links[i].inc_texts + data_links[i].out_texts;
        max_interactions = Math.max(max_interactions, data_links[i].inc_calls + data_links[i].out_calls + data_links[i].inc_texts + data_links[i].out_texts)
    }

    linkedByIndex = {};

    data_links.forEach(function (d) {
        linkedByIndex[d.source + "," + d.target] = true;
        //linkedByIndex[d.source.index + "," + d.target.index] = true;
    });

    //console.log(total_interactions);
    //console.log(max_interactions);

    function chargeForNode(d, i) {
        // main node
        if (i == 0) {
            return -25000;
        }
        // contains other links
        else if (isConnectedToOtherThanMain(d)) {
            return -2000;
        } else {
            return -1200;
        }
    }

    // initial placement of nodes prevents overlaps
    central_x = width / 2
    central_y = height / 2

    data_nodes.forEach(function(d, i) {
    if (i != 0) {
            connected = isConnectedToOtherThanMain(d);
            data_nodes[i].x = connected? central_x + 10000: central_x -10000;
            data_nodes[i].y = connected? central_y: central_y;
    }
    else {data_nodes[i].x = central_x; data_nodes[i].y = central_y;}})

    force = d3.layout.force()
        .nodes(data_nodes)
        .links(data_links)
        .charge(function (d, i) {
            return chargeForNode(d, i)
        })
        .friction(0.6) // 0.6
        .gravity(0.4) // 0.6
        .size([width, height])
        .start();

    call_links_data = data_links.filter(function(d) {
        return (d.inc_calls + d.out_calls > 0)});
    text_links_data = data_links.filter(function(d) {
        return (d.inc_texts + d.out_texts > 0)});

    callLink = svg.selectAll(".call-line")
        .data(call_links_data)
        .enter().append("line");
    textLink = svg.selectAll(".text-line")
        .data(text_links_data)
        .enter().append("line");
    link = svg.selectAll("line");

    node = svg.selectAll(".node")
        .data(data_nodes)
        .enter().append("g")
        .attr("class", "node");


    defs = svg.append("defs");

    node
    .append("circle")
    .attr("r", node_radius)
    .style("fill", function (d) {
        return (d.index == 0)? "#ffffff" : d.fill;
    })
    .style("stroke", function (d) {
        return (d.index == 0)? "#8C8C8C" : "#ffffff";
    })

    svg
    .append("marker")
    .attr("id", "arrowhead")
    .attr("refX", 6 + 7)
    .attr("refY", 2)
    .attr("markerWidth", 6)
    .attr("markerHeight", 4)
    .attr("orient", "auto")
    .append("path")
    .attr("d", "M 0,0 V 4 L6,2 Z");

    if (text_links_data.length > 0) {
        textLink
        .style("stroke-width", function stroke(d) {
            return text_width(d)
        })
        .each(function (d) {
            applyGradient(this, "text", d)
        });
    }

    if (call_links_data.length > 0) {
        callLink
        .style("stroke-width", function stroke(d) {
            return call_width(d)
        })
        .each(function (d) {
            applyGradient(this, "call", d)
        });
    }

    force
    .on("tick", tick);

}

function drawLegend() {

    var node_px = pluck(data_nodes, 'px');
    var node_py = pluck(data_nodes, 'py');
    var nodeLayoutRight  = Math.max(maxArray(node_px));
    var nodeLayoutBottom = Math.max(maxArray(node_py));

    legend = svg.selectAll('.legend')
        .data(recordTypes)
        .enter()
        .append('g')
        .attr('class', 'legend')
        .attr('transform', function (d, i) {
            var rect_height = legendRectSize + legendSpacing;
            var offset = rect_height * (recordTypes.length-1);
            var horz = nodeLayoutRight + 15; /*  - 2*legendRectSize; */
            var vert = nodeLayoutBottom + (i * rect_height) - offset;
            return 'translate(' + horz + ',' + vert + ')';
        });

    legend.append('rect')
    .attr('width', legendRectSize)
    .attr('height', legendRectSize)
    .style('fill', function (d) {
        return d.color
    })
    .style('stroke', function (d) {
        return d.color
    });

    legend.append('text')
    .attr('x', legendRectSize + legendSpacing)
    .attr('y', legendRectSize - legendSpacing + 3)
    .text(function (d) {
        return d.text;
    })
    .style('fill', '#757575');

}

var line_width_factor = 10.0 // width for the widest line

function call_width(d) {
    return (d.inc_calls + d.out_calls) / max_interactions * line_width_factor;
}

function text_width(d) {
    return (d.inc_texts + d.out_texts) / max_interactions * line_width_factor;
}

function total_width(d) {
    return (d.inc_calls + d.out_calls + d.inc_texts + d.out_texts) / max_interactions * line_width_factor + line_diff;
}

function line_perpendicular_shift(d, direction) {
    theta = getAngle(d);
    theta_perpendicular = theta + (Math.PI / 2) * direction;

    lineWidthOfOppositeLine = direction == 1 ? text_width(d) : call_width(d);
    shift = lineWidthOfOppositeLine / 2;

    delta_x = (shift + line_diff) * Math.cos(theta_perpendicular)
    delta_y = (shift + line_diff) * Math.sin(theta_perpendicular)

    return [delta_x, delta_y]

}

function line_radius_shift_to_edge(d, which_node) { // which_node = 0 if source, = 1 if target

    theta = getAngle(d);
    theta = (which_node == 0) ? theta : theta + Math.PI; // reverse angle if target node
    radius = (which_node == 0) ? node_radius(d.source) : node_radius(d.target) // d.source and d.target refer directly to the nodes (not indices)
    radius -= 2; // add stroke width

    delta_x = radius * Math.cos(theta)
        delta_y = radius * Math.sin(theta)

        return [delta_x, delta_y]

}

function getAngle(d) {
    rel_x = d.target.x - d.source.x;
    rel_y = d.target.y - d.source.y;
    return theta = Math.atan2(rel_y, rel_x);
}

Links.csv

source,target,inc_calls,out_calls,inc_texts,out_texts
0,1,1.0,0.0,1.0,0.0
0,2,0.0,0.0,1.0,3.0
0,3,3.0,9.0,5.0,7.0
0,4,2.0,12.0,9.0,14.0
0,5,5.0,9.0,9.0,13.0
0,6,5.0,17.0,2.0,25.0
0,7,6.0,13.0,7.0,16.0
0,8,7.0,7.0,8.0,8.0
0,9,3.0,10.0,8.0,20.0
0,10,5.0,10.0,6.0,23.0
0,11,8.0,10.0,13.0,15.0
0,12,9.0,18.0,9.0,22.0
0,13,1.0,2.0,2.0,2.0
0,14,11.0,13.0,7.0,15.0
0,15,5.0,18.0,9.0,22.0
0,16,8.0,15.0,13.0,20.0
0,17,4.0,10.0,9.0,26.0
0,18,9.0,18.0,8.0,33.0
0,19,12.0,11.0,4.0,15.0
0,20,4.0,15.0,9.0,25.0
0,21,4.0,17.0,10.0,19.0
0,22,4.0,16.0,12.0,29.0
0,23,6.0,9.0,12.0,20.0
0,24,2.0,2.0,1.0,3.0
0,25,3.0,8.0,10.0,16.0
0,26,3.0,10.0,11.0,22.0
0,27,6.0,14.0,9.0,11.0
0,28,2.0,7.0,8.0,15.0
0,29,2.0,11.0,8.0,15.0
0,30,1.0,8.0,9.0,6.0
0,31,3.0,6.0,7.0,7.0
0,32,4.0,9.0,3.0,12.0
0,33,4.0,4.0,7.0,12.0
0,34,4.0,4.0,5.0,9.0
0,35,2.0,3.0,0.0,7.0
0,36,3.0,7.0,5.0,9.0
0,37,1.0,7.0,5.0,3.0
0,38,1.0,13.0,1.0,2.0
0,39,2.0,7.0,3.0,4.0
0,40,1.0,3.0,2.0,6.0
0,41,0.0,1.0,2.0,1.0
0,42,0.0,0.0,2.0,0.0
0,43,0.0,3.0,1.0,5.0
0,44,0.0,1.0,0.0,2.0
0,45,4.0,1.0,1.0,10.0
0,46,2.0,7.0,3.0,5.0
0,47,5.0,7.0,3.0,5.0
0,48,2.0,5.0,4.0,10.0
0,49,3.0,3.0,5.0,13.0
1,15,10.0,30.0,13.0,37.0
2,8,16.0,9.0,24.0,15.0
2,43,4.0,10.0,9.0,16.0
5,48,3.0,5.0,0.0,4.0
6,37,11.0,25.0,15.0,34.0
8,48,12.0,4.0,7.0,2.0
9,42,25.0,9.0,29.0,15.0
9,45,11.0,3.0,16.0,5.0
12,24,4.0,15.0,13.0,16.0
14,31,18.0,9.0,29.0,12.0
14,33,5.0,10.0,4.0,9.0
15,28,8.0,5.0,16.0,5.0
16,36,14.0,11.0,10.0,19.0
23,38,3.0,11.0,6.0,10.0
26,42,9.0,23.0,17.0,21.0
27,46,12.0,12.0,15.0,21.0
29,39,8.0,15.0,9.0,20.0
29,47,8.0,27.0,19.0,24.0
33,46,6.0,4.0,13.0,13.0
37,43,10.0,12.0,6.0,21.0

Nodes.csv

no_network_info
0
0
0
1
1
0
0
0
0
0
0
1
0
1
0
0
0
1
0
1
1
0
0
0
0
1
0
0
0
0
1
0
1
0
1
1
0
0
0
0
1
1
0
0
1
0
0
0
0
0

【问题讨论】:

  • 火狐控制台有没有说什么?如果您包含数据文件,也可能会更容易。
  • 好的,我已经完成了。不知道 Firefox 控制台 - 我现在已经添加了输出。
  • 您使用的是本地版本的 d3 吗?它可能已经过时了。尝试使用
  • 我使用的是本地版本的 d3,但它是最新的。遗憾的是,更改为远程版本并没有解决问题。
  • 基于 Katherine 的调试将 current_gradient 变量识别为问题的根源,我在您的代码中查找了该特定变量:看起来您正在使用 String.substring 提取 @来自url(#id) 样式属性的987654333@ 值。这可能就像 Firefox 返回 url("#id") 或其他无法正确解析的格式一样简单。

标签: javascript google-chrome firefox d3.js compatibility


【解决方案1】:

语法错误与这一行有关:

defs.select(current_gradient).remove();

但以上才是真正的问题。替换:

current_gradient = current_gradient.substring(4, current_gradient.length - 1);

与:

if (current_gradient.match("http")) {
    var parts = current_gradient.split("/");
    current_gradient = parts[-1];
} else {
    current_gradient = current_gradient.substring(4, current_gradient.length - 1);
}

当您最初在 Chrome 中设置 current_gradient 时,它被设置为“url(somevalue)”,而在 Firefox 中它被设置为“url(fullpath/somevalue)”。因此,您需要删除所有路径信息,而不仅仅是“url()”位。拆分斜线并从拆分中获取最后一个值可能是最简单的方法。

【讨论】:

  • current_gradient 在 Chrome 中的值类似于:#line-gradient98575,而在 Firefox 中类似于:""localhost:9013/#line-gradient43891") transparen" 这就是语法错误的原因。
  • 非常感谢您的帮助!现在它在SyntaxError 已被删除时运行,但遗憾的是它太慢而无法使用。这可能是由于控制台中的mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create d3.js:553:4 行所致。我不确定是编辑这个问题还是发布一个新问题。我最终做了后者。有兴趣的去看看stackoverflow.com/questions/29705499/…
猜你喜欢
  • 2016-02-19
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 2020-04-04
  • 2011-03-16
  • 2015-06-11
  • 2018-07-05
  • 2014-08-18
相关资源
最近更新 更多