xxxxxxxxxx
<meta charset="utf-8">
<style>
/*body {
background-color: #333;
}*/
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
font: 10px sans-serif;
}
.cells path {
fill: none;
pointer-events: all;
}
</style>
<svg width="960" height="620"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// data source: https://data.sfgov.org/Culture-and-Recreation/Film-Locations-in-San-Francisco/yitu-d5am/data
// make sure to use %Y instead of %y, if have full year and not just last 2 nums of year
var parseTime = d3.timeParse("%m/%d/%Y"); // input: 3/2/2017
var formatTime = d3.timeFormat("%m/%d/%y"); // output: 3/2/17
var svg = d3.select("svg"),
margin = {top: 40, right: 40, bottom: 40, left: 40},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.rangeRound([0, width]);
var subsets = []
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv('Film_Locations_in_San_Francisco.csv', function(error, data){
if (error) throw error;
var films = d3.set(data, function(d){ return d["Title"]});
var filmNames = d3.values(films);
var prod = d3.set(data, function(d){ return d["Production Company"]});
var distr = d3.set(data, function(d){ return d["Distributor"]})
var loc = d3.set(data, function(d){ return d["Locations"]})
locNames = d3.values(loc); // I might want to limit this to top 10?
locGroups = [];
for(var i=0; i<locNames.length; i++){
locGroups.push([])
}
// filter out records that don't have category labels
data.forEach(function(d){
d.year = d["Release Year"];
var i = locNames.indexOf(d["Locations"])
if(i >= 0){
locGroups[i].push(d)
}
})
var minCount = 5; //2; //1;
// only draw subsets that have a minimum size
for(var i=0; i<locGroups.length; i++){
if(locGroups[i].length > minCount){
subsets.push(locGroups[i])
}
}
drawChart(subsets);
});
function getY(rows, row){
return (height / (rows+1)) * row;
}
function makeSim(data,rows, row){
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) { return x(d.year); }).strength(1))
.force("y", d3.forceY( function(d){return getY(rows, row)}))
.force("collide", d3.forceCollide(4))
.stop();
for (var i = 0; i < 120; ++i) simulation.tick();
}
function makeVoronoi(data){
var cell = g.append("g")
.attr("class", "cells")
.selectAll("g").data(d3.voronoi()
.extent([[-margin.left, -margin.top], [width + margin.right, height + margin.top]])
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.polygons(data)).enter().append("g");
return cell;
}
function drawGuide(rows,i){
var guide = g.append("g")
guide.append("line")
.style("stroke", "#CCC")
.style("stroke-width", .2)
.attr("x1", 0)
.attr("y1", getY(rows, i+1))
.attr("x2", width)
.attr("y2", getY(rows, i+1))
guide.append("text")
.attr("x", function(d) { return 0; })
.attr("y", function(d) { return getY(rows,i+1); })
.text(function(d){ var loc = subsets[i][0]["Locations"]; return loc != "" ? loc: "unspecified" })
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "#CCC");
}
function getFill(d){
return "black"
}
function getOpacity(d){
return .3
}
function drawChart(subsets){
var rows = subsets.length;
// collect all the subsets into a single list, so we can draw them
var data = [];
for(var i=0; i<rows; i++){
data = data.concat(subsets[i])
}
// get min/max of all data, so we can use same axess
x.domain(d3.extent(data, function(d) { return d.year; }));
var xAxis = d3.axisBottom(x).tickFormat(d3.format("d")).tickSizeOuter(0); // this formats as year "1990", not "1,990"
// group them into smal multiples
for(var i=0; i<rows; i++){
makeSim(subsets[i], rows, i+1) // calculate positions within groups
drawGuide(rows, i) // add annotation/guideline for group
}
// draw x axis, for dates
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
// create voronoi for selecting nearest dot with mouse/touch
cell = makeVoronoi(data)
// draw dots
cell.append("circle")
.attr("r", 2.4)
.attr("cx", function(d) { return d.data.x; })
.attr("cy", function(d) { return d.data.y; })
.style("opacity", function(d){ return getOpacity(d); })
g.selectAll(".cells").selectAll("g")
.on("mouseover",function(d,i){
// console.log("hello")
d3.select(this)
.style("fill","red");
})
.on("mouseout",function(d,i){
d3.select(this)
.transition()
.duration(500)
.style("fill","black");//it is style
});
cell.append("path")
// .attr("d", function(d) { return "M" + d.join("L") + "Z"; });
// Tool Tip info
cell.append("title")
.text(function(d) { var str = d.data["Release Year"] + "\n"
+ d.data["Title"] + "\n"
+ d.data["Locations"] + "\n";
return str;
});
};
function type(d) {
if (!d.value) return;
d.value = +d.value;
return d;
}
</script>
https://d3js.org/d3.v4.min.js