diff --git a/demos/assets/data/flare.json b/demos/assets/data/flare.json new file mode 100644 index 0000000000..8c21d9831b --- /dev/null +++ b/demos/assets/data/flare.json @@ -0,0 +1 @@ +{"name":"flare","children":[{"name":"analytics","children":[{"name":"cluster","children":[{"name":"AgglomerativeCluster","value":3938},{"name":"CommunityStructure","value":3812},{"name":"HierarchicalCluster","value":6714},{"name":"MergeEdge","value":743}]},{"name":"graph","children":[{"name":"BetweennessCentrality","value":3534},{"name":"LinkDistance","value":5731},{"name":"MaxFlowMinCut","value":7840},{"name":"ShortestPaths","value":5914},{"name":"SpanningTree","value":3416}]},{"name":"optimization","children":[{"name":"AspectRatioBanker","value":7074}]}]},{"name":"animate","children":[{"name":"Easing","value":17010},{"name":"FunctionSequence","value":5842},{"name":"interpolate","children":[{"name":"ArrayInterpolator","value":1983},{"name":"ColorInterpolator","value":2047},{"name":"DateInterpolator","value":1375},{"name":"Interpolator","value":8746},{"name":"MatrixInterpolator","value":2202},{"name":"NumberInterpolator","value":1382},{"name":"ObjectInterpolator","value":1629},{"name":"PointInterpolator","value":1675},{"name":"RectangleInterpolator","value":2042}]},{"name":"ISchedulable","value":1041},{"name":"Parallel","value":5176},{"name":"Pause","value":449},{"name":"Scheduler","value":5593},{"name":"Sequence","value":5534},{"name":"Transition","value":9201},{"name":"Transitioner","value":19975},{"name":"TransitionEvent","value":1116},{"name":"Tween","value":6006}]},{"name":"data","children":[{"name":"converters","children":[{"name":"Converters","value":721},{"name":"DelimitedTextConverter","value":4294},{"name":"GraphMLConverter","value":9800},{"name":"IDataConverter","value":1314},{"name":"JSONConverter","value":2220}]},{"name":"DataField","value":1759},{"name":"DataSchema","value":2165},{"name":"DataSet","value":586},{"name":"DataSource","value":3331},{"name":"DataTable","value":772},{"name":"DataUtil","value":3322}]},{"name":"display","children":[{"name":"DirtySprite","value":8833},{"name":"LineSprite","value":1732},{"name":"RectSprite","value":3623},{"name":"TextSprite","value":10066}]},{"name":"flex","children":[{"name":"FlareVis","value":4116}]},{"name":"physics","children":[{"name":"DragForce","value":1082},{"name":"GravityForce","value":1336},{"name":"IForce","value":319},{"name":"NBodyForce","value":10498},{"name":"Particle","value":2822},{"name":"Simulation","value":9983},{"name":"Spring","value":2213},{"name":"SpringForce","value":1681}]},{"name":"query","children":[{"name":"AggregateExpression","value":1616},{"name":"And","value":1027},{"name":"Arithmetic","value":3891},{"name":"Average","value":891},{"name":"BinaryExpression","value":2893},{"name":"Comparison","value":5103},{"name":"CompositeExpression","value":3677},{"name":"Count","value":781},{"name":"DateUtil","value":4141},{"name":"Distinct","value":933},{"name":"Expression","value":5130},{"name":"ExpressionIterator","value":3617},{"name":"Fn","value":3240},{"name":"If","value":2732},{"name":"IsA","value":2039},{"name":"Literal","value":1214},{"name":"Match","value":3748},{"name":"Maximum","value":843},{"name":"methods","children":[{"name":"add","value":593},{"name":"and","value":330},{"name":"average","value":287},{"name":"count","value":277},{"name":"distinct","value":292},{"name":"div","value":595},{"name":"eq","value":594},{"name":"fn","value":460},{"name":"gt","value":603},{"name":"gte","value":625},{"name":"iff","value":748},{"name":"isa","value":461},{"name":"lt","value":597},{"name":"lte","value":619},{"name":"max","value":283},{"name":"min","value":283},{"name":"mod","value":591},{"name":"mul","value":603},{"name":"neq","value":599},{"name":"not","value":386},{"name":"or","value":323},{"name":"orderby","value":307},{"name":"range","value":772},{"name":"select","value":296},{"name":"stddev","value":363},{"name":"sub","value":600},{"name":"sum","value":280},{"name":"update","value":307},{"name":"variance","value":335},{"name":"where","value":299},{"name":"xor","value":354},{"name":"_","value":264}]},{"name":"Minimum","value":843},{"name":"Not","value":1554},{"name":"Or","value":970},{"name":"Query","value":13896},{"name":"Range","value":1594},{"name":"StringUtil","value":4130},{"name":"Sum","value":791},{"name":"Variable","value":1124},{"name":"Variance","value":1876},{"name":"Xor","value":1101}]},{"name":"scale","children":[{"name":"IScaleMap","value":2105},{"name":"LinearScale","value":1316},{"name":"LogScale","value":3151},{"name":"OrdinalScale","value":3770},{"name":"QuantileScale","value":2435},{"name":"QuantitativeScale","value":4839},{"name":"RootScale","value":1756},{"name":"Scale","value":4268},{"name":"ScaleType","value":1821},{"name":"TimeScale","value":5833}]},{"name":"util","children":[{"name":"Arrays","value":8258},{"name":"Colors","value":10001},{"name":"Dates","value":8217},{"name":"Displays","value":12555},{"name":"Filter","value":2324},{"name":"Geometry","value":10993},{"name":"heap","children":[{"name":"FibonacciHeap","value":9354},{"name":"HeapNode","value":1233}]},{"name":"IEvaluable","value":335},{"name":"IPredicate","value":383},{"name":"IValueProxy","value":874},{"name":"math","children":[{"name":"DenseMatrix","value":3165},{"name":"IMatrix","value":2815},{"name":"SparseMatrix","value":3366}]},{"name":"Maths","value":17705},{"name":"Orientation","value":1486},{"name":"palette","children":[{"name":"ColorPalette","value":6367},{"name":"Palette","value":1229},{"name":"ShapePalette","value":2059},{"name":"SizePalette","value":2291}]},{"name":"Property","value":5559},{"name":"Shapes","value":19118},{"name":"Sort","value":6887},{"name":"Stats","value":6557},{"name":"Strings","value":22026}]},{"name":"vis","children":[{"name":"axis","children":[{"name":"Axes","value":1302},{"name":"Axis","value":24593},{"name":"AxisGridLine","value":652},{"name":"AxisLabel","value":636},{"name":"CartesianAxes","value":6703}]},{"name":"controls","children":[{"name":"AnchorControl","value":2138},{"name":"ClickControl","value":3824},{"name":"Control","value":1353},{"name":"ControlList","value":4665},{"name":"DragControl","value":2649},{"name":"ExpandControl","value":2832},{"name":"HoverControl","value":4896},{"name":"IControl","value":763},{"name":"PanZoomControl","value":5222},{"name":"SelectionControl","value":7862},{"name":"TooltipControl","value":8435}]},{"name":"data","children":[{"name":"Data","value":20544},{"name":"DataList","value":19788},{"name":"DataSprite","value":10349},{"name":"EdgeSprite","value":3301},{"name":"NodeSprite","value":19382},{"name":"render","children":[{"name":"ArrowType","value":698},{"name":"EdgeRenderer","value":5569},{"name":"IRenderer","value":353},{"name":"ShapeRenderer","value":2247}]},{"name":"ScaleBinding","value":11275},{"name":"Tree","value":7147},{"name":"TreeBuilder","value":9930}]},{"name":"events","children":[{"name":"DataEvent","value":2313},{"name":"SelectionEvent","value":1880},{"name":"TooltipEvent","value":1701},{"name":"VisualizationEvent","value":1117}]},{"name":"legend","children":[{"name":"Legend","value":20859},{"name":"LegendItem","value":4614},{"name":"LegendRange","value":10530}]},{"name":"operator","children":[{"name":"distortion","children":[{"name":"BifocalDistortion","value":4461},{"name":"Distortion","value":6314},{"name":"FisheyeDistortion","value":3444}]},{"name":"encoder","children":[{"name":"ColorEncoder","value":3179},{"name":"Encoder","value":4060},{"name":"PropertyEncoder","value":4138},{"name":"ShapeEncoder","value":1690},{"name":"SizeEncoder","value":1830}]},{"name":"filter","children":[{"name":"FisheyeTreeFilter","value":5219},{"name":"GraphDistanceFilter","value":3165},{"name":"VisibilityFilter","value":3509}]},{"name":"IOperator","value":1286},{"name":"label","children":[{"name":"Labeler","value":9956},{"name":"RadialLabeler","value":3899},{"name":"StackedAreaLabeler","value":3202}]},{"name":"layout","children":[{"name":"AxisLayout","value":6725},{"name":"BundledEdgeRouter","value":3727},{"name":"CircleLayout","value":9317},{"name":"CirclePackingLayout","value":12003},{"name":"DendrogramLayout","value":4853},{"name":"ForceDirectedLayout","value":8411},{"name":"IcicleTreeLayout","value":4864},{"name":"IndentedTreeLayout","value":3174},{"name":"Layout","value":7881},{"name":"NodeLinkTreeLayout","value":12870},{"name":"PieLayout","value":2728},{"name":"RadialTreeLayout","value":12348},{"name":"RandomLayout","value":870},{"name":"StackedAreaLayout","value":9121},{"name":"TreeMapLayout","value":9191}]},{"name":"Operator","value":2490},{"name":"OperatorList","value":5248},{"name":"OperatorSequence","value":4190},{"name":"OperatorSwitch","value":2581},{"name":"SortOperator","value":2023}]},{"name":"Visualization","value":16540}]}]} diff --git a/demos/assets/data/modeling-methods.json b/demos/assets/data/modeling-methods.json index 71eb58f7c5..b80aa56554 100644 --- a/demos/assets/data/modeling-methods.json +++ b/demos/assets/data/modeling-methods.json @@ -1,58 +1,58 @@ { - "name": "Modeling Methods", + "id": "Modeling Methods", "children": [ { - "name": "Classification", + "id": "Classification", "children": [ - { "name": "Logistic regression" }, - { "name": "Linear discriminant analysis" }, - { "name": "Rules" }, - { "name": "Decision trees" }, - { "name": "Naive Bayes" }, - { "name": "K nearest neighbor" }, - { "name": "Probabilistic neural network" }, - { "name": "Support vector machine" } + { "id": "Logistic regression" }, + { "id": "Linear discriminant analysis" }, + { "id": "Rules" }, + { "id": "Decision trees" }, + { "id": "Naive Bayes" }, + { "id": "K nearest neighbor" }, + { "id": "Probabilistic neural network" }, + { "id": "Support vector machine" } ] }, { - "name": "Consensus", + "id": "Consensus", "children": [ { - "name": "Models diversity", + "id": "Models diversity", "children": [ - { "name": "Different initializations" }, - { "name": "Different parameter choices" }, - { "name": "Different architectures" }, - { "name": "Different modeling methods" }, - { "name": "Different training sets" }, - { "name": "Different feature sets" } + { "id": "Different initializations" }, + { "id": "Different parameter choices" }, + { "id": "Different architectures" }, + { "id": "Different modeling methods" }, + { "id": "Different training sets" }, + { "id": "Different feature sets" } ] }, { - "name": "Methods", + "id": "Methods", "children": [ - { "name": "Classifier selection" }, - { "name": "Classifier fusion" } + { "id": "Classifier selection" }, + { "id": "Classifier fusion" } ] }, { - "name": "Common", + "id": "Common", "children": [ - { "name": "Bagging" }, - { "name": "Boosting" }, - { "name": "AdaBoost" } + { "id": "Bagging" }, + { "id": "Boosting" }, + { "id": "AdaBoost" } ] } ] }, { - "name": "Regression", + "id": "Regression", "children": [ - { "name": "Multiple linear regression" }, - { "name": "Partial least squares" }, - { "name": "Multi-layer feedforward neural network" }, - { "name": "General regression neural network" }, - { "name": "Support vector regression" } + { "id": "Multiple linear regression" }, + { "id": "Partial least squares" }, + { "id": "Multi-layer feedforward neural network" }, + { "id": "General regression neural network" }, + { "id": "Support vector regression" } ] } ] diff --git a/demos/assets/energy.json b/demos/assets/energy.json new file mode 100644 index 0000000000..d1819bf63d --- /dev/null +++ b/demos/assets/energy.json @@ -0,0 +1 @@ +{"nodes":[{"name":"Agricultural 'waste'"},{"name":"Bio-conversion"},{"name":"Liquid"},{"name":"Losses"},{"name":"Solid"},{"name":"Gas"},{"name":"Biofuel imports"},{"name":"Biomass imports"},{"name":"Coal imports"},{"name":"Coal"},{"name":"Coal reserves"},{"name":"District heating"},{"name":"Industry"},{"name":"Heating and cooling - commercial"},{"name":"Heating and cooling - homes"},{"name":"Electricity grid"},{"name":"Over generation / exports"},{"name":"H2 conversion"},{"name":"Road transport"},{"name":"Agriculture"},{"name":"Rail transport"},{"name":"Lighting & appliances - commercial"},{"name":"Lighting & appliances - homes"},{"name":"Gas imports"},{"name":"Ngas"},{"name":"Gas reserves"},{"name":"Thermal generation"},{"name":"Geothermal"},{"name":"H2"},{"name":"Hydro"},{"name":"International shipping"},{"name":"Domestic aviation"},{"name":"International aviation"},{"name":"National navigation"},{"name":"Marine algae"},{"name":"Nuclear"},{"name":"Oil imports"},{"name":"Oil"},{"name":"Oil reserves"},{"name":"Other waste"},{"name":"Pumped heat"},{"name":"Solar PV"},{"name":"Solar Thermal"},{"name":"Solar"},{"name":"Tidal"},{"name":"UK land based bioenergy"},{"name":"Wave"},{"name":"Wind"}],"links":[{"source":0,"target":1,"value":124.729},{"source":1,"target":2,"value":0.597},{"source":1,"target":3,"value":26.862},{"source":1,"target":4,"value":280.322},{"source":1,"target":5,"value":81.144},{"source":6,"target":2,"value":35},{"source":7,"target":4,"value":35},{"source":8,"target":9,"value":11.606},{"source":10,"target":9,"value":63.965},{"source":9,"target":4,"value":75.571},{"source":11,"target":12,"value":10.639},{"source":11,"target":13,"value":22.505},{"source":11,"target":14,"value":46.184},{"source":15,"target":16,"value":104.453},{"source":15,"target":14,"value":113.726},{"source":15,"target":17,"value":27.14},{"source":15,"target":12,"value":342.165},{"source":15,"target":18,"value":37.797},{"source":15,"target":19,"value":4.412},{"source":15,"target":13,"value":40.858},{"source":15,"target":3,"value":56.691},{"source":15,"target":20,"value":7.863},{"source":15,"target":21,"value":90.008},{"source":15,"target":22,"value":93.494},{"source":23,"target":24,"value":40.719},{"source":25,"target":24,"value":82.233},{"source":5,"target":13,"value":0.129},{"source":5,"target":3,"value":1.401},{"source":5,"target":26,"value":151.891},{"source":5,"target":19,"value":2.096},{"source":5,"target":12,"value":48.58},{"source":27,"target":15,"value":7.013},{"source":17,"target":28,"value":20.897},{"source":17,"target":3,"value":6.242},{"source":28,"target":18,"value":20.897},{"source":29,"target":15,"value":6.995},{"source":2,"target":12,"value":121.066},{"source":2,"target":30,"value":128.69},{"source":2,"target":18,"value":135.835},{"source":2,"target":31,"value":14.458},{"source":2,"target":32,"value":206.267},{"source":2,"target":19,"value":3.64},{"source":2,"target":33,"value":33.218},{"source":2,"target":20,"value":4.413},{"source":34,"target":1,"value":4.375},{"source":24,"target":5,"value":122.952},{"source":35,"target":26,"value":839.978},{"source":36,"target":37,"value":504.287},{"source":38,"target":37,"value":107.703},{"source":37,"target":2,"value":611.99},{"source":39,"target":4,"value":56.587},{"source":39,"target":1,"value":77.81},{"source":40,"target":14,"value":193.026},{"source":40,"target":13,"value":70.672},{"source":41,"target":15,"value":59.901},{"source":42,"target":14,"value":19.263},{"source":43,"target":42,"value":19.263},{"source":43,"target":41,"value":59.901},{"source":4,"target":19,"value":0.882},{"source":4,"target":26,"value":400.12},{"source":4,"target":12,"value":46.477},{"source":26,"target":15,"value":525.531},{"source":26,"target":3,"value":787.129},{"source":26,"target":11,"value":79.329},{"source":44,"target":15,"value":9.452},{"source":45,"target":1,"value":182.01},{"source":46,"target":15,"value":19.013},{"source":47,"target":15,"value":289.366}]} diff --git a/demos/assets/sankey.js b/demos/assets/sankey.js new file mode 100644 index 0000000000..22b6bf3fec --- /dev/null +++ b/demos/assets/sankey.js @@ -0,0 +1,292 @@ +d3.sankey = function() { + var sankey = {}, + nodeWidth = 24, + nodePadding = 8, + size = [1, 1], + nodes = [], + links = []; + + sankey.nodeWidth = function(_) { + if (!arguments.length) return nodeWidth; + nodeWidth = +_; + return sankey; + }; + + sankey.nodePadding = function(_) { + if (!arguments.length) return nodePadding; + nodePadding = +_; + return sankey; + }; + + sankey.nodes = function(_) { + if (!arguments.length) return nodes; + nodes = _; + return sankey; + }; + + sankey.links = function(_) { + if (!arguments.length) return links; + links = _; + return sankey; + }; + + sankey.size = function(_) { + if (!arguments.length) return size; + size = _; + return sankey; + }; + + sankey.layout = function(iterations) { + computeNodeLinks(); + computeNodeValues(); + computeNodeBreadths(); + computeNodeDepths(iterations); + computeLinkDepths(); + return sankey; + }; + + sankey.relayout = function() { + computeLinkDepths(); + return sankey; + }; + + sankey.link = function() { + var curvature = .5; + + function link(d) { + var x0 = d.source.x + d.source.dx, + x1 = d.target.x, + xi = d3.interpolateNumber(x0, x1), + x2 = xi(curvature), + x3 = xi(1 - curvature), + y0 = d.source.y + d.sy + d.dy / 2, + y1 = d.target.y + d.ty + d.dy / 2; + return "M" + x0 + "," + y0 + + "C" + x2 + "," + y0 + + " " + x3 + "," + y1 + + " " + x1 + "," + y1; + } + + link.curvature = function(_) { + if (!arguments.length) return curvature; + curvature = +_; + return link; + }; + + return link; + }; + + // Populate the sourceLinks and targetLinks for each node. + // Also, if the source and target are not objects, assume they are indices. + function computeNodeLinks() { + nodes.forEach(function(node) { + node.sourceLinks = []; + node.targetLinks = []; + }); + links.forEach(function(link) { + var source = link.source, + target = link.target; + if (typeof source === "number") source = link.source = nodes[link.source]; + if (typeof target === "number") target = link.target = nodes[link.target]; + source.sourceLinks.push(link); + target.targetLinks.push(link); + }); + } + + // Compute the value (size) of each node by summing the associated links. + function computeNodeValues() { + nodes.forEach(function(node) { + node.value = Math.max( + d3.sum(node.sourceLinks, value), + d3.sum(node.targetLinks, value) + ); + }); + } + + // Iteratively assign the breadth (x-position) for each node. + // Nodes are assigned the maximum breadth of incoming neighbors plus one; + // nodes with no incoming links are assigned breadth zero, while + // nodes with no outgoing links are assigned the maximum breadth. + function computeNodeBreadths() { + var remainingNodes = nodes, + nextNodes, + x = 0; + + while (remainingNodes.length) { + nextNodes = []; + remainingNodes.forEach(function(node) { + node.x = x; + node.dx = nodeWidth; + node.sourceLinks.forEach(function(link) { + nextNodes.push(link.target); + }); + }); + remainingNodes = nextNodes; + ++x; + } + + // + moveSinksRight(x); + scaleNodeBreadths((width - nodeWidth) / (x - 1)); + } + + function moveSourcesRight() { + nodes.forEach(function(node) { + if (!node.targetLinks.length) { + node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1; + } + }); + } + + function moveSinksRight(x) { + nodes.forEach(function(node) { + if (!node.sourceLinks.length) { + node.x = x - 1; + } + }); + } + + function scaleNodeBreadths(kx) { + nodes.forEach(function(node) { + node.x *= kx; + }); + } + + function computeNodeDepths(iterations) { + var nodesByBreadth = d3.nest() + .key(function(d) { return d.x; }) + .sortKeys(d3.ascending) + .entries(nodes) + .map(function(d) { return d.values; }); + + // + initializeNodeDepth(); + resolveCollisions(); + for (var alpha = 1; iterations > 0; --iterations) { + relaxRightToLeft(alpha *= .99); + resolveCollisions(); + relaxLeftToRight(alpha); + resolveCollisions(); + } + + function initializeNodeDepth() { + var ky = d3.min(nodesByBreadth, function(nodes) { + return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value); + }); + + nodesByBreadth.forEach(function(nodes) { + nodes.forEach(function(node, i) { + node.y = i; + node.dy = node.value * ky; + }); + }); + + links.forEach(function(link) { + link.dy = link.value * ky; + }); + } + + function relaxLeftToRight(alpha) { + nodesByBreadth.forEach(function(nodes, breadth) { + nodes.forEach(function(node) { + if (node.targetLinks.length) { + var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value); + node.y += (y - center(node)) * alpha; + } + }); + }); + + function weightedSource(link) { + return center(link.source) * link.value; + } + } + + function relaxRightToLeft(alpha) { + nodesByBreadth.slice().reverse().forEach(function(nodes) { + nodes.forEach(function(node) { + if (node.sourceLinks.length) { + var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value); + node.y += (y - center(node)) * alpha; + } + }); + }); + + function weightedTarget(link) { + return center(link.target) * link.value; + } + } + + function resolveCollisions() { + nodesByBreadth.forEach(function(nodes) { + var node, + dy, + y0 = 0, + n = nodes.length, + i; + + // Push any overlapping nodes down. + nodes.sort(ascendingDepth); + for (i = 0; i < n; ++i) { + node = nodes[i]; + dy = y0 - node.y; + if (dy > 0) node.y += dy; + y0 = node.y + node.dy + nodePadding; + } + + // If the bottommost node goes outside the bounds, push it back up. + dy = y0 - nodePadding - size[1]; + if (dy > 0) { + y0 = node.y -= dy; + + // Push any overlapping nodes back up. + for (i = n - 2; i >= 0; --i) { + node = nodes[i]; + dy = node.y + node.dy + nodePadding - y0; + if (dy > 0) node.y -= dy; + y0 = node.y; + } + } + }); + } + + function ascendingDepth(a, b) { + return a.y - b.y; + } + } + + function computeLinkDepths() { + nodes.forEach(function(node) { + node.sourceLinks.sort(ascendingTargetDepth); + node.targetLinks.sort(ascendingSourceDepth); + }); + nodes.forEach(function(node) { + var sy = 0, ty = 0; + node.sourceLinks.forEach(function(link) { + link.sy = sy; + sy += link.dy; + }); + node.targetLinks.forEach(function(link) { + link.ty = ty; + ty += link.dy; + }); + }); + + function ascendingSourceDepth(a, b) { + return a.source.y - b.source.y; + } + + function ascendingTargetDepth(a, b) { + return a.target.y - b.target.y; + } + } + + function center(node) { + return node.y + node.dy / 2; + } + + function value(link) { + return link.value; + } + + return sankey; +}; diff --git a/demos/custom-tree-graph.html b/demos/custom-tree-graph.html new file mode 100644 index 0000000000..0348c10fab --- /dev/null +++ b/demos/custom-tree-graph.html @@ -0,0 +1,125 @@ + + + + + Title + + +
+ + + + + + diff --git a/demos/er-diagram.html b/demos/er-diagram.html new file mode 100644 index 0000000000..e61c81d5fb --- /dev/null +++ b/demos/er-diagram.html @@ -0,0 +1,229 @@ + + + + + Title + + +
+ + + + diff --git a/demos/file-system.html b/demos/file-system.html new file mode 100644 index 0000000000..7c90b12156 --- /dev/null +++ b/demos/file-system.html @@ -0,0 +1,155 @@ + + + + + Title + + +
+ + + + + diff --git a/demos/radial-tree.html b/demos/radial-tree.html new file mode 100644 index 0000000000..805ff29c0d --- /dev/null +++ b/demos/radial-tree.html @@ -0,0 +1,84 @@ + + + + + Title + + + +
+ + + + + + diff --git a/demos/sankey-diagram.html b/demos/sankey-diagram.html new file mode 100644 index 0000000000..cccdde9cf3 --- /dev/null +++ b/demos/sankey-diagram.html @@ -0,0 +1,99 @@ + + + + + Title + + +
+ + + + + + + diff --git a/demos/tree-graph.html b/demos/tree-graph.html index f6184b985e..d9e92dd0f5 100644 --- a/demos/tree-graph.html +++ b/demos/tree-graph.html @@ -8,16 +8,15 @@ -
diff --git a/src/behavior/collapse-expand.js b/src/behavior/collapse-expand.js index f06feb8696..3a02710d6f 100644 --- a/src/behavior/collapse-expand.js +++ b/src/behavior/collapse-expand.js @@ -24,7 +24,7 @@ module.exports = { }, onNodeClick(e) { const item = e.item; - const children = item.get('model').children; + const children = item.get('model').data.children; // 叶子节点的收缩和展开没有意义 if (!children || children.length === 0) { return; @@ -62,8 +62,25 @@ module.exports = { this.animateChild(data); this.performAnimate(); } else { - // 仅有重布局 - this.graph.changeData(data); + const autoPaint = graph.get('autoPaint'); + graph.setAutoPaint(false); + Util.traverseTree(data, child => { + const node = graph.findById(child.id); + node.get('model').x = child.x; + node.get('model').y = child.y; + }); + graph.refresh(); + Util.traverseTree(item.get('model').data, child => { + const node = graph.findById(child.id); + if (node === item) { return; } + if (isCollapsed) { + graph.hideItem(node); + } else { + graph.showItem(node); + } + }); + graph.paint(); + graph.setAutoPaint(autoPaint); } } else { if (this.animate) { diff --git a/src/item/edge.js b/src/item/edge.js index 872b7e23f6..ccbc003158 100644 --- a/src/item/edge.js +++ b/src/item/edge.js @@ -130,27 +130,20 @@ class Edge extends Item { return this.get(pointName); } - _getLoopCfgs(cfg) { - const item = this.get('source'); - return Util.getLoopCfgs(item, cfg); - } - getShapeCfg(model) { const self = this; - const linkCenter = this.get('linkCenter'); // 如果连接到中心,忽视锚点、忽视控制点 - let cfg = super.getShapeCfg(model); - if (self.get('source') === self.get('target')) { - cfg = this._getLoopCfgs(cfg); - return cfg; - } + const linkCenter = self.get('linkCenter'); // 如果连接到中心,忽视锚点、忽视控制点 + const cfg = super.getShapeCfg(model); if (linkCenter) { - cfg.startPoint = this._getEndCenter('source'); - cfg.endPoint = this._getEndCenter('target'); + cfg.startPoint = self._getEndCenter('source'); + cfg.endPoint = self._getEndCenter('target'); } else { - const controlPoints = cfg.controlPoints || this._getControlPointsByCenter(cfg); - cfg.startPoint = this._getLinkPoint('source', model, controlPoints); - cfg.endPoint = this._getLinkPoint('target', model, controlPoints); + const controlPoints = cfg.controlPoints || self._getControlPointsByCenter(cfg); + cfg.startPoint = self._getLinkPoint('source', model, controlPoints); + cfg.endPoint = self._getLinkPoint('target', model, controlPoints); } + cfg.sourceNode = self.get('sourceNode'); + cfg.targetNode = self.get('targetNode'); return cfg; } diff --git a/src/item/item.js b/src/item/item.js index 2cfaa1306f..f8647cf269 100644 --- a/src/item/item.js +++ b/src/item/item.js @@ -180,6 +180,8 @@ class Item { self.set('keyShape', keyShape); self.set('originStyle', this.getKeyShapeStyle()); } + // 防止由于用户外部修改 model 中的 shape 导致 shape 不更新 + this.set('currentShape', shapeType); this._resetStates(shapeFactory, shapeType); } @@ -371,6 +373,7 @@ class Item { const shape = model.shape; const newModel = Util.mix({}, model, cfg); const onlyMove = this._isOnlyMove(cfg); + // 仅仅移动位置时,既不更新,也不重绘 if (onlyMove) { this.updatePosition(newModel); @@ -379,7 +382,7 @@ class Item { // 1. 注册的元素(node, edge)允许更新 // 2. 更新的信息中没有指定 shape // 3. 更新信息中指定了 shape 同时等于原先的 shape - if (shapeFactory.shouldUpdate(shape) && newModel.shape === shape) { + if (shapeFactory.shouldUpdate(shape) && newModel.shape === this.get('currentShape')) { const updateCfg = this.getShapeCfg(newModel); // 如果 x,y 发生改变,则重置位置 // 非 onlyMove ,不代表不 move diff --git a/src/shape/edge.js b/src/shape/edge.js index 9cbe3f93e1..1345d4bc73 100644 --- a/src/shape/edge.js +++ b/src/shape/edge.js @@ -61,6 +61,7 @@ const singleEdgeDefinition = Util.mix({}, SingleShapeMixin, { getShapeStyle(cfg) { const color = cfg.color || Global.defaultEdge.color; const size = cfg.size || Global.defaultEdge.size; + cfg = this.getPathPoints(cfg); const startPoint = cfg.startPoint; const endPoint = cfg.endPoint; const controlPoints = this.getControlPoints(cfg); @@ -181,6 +182,14 @@ const singleEdgeDefinition = Util.mix({}, SingleShapeMixin, { getControlPoints(cfg) { return cfg.controlPoints; }, + /** + * @internal 处理需要重计算点和边的情况 + * @param {Object} cfg 边的配置项 + * @return {Object} 边的配置项 + */ + getPathPoints(cfg) { + return cfg; + }, /** * 绘制边 * @override @@ -296,3 +305,9 @@ Shape.registerEdge('cubic-horizontal', { return controlPoints; } }, 'cubic'); + +Shape.registerEdge('loop', { + getPathPoints(cfg) { + return Util.getLoopCfgs(cfg); + } +}, 'cubic'); diff --git a/src/util/graphic.js b/src/util/graphic.js index e8955f6e11..e43f01e912 100644 --- a/src/util/graphic.js +++ b/src/util/graphic.js @@ -45,7 +45,8 @@ const GraphicUtil = { }; }, // 获取某元素的自环边配置 - getLoopCfgs(item, cfg) { + getLoopCfgs(cfg) { + const item = cfg.sourceNode || cfg.targetNode; const containerMatrix = item.get('group') .getMatrix(); const bbox = item.getKeyShape() @@ -61,50 +62,53 @@ const GraphicUtil = { const center = [ containerMatrix[ 6 ], containerMatrix[ 7 ] ]; const sinDelta = r * SELF_LINK_SIN; const cosDelta = r * SELF_LINK_COS; - let startPoint, - endPoint; - switch (position) { - case 'top': - startPoint = [ center[0] - sinDelta, center[1] - cosDelta ]; - endPoint = [ center[0] + sinDelta, center[1] - cosDelta ]; - break; - case 'top-right': - startPoint = [ center[0] + sinDelta, center[1] - cosDelta ]; - endPoint = [ center[0] + cosDelta, center[1] - sinDelta ]; - break; - case 'right': - startPoint = [ center[0] + cosDelta, center[1] - sinDelta ]; - endPoint = [ center[0] + cosDelta, center[1] + sinDelta ]; - break; - case 'bottom-right': - startPoint = [ center[0] + cosDelta, center[1] + sinDelta ]; - endPoint = [ center[0] + sinDelta, center[1] + cosDelta ]; - break; - case 'bottom': - startPoint = [ center[0] + sinDelta, center[1] + cosDelta ]; - endPoint = [ center[0] - sinDelta, center[1] + cosDelta ]; - break; - case 'bottom-left': - startPoint = [ center[0] - sinDelta, center[1] + cosDelta ]; - endPoint = [ center[0] - cosDelta, center[1] + sinDelta ]; - break; - case 'left': - startPoint = [ center[0] - cosDelta, center[1] + sinDelta ]; - endPoint = [ center[0] - cosDelta, center[1] - sinDelta ]; - break; - case 'top-left': - startPoint = [ center[0] - cosDelta, center[1] - sinDelta ]; - endPoint = [ center[0] - sinDelta, center[1] - cosDelta ]; - break; - default: - startPoint = [ center[0] - sinDelta, center[1] - cosDelta ]; - endPoint = [ center[0] + sinDelta, center[1] - cosDelta ]; - } - // 如果逆时针画,交换起点和终点 - if (loopCfg.clockwise === false) { - const swap = [ startPoint[0], startPoint[1] ]; - startPoint = [ endPoint[0], endPoint[1] ]; - endPoint = [ swap[0], swap[1] ]; + let startPoint = [ cfg.startPoint.x, cfg.startPoint.y ]; + let endPoint = [ cfg.endPoint.x, cfg.endPoint.y ]; + // 如果定义了锚点的,直接用锚点坐标,否则,根据自环的 cfg 计算 + if (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1]) { + switch (position) { + case 'top': + startPoint = [ center[0] - sinDelta, center[1] - cosDelta ]; + endPoint = [ center[0] + sinDelta, center[1] - cosDelta ]; + break; + case 'top-right': + startPoint = [ center[0] + sinDelta, center[1] - cosDelta ]; + endPoint = [ center[0] + cosDelta, center[1] - sinDelta ]; + break; + case 'right': + startPoint = [ center[0] + cosDelta, center[1] - sinDelta ]; + endPoint = [ center[0] + cosDelta, center[1] + sinDelta ]; + break; + case 'bottom-right': + startPoint = [ center[0] + cosDelta, center[1] + sinDelta ]; + endPoint = [ center[0] + sinDelta, center[1] + cosDelta ]; + break; + case 'bottom': + startPoint = [ center[0] + sinDelta, center[1] + cosDelta ]; + endPoint = [ center[0] - sinDelta, center[1] + cosDelta ]; + break; + case 'bottom-left': + startPoint = [ center[0] - sinDelta, center[1] + cosDelta ]; + endPoint = [ center[0] - cosDelta, center[1] + sinDelta ]; + break; + case 'left': + startPoint = [ center[0] - cosDelta, center[1] + sinDelta ]; + endPoint = [ center[0] - cosDelta, center[1] - sinDelta ]; + break; + case 'top-left': + startPoint = [ center[0] - cosDelta, center[1] - sinDelta ]; + endPoint = [ center[0] - sinDelta, center[1] - cosDelta ]; + break; + default: + startPoint = [ center[0] - sinDelta, center[1] - cosDelta ]; + endPoint = [ center[0] + sinDelta, center[1] - cosDelta ]; + } + // 如果逆时针画,交换起点和终点 + if (loopCfg.clockwise === false) { + const swap = [ startPoint[0], startPoint[1] ]; + startPoint = [ endPoint[0], endPoint[1] ]; + endPoint = [ swap[0], swap[1] ]; + } } const startVec = [ startPoint[0] - center[0], startPoint[1] - center[1] ]; const startExtendVec = BaseUtil.vec2.scale([], startVec, scaleRate); diff --git a/test/unit/graph/graph-spec.js b/test/unit/graph/graph-spec.js index bf74cbfb40..b018415dcb 100644 --- a/test/unit/graph/graph-spec.js +++ b/test/unit/graph/graph-spec.js @@ -278,56 +278,57 @@ describe('all node link center', () => { expect(edge.get('keyShape').attr('path')).eqls([[ 'M', 10, 10 ], [ 'L', 100, 100 ]]); }); it('loop', () => { - const node = graph.addItem('node', { id: 'circleNode', x: 150, y: 150, style: { fill: 'yellow' } }); - const edge1 = graph.addItem('edge', { id: 'edge', source: node, target: node, + graph.set('linkCenter', false); + const node = graph.addItem('node', { id: 'circleNode', x: 150, y: 150, style: { fill: 'yellow' }, anchorPoints: [[ 0, 0 ], [ 0, 1 ]] }); + const edge1 = graph.addItem('edge', { id: 'edge', source: node, target: node, shape: 'loop', loopCfg: { position: 'top', dist: 60, clockwise: true }, style: { endArrow: true } }); - const edge2 = graph.addItem('edge', { id: 'edge1', source: node, target: node, + const edge2 = graph.addItem('edge', { id: 'edge1', source: node, target: node, shape: 'loop', loopCfg: { position: 'top-left', dist: 60, clockwise: false }, style: { endArrow: true } }); - const edge3 = graph.addItem('edge', { id: 'edge2', source: node, target: node, shape: 'cubic', + const edge3 = graph.addItem('edge', { id: 'edge2', source: node, target: node, shape: 'loop', loopCfg: { position: 'top-right', dist: 60 }, style: { endArrow: true } }); - const edge4 = graph.addItem('edge', { id: 'edge4', source: node, target: node, shape: 'cubic', + const edge4 = graph.addItem('edge', { id: 'edge4', source: node, target: node, shape: 'loop', loopCfg: { position: 'right', dist: 60, clockwise: true }, style: { endArrow: true } }); - graph.addItem('edge', { id: 'edge5', source: node, target: node, shape: 'cubic', + const edgeWithAnchor = graph.addItem('edge', { id: 'edge5', source: node, target: node, shape: 'loop', sourceAnchor: 0, targetAnchor: 1, loopCfg: { position: 'bottom-right', dist: 60, clockwise: true }, style: { endArrow: true } }); - graph.addItem('edge', { id: 'edge6', source: node, target: node, shape: 'cubic', + graph.addItem('edge', { id: 'edge6', source: node, target: node, shape: 'loop', loopCfg: { position: 'bottom', dist: 60, clockwise: true }, style: { endArrow: true } }); - graph.addItem('edge', { id: 'edge7', source: node, target: node, shape: 'cubic', + graph.addItem('edge', { id: 'edge7', source: node, target: node, shape: 'loop', loopCfg: { position: 'bottom-left', dist: 60, clockwise: true }, style: { endArrow: true } }); - graph.addItem('edge', { id: 'edge8', source: node, target: node, shape: 'cubic', + graph.addItem('edge', { id: 'edge8', source: node, target: node, shape: 'loop', loopCfg: { position: 'left', dist: 60, @@ -341,6 +342,12 @@ describe('all node link center', () => { expect(edge3.getKeyShape().attr('path')[0][1]).to.equal(edgeShape[1][5]); expect(edge4.getKeyShape().attr('path')[0][1]).to.equal(edge3.getKeyShape().attr('path')[1][5]); expect(edge4.getKeyShape().attr('path')[0][2]).to.equal(edge3.getKeyShape().attr('path')[1][6]); + const pathWithAnchor = edgeWithAnchor.getKeyShape().attr('path'); + expect(pathWithAnchor[0][1]).to.equal(129.5); + expect(pathWithAnchor[0][2]).to.equal(129.5); + expect(pathWithAnchor[1][0]).to.equal('C'); + expect(pathWithAnchor[1][5]).to.equal(129.5); + expect(pathWithAnchor[1][6]).to.equal(170.5); }); it('clear states', () => { graph.clear();