var charts = {}; charts.bar = function() { // basic data var margin = {top: 20, bottom: 20, left: 0, right: 0}, width = 400, height = 400, // accessors xValue = function(d) { return d.x; }, yValue = function(d) { return d.y; }, // chart underpinnings brush = d3.svg.brush(), xAxis = d3.svg.axis().orient('bottom'), yAxis = d3.svg.axis().orient('left'), x = d3.scale.ordinal(), y = d3.scale.linear(), // chart enhancements elastic = { margin: true, x: true, y: true }, convertData = true, duration = 500, formatNumber = d3.format(',d'); function render(selection) { selection.each(function(data) { // setup the basics if (elastic.margin) margin.left = formatNumber(d3.max(data, function(d) { return d.y; })).length * 15; var w = width - margin.left - margin.right, h = height - margin.top - margin.bottom; // if needed convert the data if (convertData) { data = data.map(function(d, i) { return { x: xValue.call(data, d, i), y: yValue.call(data, d, i) }; }); } // set scales if (elastic.x) x.domain(data.map(function(d) { return d.x; })); if (elastic.y) y.domain([0, d3.max(data, function(d) { return d.y; })]); x.rangeRoundBands([0, w], .1); y.range([h, 0]); // reset axes and brush xAxis.scale(x); yAxis.scale(y); brush.x(x) .on('brushstart.chart', brushstart) .on('brush.chart', brushmove) .on('brushend.chart', brushend); brush.clear(); var svg = selection.selectAll('svg').data([data]), chartEnter = svg.enter().append('svg') .append('g') .attr('width', w) .attr('height', h) .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .classed('chart', true), chart = svg.select('.chart'); chartEnter.append('g') .classed('x axis', true) .attr('transform', 'translate(' + 0 + ',' + h + ')'); chartEnter.append('g') .classed('y axis', true) chartEnter.append('g').classed('barGroup', true); chart.selectAll('.brush').remove(); chart.selectAll('.selected').classed('selected', false); chart.append('g') .classed('brush', true) .call(brush) .selectAll('rect') .attr('height', h); bars = chart.select('.barGroup').selectAll('.bar').data(data); bars.enter() .append('rect') .classed('bar', true) .attr('x', w) // start here for object constancy .attr('width', x.rangeBand()) .attr('y', function(d, i) { return y(d.y); }) .attr('height', function(d, i) { return h - y(d.y); }); bars.transition() .duration(duration) .style('opacity', 1) .attr('width', x.rangeBand()) .attr('x', function(d, i) { return x(d.x); }) .attr('y', function(d, i) { return y(d.y); }) .attr('height', function(d, i) { return h - y(d.y); }); bars.exit() .transition() .duration(duration) .style('opacity', 0) .remove(); chart.select('.x.axis') .transition() .duration(duration) .call(xAxis); chart.select('.y.axis') .transition() .duration(duration) .call(yAxis); function brushstart() { chart.classed("selecting", true); } function brushmove() { var extent = d3.event.target.extent(); selection.selectAll('.bar').classed("selected", function(d) { return extent[0] <= x(d.x) && x(d.x) + x.rangeBand() <= extent[1]; }); } function brushend() { chart.classed("selecting", !d3.event.target.empty()); } }); } // basic data render.margin = function(_) { if (!arguments.length) return margin; margin = _; return render; }; render.width = function(_) { if (!arguments.length) return width; width = _; return render; }; render.height = function(_) { if (!arguments.length) return height; height = _; return render; }; // accessors render.xValue = function(_) { if (!arguments.length) return xValue; xValue = _; return render; }; render.yValue = function(_) { if (!arguments.length) return yValue; yValue = _; return render; }; // chart underpinnings render.brush = function(_) { if (!arguments.length) return brush; brush = _; return render; }; render.xAxis = function(_) { if (!arguments.length) return xAxis; xAxis = _; return render; }; render.yAxis = function(_) { if (!arguments.length) return yAxis; yAxis = _; return render; }; render.x = function(_) { if (!arguments.length) return x; x = _; return render; }; render.y = function(_) { if (!arguments.length) return y; y = _; return render; }; // chart enhancements render.elastic = function(_) { if (!arguments.length) return elastic; elastic = _; return render; }; render.convertData = function(_) { if (!arguments.length) return convertData; convertData = _; return render; }; render.duration = function(_) { if (!arguments.length) return duration; duration = _; return render; }; render.formatNumber = function(_) { if (!arguments.length) return formatNumber; formatNumber = _; return render; }; return d3.rebind(render, brush, 'on'); }; charts.graph = function() { // overall size info var width = 800, height = 600, margin = {top: 0, bottom: 0, left: 0, right: 0}, // scales colors = d3.scale.category20c(), r = d3.scale.sqrt(), x = d3.scale.ordinal(), y = d3.scale.ordinal(), // accessors // top level nodesValue = function(d) { return d.nodes; }, linksValue = function(d) { return d.links; }, // nodes rValue = function(d) { return d.radius; }, xConstraintValue = function(d) { return d.xConstraint; }, yConstraintValue = function(d) { return d.yConstraint; }, colorValue = function(d) { return d.color; }, nameValue = function(d) { return d.name; }, typeValue = function(d) { return d.type; }, // links sourceValue = function(d) { return d.source; }, targetValue = function(d) { return d.target; }, distanceValue = function(d) { return d.value; }, linkTypeValue = function(d) { return d.type; } // chart underpinnings force = d3.layout.force(), // chart enhancements outline = 0, // outline of nodes padding = { collision: 5, y: .5, x: .5, }, // padding between collisions constraints = { x: false, y: false, collisions: false, bounding: false, }, elastic = { x: false, y: false }, // data = {}, duration = 500; function render(selection) { selection.each(function(data) { var w = width - margin.left - margin.right, h = height - margin.top - margin.bottom; var domain; if (elastic.x) { domain = d3.set(force.nodes().map(function(d, i) { return d.xConst; })).values(); x.domain(domain); } x.rangeRoundBands([0, w], padding.x); if (elastic.y) { domain = d3.set(force.nodes().map(function(d, i) { return d.yConst; })).values(); y.domain(domain); } y.rangeRoundBands([0, h], padding.y); force .size([w, h]); force.start(); var svg = d3.select(this).selectAll('svg').data([data]), svgEnter = svg.enter().append('svg'); svgEnter.append('g') .classed('nodes', true); svgEnter.append('g') .classed('links', true); svgEnter.append('defs'); svg.attr('width', w) .attr('height', h); // change this to allow for marker position relative to size svg.select('defs').selectAll("marker") .data(d3.set(force.links().map(function(d, i) { return d.type; })).values()) .enter() .append("svg:marker") .attr("id", function(d) { return d; }) .attr("viewBox", "0 -5 10 10") .attr("refX", 5) .attr("refY", 0) .attr("markerWidth", 4) .attr("markerHeight", 4) .attr("orient", "auto") .append("svg:path") .attr("d", "M0,-5L10,0L0,5"); var link = d3.select('.links').selectAll('.link'), links = link.data(force.links()); links.enter() .append("path") .attr('class', function(d) { return 'link ' + d.type; }) .attr("marker-end", function(d) { return "url(#" + d.type + ")"; }); var node = d3.select('.nodes').selectAll('.node'), nodes = node.data(force.nodes()), nodesEnter = nodes.enter() // .transition() // .duration(duration) .append('g') .attr('class', function(d) { return 'node ' + d.type; }) nodesEnter.append('circle') .attr('r', function(d) { return r(d.r); }) .style('fill', function(d) { return colors(d.color) || 'steelblue'; }) .style('stroke', function(d) { return d3.rgb(colors(d.color)).darker(outline) || 'orange'; }); nodesEnter.append("text") .attr("dx", function(d) { return r(d.r); }) .attr("dy", ".35em") .text(function(d) { return d.name }); nodes.call(force.drag); nodes.exit() .transition() .duration(duration) .attr('r', 0) .style('opacity', 0) .remove(); force.on("tick", function(e) { links.attr("d", function(d) { var currLink = linkPosition(d), dx = currLink.target.x - currLink.source.x, dy = currLink.target.y - currLink.source.y, dr = Math.sqrt(dx * dx + dy * dy); return "M" + currLink.source.x + "," + currLink.source.y + "A" + dr + "," + dr + " 0 0,1 " + currLink.target.x + "," + currLink.target.y; }); var q, k = e.alpha * .1, i = -1, currentNode = force.nodes()[i], n = force.nodes().length; if (constraints.collisions) q = d3.geom.quadtree(force.nodes()); while (++i < n) { currentNode = force.nodes()[i]; // x & y constraints if (constraints.y) { if (currentNode.yConst === 'Frontend') console.log(y(currentNode.yConst)) currentNode.y += (y(currentNode.yConst) - currentNode.y)*k; } if (constraints.x) currentNode.x += (x(currentNode.xConst) - currentNode.x) * k; // collision detection if (constraints.collisions) q.visit(collide(currentNode)); } // bounding box if (constraints.bounding) { nodes.attr("transform", function(d) { d.x = Math.max(d.r, Math.min(w - d.r, d.x)); d.y = Math.max(d.r, Math.min(h - d.r, d.y)); return "translate(" + d.x + "," + d.y + ")"; }); } else { nodes.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); } }); function linkPosition(d) { var tRadius = r(d.target.r), sRadius = r(d.source.r), a = Math.abs(d.target.x - d.source.x), b = Math.abs(d.target.y - d.source.y), c = Math.sqrt(a * a + b * b), tθ = Math.acos((a * a - (b * b + c * c))/(2*b*c)), sθ = Math.asin(Math.sin(tθ)*(b/a)), sdx = sRadius*Math.sin(tθ)/Math.sin(90), sdy = sRadius*Math.sin(sθ)/Math.sin(90), tdx = tRadius*Math.sin(sθ)/Math.sin(90), tdy = tRadius*Math.sin(tθ)/Math.sin(90); if (isNaN(sθ)) console.log('t:', tθ, 's:', Math.sin(tθ)*(b/a).toFixed(2)); return { source: { x: (d.target.x > d.source.x)?(d.source.x + sdx):(d.source.x - sdx), y: (d.target.y > d.source.y)?(d.source.y + sdy):(d.source.y - sdy) }, target: { x: (d.source.x > d.target.x)?(d.target.x + sdx):(d.target.x - sdx), y: (d.source.y > d.target.y)?(d.target.y + sdy):(d.target.y - sdy) } }; } function collide(node) { var radius = r(node.r) + padding.collision, nx1 = node.x - radius, nx2 = node.x + radius, ny1 = node.y - radius, ny2 = node.y + radius; return function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== node)) { var x = node.x - quad.point.x, y = node.y - quad.point.y, l = Math.sqrt(x * x + y * y), radius = r(node.r) + r(quad.point.r) + padding.collision; if (l < radius) { l = (l - radius) / l * .5; node.x -= x *= l; node.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; } } }); } // sizing info render.width = function(_) { if (!arguments.length) return width; width = _; return render; }; render.height = function(_) { if (!arguments.length) return height; height = _; return render; }; render.margin = function(_) { if (!arguments.length) return margin; margin = _; return render; } // scales render.colors = function(_) { if (!arguments.length) return colors; colors = _; return render; }; render.r = function(_) { if (!arguments.length) return r; r = _; return render; }; render.x = function(_) { if (!arguments.length) return x; x = _; return render; }; render.y = function(_) { if (!arguments.length) return y; y = _; return render; }; // accessors render.colorValue = function(_) { if (!arguments.length) return colorValue; colorValue = d3.functor(_); return render; }; render.rValue = function(_) { if (!arguments.length) return rValue; rValue = d3.functor(_); return render; }; render.xConstraintValue = function(_) { if (!arguments.length) return xConstraintValue; xConstraintValue = d3.functor(_); return render; }; render.yConstraintValue = function(_) { if (!arguments.length) return yConstraintValue; yConstraintValue = d3.functor(_); return render; }; render.nameValue = function(_) { if (!arguments.length) return nameValue; nameValue = d3.functor(_); return render; }; render.typeValue = function(_) { if (!arguments.length) return typeValue; typeValue = d3.functor(_); return render; }; render.nodesValue = function(_) { if (!arguments.length) return nodesValue; nodesValue = d3.functor(_); return render; }; render.linksValue = function(_) { if (!arguments.length) return linksValue; linksValue = d3.functor(_); return render; }; render.sourceValue = function(_) { if (!arguments.length) return sourceValue; sourceValue = d3.functor(_); return render; }; render.targetValue = function(_) { if (!arguments.length) return targetValue; targetValue = d3.functor(_); return render; }; render.distanceValue = function(_) { if (!arguments.length) return distanceValue; distanceValue = d3.functor(_); return render; }; //chart underpinnings render.force = function(_) { if (!arguments.length) return force; force = _; return render; }; render.data = function(data) { var tData = {}, nData = nodesValue(data), lData = linksValue(data); tData.nodes = nData.map(function(d, i) { return { r: r(rValue.call(nData, d, i)), //? xConst: xConstraintValue.call(nData, d, i), yConst: yConstraintValue.call(nData, d, i), color: colorValue.call(nData, d, i), name: nameValue.call(nData, d, i), type: typeValue.call(nData, d, i), }; }); tData.links = lData.map(function(d, i) { return { source: sourceValue.call(lData, d, i), target: targetValue.call(lData, d, i), value: distanceValue.call(lData, d, i), type: linkTypeValue.call(lData, d, i), }; }); data = tData; force .nodes(data.nodes) .links(data.links) }; render.push = function(type, d) { if (type === 'node') { force.nodes().push({ r: r(rValue(d)), //? xConst: xConstraintValue(d), yConst: yConstraintValue(d), color: colorValue(d), name: nameValue(d), type: typeValue(d), }); } else if (type === 'link') { force.links().push({ source: sourceValue(d), target: targetValue(d), value: distanceValue(d), type: linkTypeValue(d), }); } } // chart enhancements render.outline = function(_) { if (!arguments.length) return outline; outline = _; return render; }; render.duration = function(_) { if (!arguments.length) return duration; duration = _; return render; }; render.constraints = function(_) { if (!arguments.length) return constraints; constraints = _; return render; }; render.padding = function(_) { if (!arguments.length) return padding; padding = _; return render; }; return render; };