Skip to content

Instantly share code, notes, and snippets.

@renauld94
Created November 5, 2018 07:19
Show Gist options
  • Save renauld94/c7e746b7d4f3bbbf87823dfb9350d2e3 to your computer and use it in GitHub Desktop.
Save renauld94/c7e746b7d4f3bbbf87823dfb9350d2e3 to your computer and use it in GitHub Desktop.
fullcode
license: mit
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sanisphere</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Sanisphere coverage and Pharmacies Universe">
<meta name="author" content="Simon Renauld">
<link rel="shortcut icon" type="image/png" href="img/favicon">
</head>
<html>
<!--.Leaflet, Dynamic Charting, Crossfilter, DataTables, MarkerCluster, core JavaScript -->
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="bower_components/crossfilter/crossfilter.min.js" charset="utf-8"></script>
<script src="bower_components/dcjs/dc.min.js" charset="utf-8"></script>
<script src="bower_components/leaflet.markercluster/leaflet.markercluster.js"></script>
<script src="plugins/L.D3SvgOverlay.min.js"></script>
<script src="plugins/leaflet-bing-layer.js"></script>
<script src="plugins/leaflet-search.js"></script>
<script type="text/javascript" src='bower_components/dcjs/jquery.js'></script>
<script type="text/javascript" src="plugins/labs-common.js"></script>
<script type="text/javascript" src="geojson/ward.js"></script>
<script type="text/javascript" src="plugins/leaflet.ajax.js"></script>
<script src="plugins/spin.js"></script>
<script src="plugins/leaflet.spin.js"></script>
<!-- Datasets SHAPEFILES to geojson for the leaflet wifget s-->
<script src="pharmacy.geojson.js"></script>
<script src="hospital.geojson.js"></script>
<!-- Bootstrap core CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<!-- Leaflet, Dynamic Charting, Crossfilter, DataTables, MarkerCluster, core CSS -->
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<link rel="stylesheet" href="bower_components/dcjs/dc.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA==" crossorigin=""/>
<link rel="stylesheet" href="bower_components/leaflet.markercluster/MarkerCluster.Default.css" />
<link rel="stylesheet" href="css/leaflet-search.css" />
<link rel="stylesheet" href="css/style.css" />
<link type="text/css" href="css/dc.css" rel="stylesheet"/>
<link type="text/css" href="css/bootstrap.min.css" rel="stylesheet">
<link type="text/css" href="css/jquery.dataTables.min.css" rel="stylesheet">
<script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js" type="text/javascript"></script>
<style>
.leaflet-marker-icon {
color: #fff;
font-size: 12px;
line-height: 16px;
text-align: center;
vertical-align: middle;
box-shadow: 2px 1px 4px rgba(0,0,0,0.3);
border-radius: 8px;
border:1px solid #fff;
}
.search-tip b {
color: #fff;
}
.pharmacy.search-tip b,
.pharmacy.leaflet-marker-icon {
background: none;
opacity: 0.1 ;
}
.hospital.search-tip b,
.hospital.leaflet-marker-icon {
background: #66f
}
.search-tip {
white-space: nowrap;
}
.search-tip b {
display: inline-block;
clear: left;
float: right;
padding: 0 8px;
margin-left: 4px;
}
<style>
html, body {
height: 100%;
margin: 0;
}
#map {
width: 600px;
height: 400px;
}
</style>
<!-- Custom styles for this template -->
<link href="css/construxn.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<div class="row">
<div class="col-xs-12 dc-data-count dc-chart" id="data-count">
<h3>Sanisphere Universe and Coverage
<small>
<span class="filter-count"></span> selected out of <span class="total-count"></span> Pharmacies
</span>
</small>
</h1>
</div>
<body>
</div>
<div class='col-md-7'>
<div class="col-md-17">
</div>
<div class='row'>
<div id='map'></div>
</div>
<h3>
<span>
</a>
</span>
</h3>
<div class='row'>
<div class='col-md-4'>
<div class='row'>
<div class='col-md-12' id="datred-chart">
<a class="reset" href="javascript:dateOfApplicationChart.filterAll();dc.redrawAll();">
</h6>
</div>
</div>
<div class='row'>
<div class='col-md-12' id="date--chart">
<a class="reset" href="javascript:dateOfIssuanceChart.filterAll();dc.redrawAll();">
</div>
</div>
</div>
<div class='col-md-5'>
<div class='col-md-6' id="day--chart">
<a class="reset" href="javascript:dayEnteredChart.filterAll();dc.redrawAll();">
</h6>
</div>
<div class='col-md-6' id="day-issued-">
<a class="reset" href="javascript:dayIssuedChart.filterAll();dc.redrawAll();">
</h6>
</div>
</div>
</div>
</div>
<div class='col-md-3' id="District-chart">
<h6>District
<a class="reset" href="javascript:districtChart.filterAll();dc.redrawAll();" style="display: none;">reset</a>
</h6>
</div>
<div class='col-md-2'>
<div class='row col-md-12' id="type-chart">
<h6>Coverage
<a class="reset" href="javascript:typechart.filterAll();dc.redrawAll();" style="display: none;">reset</a>
</h6>
</div>
<div class='row col-md-12' id="city-chart">
<h6>Province
<a class="reset" href="javascript:citiesChart.filterAll();dc.redrawAll();" style="display: none;">reset</a>
</h6>
</div>
</div>
<a class="reset" href="javascript:datatable.filterAll();dc.redrawAll();" style="display: none;">reset</a>
</div>
<div class='row'>
<div class='col-xs-12'>
<h3>Data Table</h3>
<table class='table table-hover' id='dc-table-chart'>
<thead>
<tr class='header'>
<th>Date</th>
<th>Type</th>
<th>Coverage</th>
<th>date</th>
<th>Address</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
//////////////////////////////////////////////////////////////////////////////////////////////////
// Create a new group to which we can (later) add or remove our markers / spots on the map
var markersLayer = new L.LayerGroup(); // NOTE: Layer is created here!
var pharmaciesMarkers = new L.FeatureGroup();
/////////////////////////////////////////////////////
// Create a new cluster group to which we can (later) add or remove our markers / spots on the map
var clusterLayer = new L.MarkerClusterGroup();
////////////////////////////////////////////////////////////////////////////////////////////
// Set up the base map tile layers (this is the terrain, imagery, etc)
///////////////////////////////////////////////////////////////////////////////////////////
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> ',
maxZoom: 18
});
var cartodb_light = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
attribution: 'Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a> ',
maxZoom:18
});
var cartodb_dark = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',{
attribution: 'Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
maxZoom:18
});
// Our basemaps (tile layers)
var baseMaps = {
"OpenStreetMap": cartodb_light,
"Esri Basemaps": cartodb_dark,
"OpenStreetMap": osm
};
// Our overlays
var overlays = {
"Clustered Markers": clusterLayer,
"Individual Markers": markersLayer
};
//////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize the Leaflet map and set an initial location and zoom level
////////////////////////////////////////////////////////////////////////////////////////////////////
var map = L.map('map', {
center: [10.762622, 106.660172],
zoom: 10,
layers: [cartodb_light, clusterLayer]
}),
///////////////////////////////////////////////////////////////
/// ADD HOSPITAL AND PHARMACIES SEARCH MULTI LAYER
/////////////////////////////////////////////////////////////////
geojsonOpts = {
pointToLayer: function(feature, latlng) {
return L.marker(latlng, {
icon: L.divIcon({
className: feature.properties.amenity,
iconSize: L.point(16, 16),
html: feature.properties.amenity[0].toUpperCase(),
})
}).bindPopup(feature.properties.amenity+'<br><b>'+feature.properties.name_full+'<br><b>'
+feature.properties.id+'</b>');
}
};
var poiLayers = L.layerGroup([
L.geoJson(pharmacy, geojsonOpts),
L.geoJson(hospital, geojsonOpts)
])
.addTo(map);
L.control.search({
layer: poiLayers,
initial: false,
zoom: 15,
propertyName: 'name_full',
buildTip: function(text, val) {
var type = val.layer.feature.properties.amenity;
return '<a href="#" class="'+type+'">'+text+'<b>'+type+'</b></a>';
}
})
.addTo(map);
/////////////////////////////////////////////////////////
// control that shows state info on hover of the ward
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
// control that shows state info on hover
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h4>Geography</h4>' + (props ?
'<b>' + props.ADM2_NAME + '</b><br />'+ props.ADM3_NAME + '</b><br />' : 'Hover over a ward');
};
info.addTo(map);
// get color depending on population density value
function getColor(d) {
return d > 98500 ? '#800026' :
d > 70000 ? '#BD0026' :
d > 60000 ? '#E31A1C' :
d > 35000 ? '#FC4E2A' :
d > 20000 ? '#FD8D3C' :
d > 10000 ? '#FEB24C' :
d > 5000 ? '#FED976' :
'#FFEDA0';
}
function style(feature) {
return {
weight: 2,
opacity: 0.5,
color: 'white',
dashArray: '3',
fillOpacity: 0.0,
fillColor: getColor(feature.properties.POP)
};
}
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 1,
color: '#666',
dashArray: '',
fillOpacity: 0.0
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
info.update(layer.feature.properties);
}
var geojson;
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
geojson = L.geoJson(warddata, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////// DISPLAY and PROJECT GEOJSON SHAPEFILES WARDS AND DISTRICTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////
var ward = [];
var wardOverlay = L.d3SvgOverlay(function(sel, proj) {
var upd = sel.selectAll('path').data(ward);
upd.enter()
.append('path')
.attr('d', proj.pathFromGeojson)
.attr('pointer-events','none')
.attr('stroke', 'red')
.attr('fill-opacity', '0.2');
upd.attr('stroke-width', 1 / proj.scale);
});
var district = [];
var districtOverlay = L.d3SvgOverlay(function(sel, proj) {
var upd = sel.selectAll('path').data(district);
upd.enter()
.append('path')
.attr('d', proj.pathFromGeojson)
.attr('pointer-events','none')
.attr('stroke', 'black')
.attr('fill-opacity', '0.1')
upd.attr('stroke-width', 1 / proj.scale);
});
var mixed = {
"districtOverlay": districtOverlay, // OverlayMaps
"wardOverlay": wardOverlay // OverlayMaps
};
//////////////////////// Add the control base layers to the map ////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//L.control.layers(baseMaps, overlays).addTo(map);
L.control.layers(baseMaps, overlays).addTo(map);
L.control.layers(null, mixed).addTo(map);
wardOverlay = d3.json("geojson/ward.geo.json", function(data) { ward = data.features; wardOverlay.addTo(map) });
districtOverlay = d3.json("geojson/district.geo.json", function(data) { district = data.features; districtOverlay.addTo(map) });
////////////////////////CHARTS!!!//////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize our dc.js charts, passing the DOM Id in which we want the chart rendered as an argument
var citiesChart = dc.rowChart("#city-chart");
var districtChart = dc.rowChart("#District-chart");
var wardTypesChart = dc.rowChart("#ward_type-chart");
var typeChart = dc.rowChart("#type-chart");
var dataCount = dc.dataCount('#data-count');
var datatable = $('#dc-table-chart');
// A common color for all of the bar and row charts
var commonChartBarColor = '#b3d9ff';
// This is where we will hold our crossfilter data
var xdata = null;
var all = null;
var City = null;
var locations = null;
// Called when dc.js is filtered (typically from user click interaction)
var onFilt = function(chart, filter) {
updateMap(locations.top(Infinity));
};
// Updates the displayed map markers to reflect the crossfilter dimension passed in
var updateMap = function(locs) {
// clear the existing markers from the map
markersLayer.clearLayers();
clusterLayer.clearLayers();
locs.forEach( function(d, i) {
if (d.latitude!=null && d.latitude!=undefined) {
// add a Leaflet marker for the lat lng and insert the application's stated purpose in popup
var mark = L.marker([d.latitude, d.longitude]).bindPopup(d.City);
markersLayer.addLayer(mark);
clusterLayer.addLayer(mark);
}
});
};
///////////////////////////////////////////////////////
// http://wallinm1.github.io/map-dashboard/
/////////////////////////////////////////////////////
///////// VIEW FOR DINAMICS CHARTS AND MAP
///////////////////////////////////////////////////////
// d3's JSON call to grab the JSON data
d3.csv("universe_test.csv", function(error, data) {
// used by d3's dateFormat to parse the date correctly
var dateFormat = d3.time.format("%Y-%m-%dT%H:%M:%S");
// add map markers to map layer
data.forEach( function(d,i) {
d.date_e = dateFormat.parse(d.date_entered);
d.date_i = dateFormat.parse(d.date_issued);
// create a map marker if the lat lng is present
//handle missing values for table variables
d.contact = d.contact ? d.contact : "MISSING";
d.city = d.city ? d.city : "MISSING";
d.permit_type_description = d.permit_type_description ? d.permit_type_description : "MISSING";
d.address = d.address ? d.address : "MISSING";
d.purpose = d.purpose ? d.purpose: "MISSING";
if (d.latitude!=null && d.latitude!=undefined) {
d.ll = L.latLng(d.latitude, d.longitude);
var mark = L.marker([d.latitude, d.longitude]);
markersLayer.addLayer(mark);
clusterLayer.addLayer(mark);
}
});
// Construct the charts
xdata = crossfilter(data);
all = xdata.groupAll();
dataCount.dimension(xdata)
.group(all);
// Define the crossfilter dimensions
cities = xdata.dimension(function (d) { return d.City; });
locations = xdata.dimension(function (d) { return d.ll; });
var districttype = xdata.dimension(function (d) { return d.zip; });
var wardTypes = xdata.dimension(function (d) { return d.ward; });
var coordinates = xdata.dimension(function(d) { return d.geo; });
var type = xdata.dimension(function(d) { return d.type; });
var enteredDates = xdata.dimension(function (d) { return d.date_e; });
var issuanceDates = xdata.dimension(function (d) { return d.date_i; });
// Define variable to % of types in district charts
//map
////http://dataviz.pitchbook.com/founders/ for charts in percentage
// Marker Chart
typeChart.width($('#type-chart').innerWidth()-30)
.height(250)
.colors(commonChartBarColor)
.margins({top: 10, left: 20, right: 10, bottom: 20})
.group(type.group())
.dimension(type)
.elasticX(true)
.ordering(function(d) {
return -d.value
})
.label(function (d) {
if (typeChart.hasFilter() && !typeChart.hasFilter(d.key))
return d.key + " (0%)";
var label = d.key;
if(all.value())
label += " (" + Math.floor(d.value / all.value() * 100) + "%)";
return label;
})
.xAxis().ticks(5);
//.on("filtered", onFilt);
// Start constructing the charts and setting each chart's options
citiesChart.width($('#city-chart').innerWidth()-30)
.height(250)
.colors(commonChartBarColor)
.margins({top: 10, left: 20, right: 10, bottom: 20})
.group(cities.group())
.dimension(cities)
.elasticX(true)
.ordering(function(d) {
return -d.value
})
.label(function (d) {
if (citiesChart.hasFilter() && !citiesChart.hasFilter(d.key))
return d.key + " (0%)";
var label = d.key;
if(all.value())
label += " (" + Math.floor(d.value / all.value() * 100) + "%)";
return label;
})
.xAxis().ticks(5);
//.on("filtered", onFilt);
districtChart.width($('#District-chart').innerWidth()-30)
.height(window.innerHeight - 50)
.colors(commonChartBarColor)
.margins({top: 10, left: 10, right: 10, bottom: 10})
.group(districttype.group())
.dimension(districttype)
.elasticX(true)
.cap(25)
.ordering(function(d) {
return -d.value
})
.label(function (d) {
if(!all.value() && !citiesChart.hasFilter(d.key))
return d.key + " (0%)";
var label = d.key;
if(all.value())
label += " (" + Math.floor(d.value / all.value() * 100) + "%)";
return label;
})
.xAxis().ticks(5);
//.on("filtered", onFilt);
////////////////////////////CHECK TO DO A STACKED BAR EXAMPLE /////////////////////////////////////////
//////////////////////////////////////////////////////////////////http://jsfiddle.net/myrsq3Ly/
//// possibility https://dc-js.github.io/dc.js/examples/bar-grouped-multi.html
// figgle http://jsfiddle.net/myrsq3Ly/
wardTypesChart.width($('#ward_type-chart').innerWidth()-30)
.height(400)
.colors(commonChartBarColor)
.margins({top: 10, left: 20, right: 10, bottom: 20})
.group(wardTypes.group())
.dimension(wardTypes)
.elasticX(true)
.ordering(function(d) {
return -d.value
})
.label(function (d) {
if (wardTypesChart.hasFilter() && !wardTypesChart.hasFilter(d.key))
return d.key + " (0%)";
var label = d.key;
if(all.value())
label += " (" + Math.floor(d.value / all.value() * 100) + "%)";
return label;
})
.xAxis().ticks(5);
//.on("filtered", onFilt);
////////////////////////////////////////////////////////////////////////////////////////////////////////
//VIEW FOR DATA TABLE ///////////////////////////////////////////
//////////////////////////////////////////////////////////
//table
//dimension for table search
var tableDimension = xdata.dimension(function (d) { return d.pop.toLowerCase() + ' ' +
d.City.toLowerCase() + ' ' +
d.Province.toLowerCase() + ' ' +
d.zip.toLowerCase() + ' ' +
d.ward.toLowerCase();});
//set options and columns
var dataTableOptions = {
"bSort": true,
columnDefs: [
{
targets: 0,
data: function (d) { return d.date_entered; },
type: 'date',
defaultContent: 'Not found'
},
{
targets: 1,
data: function (d) { return d.amenity; },
defaultContent: ''
},
{
targets: 2,
data: function (d) { return d.type; },
defaultContent: ''
},
{
targets: 3,
data: function (d) { return d.date_issued;},
defaultContent: ''
},
{
targets: 4,
data: function (d) {return d.name_full;},
defaultContent: ''
},
{
targets: 5, //search column
data: function (d) {return d.pop;},
defaultContent: '',
visible: false
}
]
};
//initialize datatable
datatable.dataTable(dataTableOptions);
//row details
function format ( d ) {
return '<b>Purpose: </b>' + d.purpose;
}
datatable.DataTable().on('click', 'tr[role="row"]', function () {
var tr = $(this);
var row = datatable.DataTable().row( tr );
if ( row.child.isShown() ) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
}
else {
// Open this row
row.child( format(row.data()) ).show();
tr.addClass('shown');
}
} );
//custom refresh function, see http://stackoverflow.com/questions/21113513/dcjs-reorder-datatable-by-column/21116676#21116676
function RefreshTable() {
dc.events.trigger(function () {
alldata = tableDimension.top(Infinity);
datatable.fnClearTable();
datatable.fnAddData(alldata);
datatable.fnDraw();
});
}
//call RefreshTable when dc-charts are filtered
for (var i = 0; i < dc.chartRegistry.list().length; i++) {
var chartI = dc.chartRegistry.list()[i];
chartI.on("filtered", RefreshTable);
}
//filter all charts when using the datatables search box
$(":input").on('keyup',function(){
text_filter(tableDimension, this.value);//cities is the dimension for the data table
function text_filter(dim,q){
if (q!='') {
dim.filter(function(d){
return d.indexOf (q.toLowerCase()) !== -1;
});
} else {
dim.filterAll();
}
RefreshTable();
dc.redrawAll();}
});
//initial table refresh
RefreshTable();
//initialize other charts
dc.renderAll();
/////////////////////////////////////////////////FORCE FILTERING ON THE MAP///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////TYPE CHART
typeChart.on("filtered", function (chart, filter) {
//console.log('11111111111');
//console.log(locations.top(Infinity));
updateMap(locations.top(Infinity));
});
dc.renderAll();
// Called when dc.js is filtered (typically from user click interaction)
var onFilt = function(chart, filter) {
console.log('fffff');
updateMap(locations.top(Infinity));
};
///////////CITY CHART
citiesChart.on("filtered", function (chart, filter) {
//console.log('11111111111');
//console.log(locations.top(Infinity));
updateMap(locations.top(Infinity));
});
dc.renderAll();
// Called when dc.js is filtered (typically from user click interaction)
var onFilt = function(chart, filter) {
console.log('fffff');
updateMap(locations.top(Infinity));
};
///////////district CHART
districtChart.on("filtered", function (chart, filter) {
//console.log('11111111111');
//console.log(locations.top(Infinity));
updateMap(locations.top(Infinity));
});
dc.renderAll();
// Called when dc.js is filtered (typically from user click interaction)
var onFilt = function(chart, filter) {
console.log('fffff');
updateMap(locations.top(Infinity));
};
///////////datatable
districtChart.on("update", function (chart, filter) {
//console.log('11111111111');
//console.log(locations.top(Infinity));
updateMap(locations.top(Infinity));
});
dc.renderAll();
// Called when dc.js is filtered (typically from user click interaction)
var onFilt = function(chart, filter) {
console.log('fffff');
updateMap(locations.top(Infinity));
};
</script>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment