(function($){ $.fn.geoflow = function(options){ var providername = options.providername; var NPI = options.NPI; var color = d3.scale.category20(); var LEAKAGE_LINES = '#d62728', LOYAL_LINES = '#005C85', COMPETITOR_NODES = '#756bb1', SELECTED_HOSPITAL_NODE = '#2ca02c', LEAKAGE_THRESHOLD = [0, 12.5, 25.0, 37.5, 50.0, 62.5, 75.0, 87.5], LINK_OPACITY = 0.6, NODE_OPACITY = 0.7, MAP_ZOOM = 8, RECENT_LIST_LENGTH = 9, // legend vars SVG_CIRCLE_HEIGHT = 16, SVG_CIRCLE_WIDTH = 20, LEGEND_CX = 8, LEGEND_CY = 8, LEGEND_RADIUS = 8, SVG_LINE_HEIGHT = 5, SVG_LINE_WIDTH = 20, LEGEND_STROKE_WIDTH = 5, LEGEND_X1 = 0, LEGEND_Y1 = 0, LEGEND_X2 = 16, LEGEND_Y2 = 0; var uniq = function(a) { return Array.from(new Set(a)); } var getCheckedInputs = function(opt){ // get all checked NPIs var checked = [] d3.selectAll('.recentInputList')[0].forEach(function(v, k){ if (opt == 'checked'){ if ($(v)[0].checked){ checked.push($(v)[0].id) } } else if (opt == 'all'){ checked.push($(v)[0].id) } }) return checked; }; $('body').append('') // Set leaflet map var map = new L.map(this[0].id, { center: new L.LatLng(39.095963, -95.976563), // center: new L.LatLng(options.center.lat, options.center.lon), zoom: options.zoom, layers: [ new L.tileLayer('http://{s}tile.stamen.com/toner-lite/{z}/{x}/{y}.png', { subdomains: ['','a.','b.','c.','d.'], attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA' }) ] }); // Initialize the SVG layer map._initPathRoot(); map.doubleClickZoom.disable(); // Setup svg element to work with var svg = d3.select(this.selector).select("svg"), linklayer = svg.append("g"), nodelayer = svg.append("g"), headerText, bodyText, showingAll = false, hoveredOverHospitalNode = false, selectedNPI = NPI, linkType = 'target', ignoreHover = false; var formatNumber = d3.format(",d"); var linksLegend = L.control({position: 'bottomright'}); linksLegend.onAdd = function (map) { var div = L.DomUtil.create('div', 'info legend'); div.innerHTML = '
' + '' + '' + ' Visits to Similar Providers' + '
' + '' + ' Visits to Selected Provider' + '
' + '' + '' + ' Similar Providers' + '
' + '' + '' + ' Selected Provider' + '
' return div; }; linksLegend.addTo(map); var legend = L.control({position: 'bottomright'}); legend.onAdd = function (map) { var div = L.DomUtil.create('div', 'info legend'); // loop through our density intervals and generate a label with a colored square for each interval for (var i = 0; i < LEAKAGE_THRESHOLD.length; i++) { div.innerHTML += (i == 0 ? "

Leakage %

" : '') + '' + '' + '' + '' + (LEAKAGE_THRESHOLD[i]+(i==0 ? 0 : 0)) + (LEAKAGE_THRESHOLD[i + 1] ? ' – ' + LEAKAGE_THRESHOLD[i + 1] + '
' : '+') } return div; }; legend.addTo(map); var ignoreHoverLegend = L.control({position: 'topright'}); ignoreHoverLegend.onAdd = function (map) { var div = L.DomUtil.create('div', 'info legend'); div.innerHTML = '
' + '
' + '
' return div; }; ignoreHoverLegend.addTo(map); $('#ignoreHover').on('click', function(){ if (!ignoreHover){ ignoreHover = true; } else { ignoreHover = false; } }); var recentLegend = L.control({position: 'topright'}); recentLegend.onAdd = function (map) { var div = L.DomUtil.create('div', 'info legend'); div.innerHTML = '

Highlight Links

' + '
' + '
' + '
' return div; }; recentLegend.addTo(map); // Load data asynchronosuly d3.json(options.endpoint, function(geojson){ // .get data\ // bind click event to selected hospital NPI in Recent Links on page load $('#'+NPI).on('click', function(){ hideSpecificLinks(NPI); if ($(this)[0].checked){ specificLinks(NPI); } }); map.panTo(new L.LatLng(geojson.source.lat, geojson.source.lng)); map.zoomIn(MAP_ZOOM); var nodes = geojson.nodes; var links = geojson.links; var radiusFromLatLon = function(latIn, lonIn, radius){ var locs = []; var lat1 = parseFloat(latIn) * Math.PI/180.0; var lon1 = parseFloat(lonIn) * Math.PI/180.0; var d = radius/3956.0; var range360 = Array.apply(null, Array(360)).map(function (_, i) {return i;}); for (var i in range360){ var lon, loc; var tc = (range360[i] / 90.0) * Math.PI/2.0; var lat = Math.asin(Math.sin(lat1)*Math.cos(d)+Math.cos(lat1)*Math.sin(d)*Math.cos(tc)); lat = (180.0 * lat) / Math.PI; if (Math.cos(lat1) == 0){ lon = lonIn; } else { lon = ((lon1 - Math.asin(Math.sin(tc) * Math.sin(d)/Math.cos(lat1)) + Math.PI) % (2 * Math.PI)) - Math.PI; } lon = (180.0 * lon) / Math.PI; loc = [lat, lon]; locs.push(loc); } return locs; } var features = geojson.nodes.features; var featureKeys = Object.keys(features); var geocontainer = {}; for (var i in featureKeys){ var npi = featureKeys[i]; var lat = features[npi].geometry.coordinates[0]; var lon = features[npi].geometry.coordinates[1]; var key = lat + '|' + lon; if (!(key in geocontainer)){ geocontainer[key] = { npis: [npi], coords: { lat: lat, lon: lon } }; } else { geocontainer[key].npis.push(npi); } } var radius = 2; var geocontainerKeys = Object.keys(geocontainer); for (var i in geocontainerKeys){ var latLonKey = geocontainer[geocontainerKeys[i]] var npilist = latLonKey.npis; if (npilist.length > 1){ var coords = latLonKey.coords; var lat = coords.lat; var lon = coords.lon; var circlePoints = radiusFromLatLon(lat, lon, radius); countby = ((360/npilist.length) < 2) ? 2 : Math.floor(360/npilist.length); for (var index=0, j=0; index 500 ? d3.event.pageY - 140 : d3.event.pageY + 20; if (d.source && d.target){ headerText = null; if (hoveredOverHospitalNode){ bodyText = '

' + nodes.features[d.target].name + ' recieves ' + formatNumber(d.flow) + ' visits from ' + nodes.features[d.source].name + '

'; } else { bodyText = '

' + nodes.features[d.source].name + ' refers ' + formatNumber(d.flow) + ' visits to ' + nodes.features[d.target].name + '

'; } } else { var firstname = nodes.features[d].name.replace(".", "").split(" ")[0]; var lastname = nodes.features[d].name.replace(".", "").split(" ")[1]; var inputs = getCheckedInputs("all"); if (inputs.indexOf(nodes.features[d].npi) == -1){ $('#recentLegend').prepend('
'); } headerText = nodes.features[d].name; var inputs = d3.selectAll("#recentLegend > label")[0]; inputs.forEach(function(x, i){ if (i > RECENT_LIST_LENGTH){ $(inputs[i]).next('br').remove(); $(inputs[i]).remove(); } }); $('#'+nodes.features[d].npi).on('click', function(){ hideSpecificLinks(nodes.features[d].npi) if ($(this)[0].checked){ specificLinks(nodes.features[d].npi); } }); if (hoveredOverHospitalNode){ bodyText = '

Inflow: ' + formatNumber(nodes.features[d].aggregate_inflows) } else { bodyText = '

Outflow: ' + formatNumber(nodes.features[d].aggregate_outflows) + '

' + '

Total Charges: $' + formatNumber(nodes.features[d].charges) + '

' + '

Leakage: ' + Math.round(nodes.features[d].leakage*10000)/100 + '%' } } d3.select("#tooltip") .style("left", xPosition + "px") .style("top", yPosition + "px"); d3.select("#tooltip #heading") .html(function(){ return headerText; }); d3.select("#tooltip #visits") .html(function(){ return bodyText; }); d3.select("#tooltip").classed("hidden", false); }; var mouseHideTooltip = function() { d3.select("#tooltip").classed("hidden", true); }; var hideSpecificLinks = function(npi){ var checked = getCheckedInputs("checked"); linklayer.selectAll("path").remove(); // Get link data for this node var nodelinks = spatialsankey.links().filter(function(link){ return checked.indexOf(link.target) > -1 || checked.indexOf(link.source) > -1; }); // Add data to link layer var beziers = linklayer.selectAll("path").data(nodelinks); link = spatialsankey.link(options) // Draw new links beziers.enter() .append("path") .attr("d", link) .attr('id', function(d){return npi}) .style("stroke-width", spatialsankey.link().width()) .style("stroke", function(a){ if (!showingAll){ return a.source == NPI || a.target == NPI ? LOYAL_LINES : LEAKAGE_LINES; } }) .on("mousemove", mouseShowTooltip) .on("mouseout", mouseHideTooltip); // Remove old links beziers.exit().remove(); if (showingAll){ beziers.transition().style("stroke", function(link){ return link.source == npi ? LEAKAGE_LINES : LOYAL_LINES && link.target == npi ? LEAKAGE_LINES : LOYAL_LINES; }); }; if (linkType == 'target' || linkType == 'comp'){ hoveredOverHospitalNode = true; } else { hoveredOverHospitalNode = false; } // Hide inactive nodes var circleUnderMouse = this; circs.transition().style('opacity',function (d) { var checked = getCheckedInputs('checked'); return (nodes.features[d].type == "target" || nodes.features[d].type == "comp" || this === circleUnderMouse || checked.indexOf(nodes.features[d].npi) > -1) ? NODE_OPACITY : 0; }); // show all nodes when input has none checked if (checked.length == 0){ circs.call(mouseout); } } var specificLinks = function(npi){ // Hide inactive nodes var circleUnderMouse = this; circs.transition().style('opacity',function (d) { // return (this === circleUnderMouse) ? NODE_OPACITY : 0; var checked = getCheckedInputs('checked'); return (nodes.features[d].type == "target" || nodes.features[d].type == "comp" || this === circleUnderMouse || checked.indexOf(nodes.features[d].npi) > -1) ? NODE_OPACITY : 0; }); }; // hide tooltip when clicking outside circ $('body').on('click', function(e){ var container = $('#tooltip'); if (!container.is(e.target) && e.target.nodeName == 'svg'){ d3.select("#tooltip").classed("hidden", true); } }); // hide tooltip on zoom start map.on('zoomstart', function() { d3.select("#tooltip").classed("hidden", true); }); // hide tooltip on move start map.on('movestart', function(){ d3.select("#tooltip").classed("hidden", true); }); // Draw nodes var node = spatialsankey.node(); var circs = nodelayer.selectAll("circle") .data(Object.keys(nodes.features)) .enter() .append("circle") .attr("cx", node.cx) .attr("cy", node.cy) .attr("r", node.r) .style("fill", function(d){ return node.fill(d, NPI); }) .attr("opacity", NODE_OPACITY) .on('mouseover', mouseover) .on("mouseup", mouseShowTooltip) .on("mouseout", mouseout); // Adopt size of drawn objects after leaflet zoom reset var zoomend = function(){ linklayer.selectAll("path").attr("d", spatialsankey.link()); circs.attr("cx", node.cx) .attr("cy", node.cy); }; map.on("zoomend", zoomend); $('#loading').hide(); }); // end .get data } return this })(jQuery);