PageSourceSearch

http://arda-maps.org/opensource/js/familytree-d3.js

js arda-maps.org collected 2026-10-03 00:56:23 UTC 18,460 bytes, 473 lines download raw bytes

1var familytree = (function() {
2    var svg, link = {}, node;
3    var zoom;
4    var width = 1200, height = 900;
5    var container;
6    var eventNames = ["node_click", "node_dblclick", "node_contextmenu", "graph_init", "force_stage", "force_stop"],
7        events     = d3.dispatch.apply(null, eventNames),
8        markerEnds = d3.range(1, 9).reduce(function(m, d) {
9            return (m[d] = "url(#end" + d + ")", m)
10        }, {}),
11        phasesAlpha = (function(){
12            var p = [0.08, 0.04, 0.02, 0], i = null;
13            return function (alpha){
14                // resets if alpha is falsey or > p[0]
15                var i0 = i;
16                i = alpha ? (alpha > p[0] ? (i = 0) : i) : 0;
17                if(alpha < (p[i])) i++;
18                return i != i0;
19            };
20        })(), // fire the force_stage event when alpha reaches each value
21
22        fdg = (function() {
23            var force = d3.layout.force()
24                    .size([width, height])
25                    .gravity(.2)
26                    .charge(-400)
27                    .friction(0.9)
28                    .theta(0.9)
29                    .linkStrength(1)
30                    .distance(100)
31                    .on("tick", tick)
32                    .on("end", events.force_stop),
33                fdg = {};
34
35            svg = zoomableSVG({width: "100%", height: "100%"}, "#familytreecontentsvg", {extent: [0.4, 4], dblclk: null});
36
37            function data(dataSet) {
38                if (!dataSet) return force.nodes().length ? {nodes: force.nodes(), links: force.links()} : null;
39                force
40                    .nodes(dataSet.nodes)
41                    .links(dataSet.links);
42
43                svg.onZoom(zoomed);
44
45                hookDrag(force.drag(), "dragstart.force", function(d) {
46                    //prevent dragging on the nodes from dragging the canvas
47                    var e = d3.event.sourceEvent;
48                    e.preventDefault();
49                    if(e.button != 2) {
50                        //left click only
51                        e.stopPropagation();
52                        d.fixed = e.shiftKey || e.touches && (e.touches.length > 1);
53                        events.node_click(d);
54                    }
55                });
56                hookDrag(force.drag(), "dragend.force", function(d) {
57                    //prevent dragging on the nodes from dragging the canvas
58                    var e = d3.event.sourceEvent;
59                    d.fixed = e.shiftKey || d.fixed;
60                });
61
62                var varsvgMarker = svg.selectAll("defs")
63                    .data([["end"]])
64                    .enter().append("defs")
65                    .selectAll("marker")
66                    .data(id)
67                    .enter();
68                createMarker(varsvgMarker);
69
70                container = svg.container("#container").data([{nodes: [force.nodes()], links: [force.links()]}]);
71                container.enter().append("g").attr("id", "container");
72
73                var links = container.selectAll(".links").data(function(d) {
74                    return d.links
75                });
76                links.enter().append("g").attr("class", "links");
77
78                link = links.selectAll("line").data(id);
79                link.enter().append("line");
80                link.exit().remove();
81                link.attr("class", function(d) {
82                    if(d.relation == "BEGETS") {
83                        return "linkBEGETS";
84                    }
85                    if(d.relation == "LOVES") {
86                        return "linkLOVES";
87                    }
88                    if(d.relation == "HASSIBLING") {
89                        return "linkHASSIBLING";
90                    }
91                })
92                    .attr("marker-end", function(d) {
93                        if(d.relation == "BEGETS") {
94                            return markerEnds[d.targetSign] || "url(#end1)"
95                        }
96                    });
97
98                var nodes = container.selectAll(".nodes").data(function(d) {
99                    return d.nodes
100                });
101                nodes.enter().append("g").attr("class", "nodes");
102
103                node = nodes.selectAll(".node").data(id, nodeKey);
104                var newNode = node.enter().append("g").attr("class", "node");
105                node.exit().remove();
106                newNode
107                    .on("mouseover", mouseover)
108                    .on("mouseout", mouseout)
109                    .on("dblclick", function(d) {
110                        events.node_dblclick(d);
111                    })
112                    .on('contextmenu', function(data, index) {
113                        d3.event.preventDefault();
114                        events.node_contextmenu(data, index);
115                    })
116                    .call(force.drag);
117                newNode
118                    .append("circle")
119                    .attr("class", "bgcircle");
120                node.select("circle")
121                    .attr("r", function(d) {
122                        return Math.abs(posXY(d));
123                    })
124                    .style("fill", function(d) {
125                        return colourRace(d);
126                    })
127                    .style("stroke", function(d) {
128                        return colourRace(d);
129                    });
130                newNode
131                    .append("svg:image")
132                    .attr("class", "circle")
133                    .attr("xlink:href", imgHref)
134                    .attr("width", function(d) {
135                        return sizeXY(d);
136                    })
137                    .attr("height", function(d) {
138                        return sizeXY(d);
139                    })
140                    .on("error", function() {
141                        d3.select(this).style("visibility", "hidden");
142                    });
143                node.select("image")
144                    .attr("x", function(d) {
145                        return posXY(d);
146                    })
147                    .attr("y", function(d) {
148                        return posXY(d);
149                    });
150                newNode
151                    .append("text")
152                    .attr("class", "nodetext");
153                node.select("text")
154                    .attr("x", function(d) {
155                        return Math.abs(posXY(d) - 5);
156                    })
157                    .attr("y", 4)
158                    .text(function(d) {
159                        return d.name;
160                    });
161
162                force.start();
163
164                function hookDrag(target, event, hook) {
165                    //hook force.drag behaviour
166                    var stdDrag = target.on(event);
167                    target.on(event, function(d) {
168                        hook.call(this, d);
169                        stdDrag.call(this, d);
170                    });
171                }
172
173                function zoomed() {
174                    if(force.alpha() < 0.01) force.alpha(0.01);
175                }
176
177                function imgHref(d) {
178                    return "/pics/arda/creature/" + d.uniquename + "_familytree.png";
179                }
180
181                function nodeKey(d, i) {
182                    return this.href ? d3.select(this).attr("href") : imgHref(d);
183                }
184
185                // zoom context services
186                //  content is the target for zoom movements in zoomed
187                d3.rebind(fdg, container, "attr");
188
189                //  access the current transform state in zoom listener coordinates
190                d3.rebind(fdg, svg, "translate");
191
192                events.graph_init();
193
194                return this;
195
196            }
197
198            var zoomTo = svg.zoomTo.bind(null, 1000);
199            fdg.zoomTo = (function() {
200                var _n;
201                return function(n) {
202                    if(!n && ! _n) return false;
203                    zoomTo(n || _n);
204                    _n = n ? n : _n;
205                }
206            })();
207            fdg.zoomTime = (function() {
208                var _t;
209                return function(t) {
210                    if(t == undefined) return _t;
211                    if(t == null) return (zoomTo = svg.zoomTo, this);
212                    zoomTo = svg.zoomTo.bind(null, _t = t);
213                    return this;
214                }
215            })();
216
217            fdg.focusNode = (function() {
218                var _datum; // previous datum is stored as default
219                return function(datum) {
220                    _datum = datum || _datum;
221                    // closure on a reference to the node and transition state
222                    var _n = node.filter(function(d) {
223                        return _datum === d;
224                    }), _trans, _t;
225                    // return an object with chainable methods
226                    return {
227                        highlight: function() {
228                            _trans = highlight(_n);
229                            return this;
230                        },
231                        delay    : function(t) {
232                            _t = t;
233                            return this;
234                        },
235                        blur     : function() {
236                            if(_trans) _trans.each("end.is-focused", function() {
237                                blur(_n, _t)
238                            });
239                            else blur(_n);
240                            return this;
241                        }
242                    }
243                }
244            })();
245
246            fdg.data = data;
247
248            return fdg;
249
250            function tick(e) {
251                link
252                    .attr("x1", function(d) {
253                        return d.source.x;
254                    })
255                    .attr("y1", function(d) {
256                        return d.source.y;
257                    })
258                    .attr("x2", function(d) {
259                        return d.target.x;
260                    })
261                    .attr("y2", function(d) {
262                        return d.target.y;
263                    });
264                node
265                    .attr("transform", function(d) {
266                        return "translate(" + d.x + "," + d.y + ")";
267                    });
268                if(phasesAlpha(e.alpha)) events.force_stage(e.alpha);
269            }
270        })().zoomTime(1000);
271    //events.on("force_stage.debug", function(a){console.log(a)});
272
273    function mouseover() {
274        highlight(d3.select(this))
275    }
276    function mouseout() {
277        blur(d3.select(this));
278    }
279
280    function highlight(selection){
281        var s = svg.scale(), transition = selection.select("text").transition()
282            .duration(750)
283            .style({"font-size": (s > 1 ? 15 : (15/s).toFixed()) + "px","fill": "#F9D276"});
284        selection.moveToFront();
285        return transition;
286    }
287    function blur(selection, delay){
288        selection.select("text").transition()
289            .duration(750)
290            .delay(delay || 0)
291            .style({"font-size": "8px", "fill": "#ccc", stroke: "none"});
292    }
293    function createMarker(svg) {
294        var obj = [38, 43, 50, 54, 60, 65, 70, 80, 85];
295        for(var i = 0; i < obj.length; i++) {
296            svg.append("svg:marker")
297                .attr("id", "end" + (i + 1))
298                .attr("viewBox", "0 -5 10 10")
299                .attr("refX", obj[i])
300                .attr("refY", -0.05)
301                .attr("markerWidth", 6)
302                .attr("markerHeight", 6)
303                .attr("orient", "auto")
304                .append("svg:path")
305                .attr("d", "M0,-4L10,0L0,4");
306        }
307    }
308    function sizeXY(d) {
309        var deflt = -10;
310        return [deflt, 20, 24, 28, 32, 36, 40, 44, 48, 52][d.significance || 0] || deflt;
311    }
312    function posXY(d) {
313        var deflt = 10;
314        return [deflt, -10, -12, -14, -16, -18, -20, -22, -24, -26][d.significance || 0] || deflt;
315    }
316    function colourRace(d) {
317        return {
318            "Ainu"            : "#000",
319            "Arnorian"        : "#5D8AA8",
320            "Balrog"          : "#000",
321            "Dragon"          : "#900",
322            "Dwarf"           : "#996515",
323            "Elf"             : "#900020",
324            "Ent"             : "#5b3",
325            "Falmar/Falas Elf": "#0099CC",
326            "God"             : "#fff",
327            "Gondorian"       : "#393939",
328            "Half-Elf"        : "#900020",
329            "Hobbit"          : "#006600",
330            "Maia"            : "#9A03B5",
331            "Man"             : "#993D00",
332            "Nando"           : "#355E3B",
333            "Nazgûl"          : "#000",
334            "Noldo"           : "#090A67",
335            "Númenórean"      : "#007BA7",
336            "Orc"             : "#736326",
337            "Rohir"           : "#80461B",
338            "Sinda"           : "#949494",
339            "Spider"          : "#000",
340            "Teleri"          : "#4B0101",
341            "Tree"            : "#2b6",
342            "Troll"           : "#000",
343            "Vala"            : "#440D60",
344            "Vanya"           : "#FFCC00",
345            "Werewolf"        : "#000"
346        }[d.race] || "#aaa";
347    }
348
349    return d3.rebind.bind(fdg, {
350        initializeGraph: fdg.data,
351        zoomTo: function(){
352            fdg.zoomTo.apply(this, arguments)
353        },
354        focusNode       : fdg.focusNode,
355        events          : events,
356        data: function() {
357            return fdg.data();
358        }
359    }, events, "on").apply(fdg, events);
360
361    function zoomableSVG(size, selector, z) {
362        //delivers an svg background with zoom/drag context in the selector element
363        //if height or width is NaN, assume it is percentage and ignore margin
364        var margin = size.margin || {top: 0, right: 0, bottom: 0, left: 0},
365            percentW = isNaN(size.width), percentH = isNaN(size.height),
366            w = percentW ? size.width : size.width - margin.left - margin.right,
367            h = percentH ? size.height : size.height - margin.top - margin.bottom,
368            zoomStart = function() {
369                return this
370            },
371            zoomed = function() {
372                return this
373            },
374            container,
375
376            zoom = zoom || d3.behavior.zoom().scaleExtent(z && z.extent || [0.4, 4])
377                .on("zoom", function(d, i, j) {
378                    onZoom.call(this, d, i, j);
379                    zoomed.call(this, d, i, j);
380                })
381                .on("zoomstart", function(d, i, j){
382                    onZoomStart.call(this, d, i, j);
383                    zoomStart.call(this, d, i, j);
384                });
385
386        var svg = d3.select(selector).selectAll("svg").data([["transform root"]]);
387        svg.enter().append("svg");
388        svg.attr({width: size.width, height: size.height});
389
390        var g = svg.selectAll("#zoom").data(id),
391            gEnter = g.enter().append("g")
392                .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
393                .call(zoom)
394                .attr({class: "outline", id: "zoom"}),
395            surface = gEnter.append("rect")
396                .attr({width: w, height: h, fill: "none"})
397                .style({"pointer-events": "all"});
398        if(z && (typeof z.dblclk != "undefined")) gEnter.on("dblclick.zoom", z.dblclk);
399
400        function onZoomStart(){
401            // zoom translate and scale are initially [0,0] and 1
402            // this needs to be aligned with the container to stop
403            // jump back to zero before first jump transition
404            if(container != undefined) {
405                var t = d3.transform(container.attr("transform"));
406                zoom.translate(t.translate); zoom.scale(t.scale[0]);
407            }
408        }
409
410        function onZoom(){
411            if(container != undefined) {
412                var e = d3.event.sourceEvent,
413                    isWheel = e && ((e.type == "mousewheel") || (e.type == "wheel")),
414                    t = d3.transform(container.attr("transform"));
415                t.translate = d3.event.translate; t.scale = [d3.event.scale, d3.event.scale];
416                return isWheel ? zoomWheel.call(this, t) : zoomInst.call(this, t)
417            }
418        }
419        function zoomInst(t){
420            container.attr("transform", t.toString());
421        }
422        function zoomWheel(t){
423            container.transition().duration(450).attr("transform", t.toString());
424        }
425
426        g.h = h;
427        g.w = w;
428
429        g.container = function(selection){
430            var d3_data, d3_datum;
431            if(selection) {
432                container = g.selectAll(selection);
433                // temporarily subclass container
434                d3_data = container.data;
435                // need a reference to the update selection
436                // so force data methods back to here
437                container.data = function() {
438                    delete container.data;	// remove the sub-classing
439                    return container = d3_data.apply(container, arguments)
440                }
441            }
442            return container;
443        };
444
445        g.onZoom = function(cb) {
446            zoomed = cb;
447        };
448        g.onZoomStart = function(cb) {
449            zoomStart = cb;
450        };
451        g.zoomTo = function(t, p){
452            // map p to the center of the plot surface
453            var s = zoom.scale(), bBox = surface.node().getBBox(),
454                w = bBox.width - infoState.width(), h = bBox.height,
455                p1 = [w/2 - p.x * s, h/2 - p.y * s];
456            container.transition().duration(t).call(zoom.translate(p1).event);
457        };
458        d3.rebind(g, zoom, "translate");
459        d3.rebind(g, zoom, "scale");
460
461        return g;
462    }
463
464    function id(d) {
465        return d;
466    }
467})();
468
469d3.selection.prototype.moveToFront = function() {
470    return this.each(function() {
471        this.parentNode.appendChild(this);
472    });
473};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.