The bounty expires in 6 days. Answers to this question are eligible for a +50 reputation bounty.
Dmitry is looking for an answer from a reputable source.
I have project built on d3.js lib v.7.6.1. It hosted on GitHub Pages and works totally Ok on my laptop:
but renders incorrectly on most of the other devices:
with error in console for y and cy attributes:
I've reproduced code here. What can be wrong with it?
async function drawLineChart() {
const pathToCsv = 'https://raw.githubusercontent.com/dsibi/portfolio/main/projects/line-graph-2/data/time_entries.csv';
let rawDataset = await d3.dsv(";", pathToCsv);
const record = {
date: '',
duration: ''
};
let dataset = [];
for (let i = 0; i < rawDataset.length; i++) {
let currRecord = Object.create(record);
const [day, month, year] = rawDataset[i]['Start date'].split('.');
currRecord.date = new Date(+year, +month - 1, +day);
const [hours, minutes, seconds] = rawDataset[i]['Duration'].split(':');
currRecord.duration = new Date(+year, +month - 1, +day, +hours, +minutes, +seconds);
dataset.push(currRecord);
}
dataset.forEach(function(element) {
let timeString = element.duration.toLocaleTimeString();
let timeEl = timeString.split(':');
element.durationSeconds = (+timeEl[0]) * 60 * 60 + (+timeEl[1]) * 60 + (+timeEl[2]);
});
var groupedDataset = [];
dataset.reduce(function(res, value) {
if (!res[value.date]) {
res[value.date] = {
date: value.date,
totalDurationSeconds: 0
};
groupedDataset.push(res[value.date])
}
res[value.date].totalDurationSeconds += value.durationSeconds;
return res;
}, {});
const xAccessor = d => d.date;
const formatHours = d3.format(".2f");
const yAccessor = d => +formatHours(d['totalDurationSeconds'] / 3600);
const yAccessorLine = d => d['meanDurationHours'];
let datasetWeeks = downsampleData(groupedDataset, xAccessor, yAccessor);
const vacation = [{
name: 'vacation',
start: new Date('2022-06-16'),
end: new Date('2022-06-26'),
}, ];
let dimensions = {
width: window.innerWidth * 0.8,
height: 400,
margin: {
top: 15,
right: 40,
bottom: 40,
left: 40,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
// 3. Draw Canvas
const wrapper = d3.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);
const bounds = wrapper.append("g")
.style("transform", `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`);
// 4. Scales
const xScale = d3.scaleTime()
.domain(d3.extent(groupedDataset, xAccessor))
.range([0, dimensions.boundedWidth]);
const yScale = d3.scaleLinear()
.domain(d3.extent(groupedDataset, yAccessor))
.range([dimensions.boundedHeight, 0])
.nice();
const meanHours = d3.mean(groupedDataset, yAccessor);
bounds.append('line').attr('class', 'mean');
const meanLine = bounds.select('.mean')
.attr('x1', 0)
.attr('x2', dimensions.boundedWidth)
.attr('y1', yScale(meanHours))
.attr('y2', yScale(meanHours));
const xAxisGenerator = d3.axisBottom()
.scale(xScale);
const xAxis = bounds.append("g")
.attr("class", "x-axis")
.style("transform", `translateY(${dimensions.boundedHeight}px)`)
.call(xAxisGenerator);
// 5. Draw Data
//dots
const dots = bounds.selectAll(".dot")
.data(groupedDataset)
.enter()
.append("circle")
.attr("cx", d => xScale(xAccessor(d)))
.attr("cy", d => yScale(yAccessor(d)))
.attr("r", 2)
.attr("class", "dot");
//line
const lineGenerator = d3.line()
.x(function(d) {
// console.log(xScale(xAccessor(d)))
return xScale(xAccessor(d))
})
.y(d => yScale(yAccessorLine(d)))
.curve(d3.curveCatmullRom.alpha(.5));
// .curve(d3.curveMonotoneX);
const line = bounds.append("path")
.attr("class", "line")
.attr("d", lineGenerator(datasetWeeks))
// 6. Draw Peripherals
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
.ticks(7);
const yAxis = bounds.append("g")
.attr("class", "y-axis")
.call(yAxisGenerator);
};
drawLineChart();
function downsampleData(data, xAccessor, yAccessor) {
const weeks = d3.timeWeeks(xAccessor(data[0]), xAccessor(data[data.length - 1]))
return weeks.map((week, index) => {
const weekEnd = weeks[index + 1] || new Date()
const days = data.filter(d => xAccessor(d) > week && xAccessor(d) <= weekEnd)
const meanTotalDurationHours = d3.mean(days, yAccessor)
const meanDurationHours = meanTotalDurationHours === undefined ? 0 : d3.mean(days, yAccessor)
return {
date: week,
meanDurationHours: meanDurationHours
}
})
};
.line {
fill: none;
stroke: #eb4511;
stroke-width: 3;
}
.mean {
stroke: #7d82b8;
stroke-dasharray: 2px 4px;
}
.y-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
}
.x-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
}
.dot {
fill: #9c9b98;
}
.mean_text,
.vacation_text {
fill: #7d82b8;
font-size: .8em;
font-weight: 800;
text-anchor: start;
}
.x-axis line,
.y-axis line,
.domain {
stroke: gray;
}
.tick text,
.y-axis-label {
fill: gray
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<html lang="en">
<body>
<div id="wrapper">
</div>
</body>
</html>
Your issue is caused by toLocaleTimeString().
When adding a console.log() to the following part:
dataset.forEach(function(element) {
let timeString = element.duration.toLocaleTimeString();
let timeEl = timeString.split(':');
console.log(timeString, timeEl)
element.durationSeconds = (+timeEl[0]) * 60 * 60 + (+timeEl[1]) * 60 + (+timeEl[2]);
});
Dekstop shows:
00:19:34 (3) ['00', '19', '34']
But my mobile (using chrome remote inspector) shows:
12:06:53 AM (3) ['12', '06', '53 AM']
So when you're doing * 60 + (+timeEl[2]) you're getting NaN due the 53 AM
You should change the time-logic to both be able to parse 24 and 12h format time, eg, using:
How to use toLocaleTimeString 12 hours time without AM/PM abbreviations?
Here's my attempt to fix this.
I've used new Date() to convert it to a date object. Then using valueOf() to get the unix timestamp. Now we just need to substract those from each-other to get the durationSeconds
dataset.forEach(function(element) {
let secondsDiff = new Date(element.duration).valueOf() - new Date(element.date).valueOf();
element.durationSeconds = secondsDiff;
});
This works both on my laptop as mobile:
Updated snippet:
async function drawLineChart() {
const pathToCsv = 'https://raw.githubusercontent.com/dsibi/portfolio/main/projects/line-graph-2/data/time_entries.csv';
let rawDataset = await d3.dsv(";", pathToCsv);
const record = {
date: '',
duration: ''
};
let dataset = [];
for (let i = 0; i < rawDataset.length; i++) {
let currRecord = Object.create(record);
const [day, month, year] = rawDataset[i]['Start date'].split('.');
currRecord.date = new Date(+year, +month - 1, +day);
const [hours, minutes, seconds] = rawDataset[i]['Duration'].split(':');
currRecord.duration = new Date(+year, +month - 1, +day, +hours, +minutes, +seconds);
dataset.push(currRecord);
}
dataset.forEach(function(element) {
let secondsDiff = new Date(element.duration).valueOf() - new Date(element.date).valueOf();
element.durationSeconds = secondsDiff;
});
var groupedDataset = [];
dataset.reduce(function(res, value) {
if (!res[value.date]) {
res[value.date] = {
date: value.date,
totalDurationSeconds: 0
};
groupedDataset.push(res[value.date])
}
res[value.date].totalDurationSeconds += value.durationSeconds;
return res;
}, {});
const xAccessor = d => d.date;
const formatHours = d3.format(".2f");
const yAccessor = d => +formatHours(d['totalDurationSeconds'] / 3600);
const yAccessorLine = d => d['meanDurationHours'];
let datasetWeeks = downsampleData(groupedDataset, xAccessor, yAccessor);
const vacation = [{
name: 'vacation',
start: new Date('2022-06-16'),
end: new Date('2022-06-26'),
}, ];
let dimensions = {
width: window.innerWidth * 0.8,
height: 400,
margin: {
top: 15,
right: 40,
bottom: 40,
left: 40,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
dimensions.boundedHeight = dimensions.height - dimensions.margin.top - dimensions.margin.bottom
// 3. Draw Canvas
const wrapper = d3.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);
const bounds = wrapper.append("g")
.style("transform", `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`);
// 4. Scales
const xScale = d3.scaleTime()
.domain(d3.extent(groupedDataset, xAccessor))
.range([0, dimensions.boundedWidth]);
const yScale = d3.scaleLinear()
.domain(d3.extent(groupedDataset, yAccessor))
.range([dimensions.boundedHeight, 0])
.nice();
const meanHours = d3.mean(groupedDataset, yAccessor);
bounds.append('line').attr('class', 'mean');
const meanLine = bounds.select('.mean')
.attr('x1', 0)
.attr('x2', dimensions.boundedWidth)
.attr('y1', yScale(meanHours))
.attr('y2', yScale(meanHours));
const xAxisGenerator = d3.axisBottom()
.scale(xScale);
const xAxis = bounds.append("g")
.attr("class", "x-axis")
.style("transform", `translateY(${dimensions.boundedHeight}px)`)
.call(xAxisGenerator);
// 5. Draw Data
//dots
const dots = bounds.selectAll(".dot")
.data(groupedDataset)
.enter()
.append("circle")
.attr("cx", d => xScale(xAccessor(d)))
.attr("cy", d => yScale(yAccessor(d)))
.attr("r", 2)
.attr("class", "dot");
//line
const lineGenerator = d3.line()
.x(function(d) {
// console.log(xScale(xAccessor(d)))
return xScale(xAccessor(d))
})
.y(d => yScale(yAccessorLine(d)))
.curve(d3.curveCatmullRom.alpha(.5));
// .curve(d3.curveMonotoneX);
const line = bounds.append("path")
.attr("class", "line")
.attr("d", lineGenerator(datasetWeeks))
// 6. Draw Peripherals
const yAxisGenerator = d3.axisLeft()
.scale(yScale)
.ticks(7);
const yAxis = bounds.append("g")
.attr("class", "y-axis")
.call(yAxisGenerator);
};
drawLineChart();
function downsampleData(data, xAccessor, yAccessor) {
const weeks = d3.timeWeeks(xAccessor(data[0]), xAccessor(data[data.length - 1]))
return weeks.map((week, index) => {
const weekEnd = weeks[index + 1] || new Date()
const days = data.filter(d => xAccessor(d) > week && xAccessor(d) <= weekEnd)
const meanTotalDurationHours = d3.mean(days, yAccessor)
const meanDurationHours = meanTotalDurationHours === undefined ? 0 : d3.mean(days, yAccessor)
return {
date: week,
meanDurationHours: meanDurationHours
}
})
};
.line {
fill: none;
stroke: #eb4511;
stroke-width: 3;
}
.mean {
stroke: #7d82b8;
stroke-dasharray: 2px 4px;
}
.y-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
}
.x-axis-label {
fill: black;
font-size: 1.4em;
text-anchor: middle;
}
.dot {
fill: #9c9b98;
}
.mean_text,
.vacation_text {
fill: #7d82b8;
font-size: .8em;
font-weight: 800;
text-anchor: start;
}
.x-axis line,
.y-axis line,
.domain {
stroke: gray;
}
.tick text,
.y-axis-label {
fill: gray
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<html lang="en">
<body>
<div id="wrapper">
</div>
</body>
</html>
Related
I have the following data in a csv file called BarData.csv:
Fruit,dt,amount
Apple,12/28/2016,-1256
Apple,12/29/2016,-500
Apple,12/30/2016,3694
Apple,12/31/2016,5586
Apple,1/1/2017,4558
Apple,1/2/2017,6696
Apple,1/3/2017,7757
Apple,1/4/2017,8528
Apple,1/5/2017,5543
Apple,1/6/2017,3363
Apple,1/7/2017,5464
Pear,12/25/2017,250
Pear,12/26/2017,669
Pear,12/27/2017,441
Pear,12/28/2017,159
Pear,12/29/2017,357
Pear,12/30/2017,775
Pear,12/31/2017,669
The following html, css, and javascript is in one .html file:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>BAR SINGLE FUNCTION</title>
<script src="http://d3js.org/d3.v3.js"></script>
<style type="text/css">
#radioDiv {
top: 45px;
font-family: verdana;
font-size: 8px;
width: 455px;
}
#TOPbarChart {
position: absolute;
top: 50px;
left: 30px;
width: 750px;
height: 195px;
}
.axis--y path,
.axis--x path {
display: none;
}
.axis--x line,
.axis--y line {
stroke: black;
fill: none;
stroke-width: 2px
}
.yAxis text,
.xAxis text {
font: 7pt Verdana;
stroke: none;
fill: black;
}
.title,
.titleX {
font-family: Verdana;
font-size: 10px;
}
</style>
</head>
<body>
<div id="radioDiv">
<label>
<input id="radioFrt" type="radio" name="frt" value="Apple" class="radioB" checked> APPLE
</label>
<label>
<input type="radio" name="frt" value="Pear" class="radioB"> PEAR
</label>
</div>
<div id="TOPbarChart"></div>
<script type="text/javascript">
var currentFruit = "Apple";
var currentColr = "#00a5b6";
var barDataCSV_Dly = "BarData.csv";
//
//
// radio button
document.getElementById("radioFrt").checked = true;
d3.selectAll('input[name="frt"]').on("change", function change() {
currentFruit = this.value;
TOPbarChart(currentFruit, currentColr);
});
//FORMATS
var parseDate = d3.time.format("%m/%d/%Y").parse;
//
// BASIC SIZING
//
function barChartBasics() {
var margin = {
top: 25,
right: 35,
bottom: 25,
left: 70
},
width = 550 - margin.left - margin.right,
height = 155 - margin.top - margin.bottom,
colorBar = d3.scale.category20(),
barPaddingFine = 1,
barPaddingThick = 2;
return {
margin: margin,
width: width,
height: height,
colorBar: colorBar,
barPaddingFine: barPaddingFine,
barPaddingThick: barPaddingThick
};
}
// create svg element
var basics = barChartBasics();
var svg = d3.select("#TOPbarChart")
.append("svg")
.attr({
"width": basics.width + basics.margin.left + basics.margin.right,
"height": basics.height + basics.margin.top + basics.margin.bottom,
id: "svgTOPbarChart"
});
// create svg group
var plot = svg
.append("g")
.attr({
"transform": "translate(" + basics.margin.left + "," + basics.margin.top + ")",
id: "svgPlotTOPbarChart"
});
var axisPadding = 2;
var leftAxisGroup = svg
.append('g')
.attr({
transform: 'translate(' + (basics.margin.left - axisPadding) + ',' + (basics.margin.top) + ')',
'class': "yAxis axis--y",
id: "yAxisGTOPbarChart"
});
var bottomAxisGroup = svg
.append('g')
.attr({
'class': "xAxis axis--x",
id: "xAxisGTOPbarChart"
});
var titleTxt = svg.append("text")
.attr({
x: basics.margin.left + 12,
y: 20,
'class': "title",
'text-anchor': "start"
})
// create scales with ranges
var xScale = d3.time.scale().range([0, basics.width]);
var yScale = d3.scale.linear().range([basics.height, 0]);
function TOPbarChart(
frt, colorChosen) {
// get the data
d3.csv(barDataCSV_Dly, function(rows) {
TOPbarData = rows.map(function(d) {
return {
"Fruit": d.Fruit,
"dt": parseDate(d.dt),
"amount": +d.amount
};
}).filter(function(row) {
if (row['Fruit'] == frt) {
return true;
}
});
// create domains for the scales
xScale.domain(d3.extent(TOPbarData, function(d) {
return d.dt;
}));
var amounts = TOPbarData.map(function(d) {
return d.amount;
});
var yMax = d3.max(amounts);
var yMin = d3.min(amounts);
var yMinFinal = 0;
if (yMin < 0) {
yMinFinal = yMin;
}
yScale.domain([yMinFinal, yMax]);
// introduce the bars
// var plot = d3.select("#svgPlotTOPbarChart")
var sel = plot.selectAll("rect")
.data(TOPbarData);
sel.enter()
.append("rect")
.attr({
x: function(d, i) {
return xScale(d.dt);
},
y: function(d) {
return yScale(d.amount);
},
width: (basics.width / TOPbarData.length - basics.barPaddingFine),
height: function(d) {
return basics.height - yScale(d.amount);
},
fill: colorChosen,
'class': "bar"
});
// this little function will create a small ripple affect during transition
var dlyRipple = function(d, i) {
return i * 100;
};
sel
.transition()
.duration(dlyRipple) //1000
.attr({
x: function(d, i) {
return xScale(d.dt);
},
y: function(d) {
return yScale(d.amount);
},
width: (basics.width / TOPbarData.length - basics.barPaddingFine),
height: function(d) {
return basics.height - yScale(d.amount);
},
fill: colorChosen
});
sel.exit().remove();
// add/transition y axis - with ticks and tick markers
var axisY = d3.svg.axis()
.orient('left')
.scale(yScale)
.tickFormat(d3.format("s")) // use abbreviations, e.g. 5M for 5 Million
.outerTickSize(0);
leftAxisGroup.transition().duration(1000).call(axisY);
// add/transition x axis - with ticks and tick markers
var axisX = d3.svg.axis()
.orient('bottom')
.scale(xScale);
bottomAxisGroup
.attr({
transform: 'translate(' + (basics.margin.left + ((basics.width / TOPbarData.length) / 2)) + ',' + (basics.margin.top + basics.height) + ')',
})
.transition().duration(1000).call(axisX.ticks(5));
titleTxt.text("Daily: last " + TOPbarData.length + " days");
// console.log(TOPbarData.length)
});
}
//
//
//
//
TOPbarChart(currentFruit, currentColr);
//
//
//
//
</script>
</body>
</html>
When all the data is positive everything is pretty much ok - but when some of the data is negative we can see the result in this plunker demo:
http://plnkr.co/edit/1hudJYkRq2MnuIlwxXZi?p=preview
How do I amend the code so that:
- the negative bars are shown?
- the base of the positive bars moves vertically up when negative numbers are included?
- the vertical movement is also included in the transition?
Above is more than 1 question but help on any would be appreciated.
The key is to play with the y and height attributes of the bars to position them correctly.
For y, change it to:
y: function(d) {
return yScale(Math.max(0, d.amount));
},
And for the height, change it to:
height: function(d) {
return Math.abs(yScale(d.amount) - yScale(0));
},
You can then style the negative bars to make them a different color.
Check the updated Plunkr - http://plnkr.co/edit/q7dQsPW0PiPuwFTy8gLN?p=preview
Edit:
For the coloring part, you can achieve it with a 1 liner if you want to reduce lines and want more simplicity.
Instead of:
fill: function(d) {
var col = colorChosen
if (d.amount < 0) {
col = "#FF0000";
}
return col;
},
});
You can do:
fill: function(d) {
return d.amount < 0 ? "#FF0000" : colorChosen;
},
I'm trying to display some circles based on their geo location with a slider (per day). The data is saved in a vorfaelle.json file which is here and the HTML/d3 file looks like this.
<!DOCTYPE html>
<head>
<title>D3 Mapping Timeline</title>
<meta charset="utf-8">
<link rel="stylesheet" href="d3.slider.css" />
<style>
path {
fill: none;
stroke: #333;
stroke-width: .5px;
}
.land-boundary {
stroke-width: 1px;
}
.county-boundary {
stroke: #ddd;
}
.site {
stroke-width: .5px;
stroke: #333;
fill: #9cf;
}
#slider3 {
margin: 20px 0 10px 20px;
width: 900px;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="d3.slider.js"></script>
</head>
<body>
<div id="slider3"></div>
<script>
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var width = 1240,
height = 720;
var projection = d3.geo.mercator()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.PI);
d3.json("vorfaelle.json", function(error, data){
console.log(data.features[1].geometry.coordinates);
window.site_data = data;
});
var displaySites = function(data) {
var sites = svg.selectAll(".site")
.data(data);
sites.enter().append("circle")
.attr("class", "site")
.attr("cx", function(d) {
for (var i = 0; i < d.features.length+1; i++) {
console.log(d.features[i].geometry.coordinates[0]);
return projection(d.features[i].geometry.coordinates[0])
//return projection([d.lng, d.lat])[0];
}
})
.attr("cy", function(d) {
for (var i = 0; i < d.features.length+1; i++) {
console.log(d.features[i].geometry.coordinates[1]);
return projection([d.features[i].geometry.coordinates[1]])
//return projection([d.lng, d.lat])[0];
}
})
.attr("r", 1)
.transition().duration(400)
.attr("r", 5);
sites.exit()
.transition().duration(200)
.attr("r",1)
.remove();
};
// var minDateUnix = moment('2014-07-01', "YYYY MM DD").unix();
// var maxDateUnix = moment('2015-07-21', "YYYY MM DD").unix();
var dateParser = d3.time.format("%d.%m.%Y").parse;
var minDate = dateParser("01.01.2015");
var maxDate = dateParser("31.12.2015");
console.log(minDate);
var secondsInDay = 60 * 60 * 24;
d3.select('#slider3').call(d3.slider()
.axis(true).min(minDate).max(maxDate).step(1)
.on("slide", function(evt, value) {
var newData = _(site_data).filter( function(site) {
for (var i = 0; i < site.features.length+1; i++) {
var date = dateParser(site.features[2].properties.date)
return date < value;
}
})
console.log("New set size ", newData.length);
displaySites(newData);
})
);
</script>
</body>
I am not sure if I am filtering the data properly at the end as I a was experimenting with this example and my data. When I move the slider I get this error:
For filtering you are do this which is wrong usage of filter as filter operates on an array.
var newData = _(site_data).filter( function(site) {
for (var i = 0; i < site.features.length+1; i++) {
var date = dateParser(site.features[2].properties.date)
return date < value;
}
})
You can do filtering like shown below:
d3.select('#slider3').call(d3.slider()
.axis(true).min(minDate).max(maxDate).step(1)
.on("slide", function(evt, value) {
newData = site_data.features.filter(function(d){
//convert the value to date
//convert the d.properties.date to date object using parser
return dateParser(d.properties.date) < new Date(value);
});
displaySites(newData);
})
);
Again in your code you doing a for loop to calculate the cx of the circle which is wrong:
.attr("cx", function(d) {
for (var i = 0; i < d.features.length+1; i++) {
console.log(d.features[i].geometry.coordinates[0]);
return projection(d.features[i].geometry.coordinates[0])
//return projection([d.lng, d.lat])[0];
}
})
There is no need for a for loop you should do like this:
.attr("cx", function(d) {
var p = projection(d.geometry.coordinates);
return p[0];
})
.attr("cy", function(d) {
var p = projection(d.geometry.coordinates);
return p[1]
})
Working code here
Hope this helps!
I made a bar chart in D3. The bars and gridlines are visible in Chrome, but not the labels. Everything is visible in Firefox. Here's the CSV file I'm using. Here's my code.
<style type="text/css">
.axis {
font: 10px sans-serif;
}
.x line,
.y .domain {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x line {
opacity: 0.2;
}
.x .domain {
display: none;
}
.legend text,
.axis text {
font: 14px sans-serif;
font-weight: bold;
fill: #000;
}
.city text {
font: 16px sans-serif;
font-weight: bold;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js" charset="utf-8"></script>
<h3>How population changed in American cities with 500,000+ people, 2010-2014</h3>
<svg class="chart"></svg>
<script type="text/javascript">
// Add commas to numbers. Example: 1000 becomes 1,000.
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function apCityNameStyling(cityStateString){
// This isn't exact AP styling, but pretty close
var apCities = {
'Atlanta, Georgia': 'Atlanta',
'Baltimore, Maryland': 'Baltimore',
'Boston, Massachusetts': 'Boston',
'Chicago, Illinois': 'Chicago',
'Cincinnati, Ohio': 'Cincinnati',
'Cleveland, Ohio': 'Cleveland',
'Dallas, Texas': 'Dallas',
'Denver, Colorado': 'Denver',
'Detroit, Michigan': 'Detroit',
'Honolulu, Hawaii': 'Honolulu',
'Houston, Texas': 'Houston',
'Indianapolis (balance), Indiana': 'Indianapolis',
'Las Vegas, Nevada': 'Las Vegas',
'Los Angeles, California': 'Los Angeles',
'Miami, Florida': 'Miami',
'Milwaukee, Wisconsin': 'Milwaukee',
'Minneapolis, Minnesota': 'Minneapolis',
'Nashville-Davidson (balance), Tennessee': 'Nashville, Tennessee', // Not a standalone city, but the key here is how the Census calls Nashville, TN
'New Orleans, Louisiana': 'New Orleans',
'New York, New York': 'New York City',
'Oklahoma City, Oklahoma': 'Oklahoma City',
'Philadelphia, Pennsylvania': 'Philadelphia',
'Phoenix, Arizona': 'Phoenix',
'Pittsburgh, Pennsylvania': 'Pittsburgh',
'Salt Lake City, Utah': 'Salt Lake City',
'San Antonio, Texas': 'San Antonio',
'San Diego, California': 'San Diego',
'San Francisco, California': 'San Francisco',
'Seattle, Washington': 'Seattle',
'St. Louis, Missouri': 'St. Louis',
'Washington, District of Columbia': 'Washington, D.C.'
};
if(cityStateString in apCities){
return apCities[cityStateString];
} else {
return cityStateString;
}
}
var colorArray = [
'#9ecae1',
'#3182bd'
];
var color = d3.scale.ordinal()
.range(colorArray);
var margin = {
top: 50,
right: 30,
bottom: 100,
left: 105
};
var marginTop = margin.top;
var marginLeft = margin.left;
var marginBottom = margin.bottom;
var marginRight = margin.right;
var width = document.getElementsByTagName('main')[0].clientWidth*0.95 - marginLeft - marginRight;
var widthWithMargins = width+marginRight+marginLeft;
var height = 2000;
var chart = d3.select('.chart')
.attr('width',widthWithMargins);
d3.csv('public/datasets/US cities population 2010 to 2014.csv', function(error,csvData){
var data = csvData.filter(function(d){
return +d.population2014>=500000;
});
var maxPopulation2014 = d3.max(
data,
function(d){
return +d.population2014;
}
);
var dataCount = data.length;
var yearColumnNames = d3.keys(data[0]).filter(function(key){
return (key!=="Id" && key!=="City");
});
data.sort(
function(a,b){
return +b.population2014 - +a.population2014; // Sort by 2014 population, highest to lowest
}
);
data.forEach(function(d){
d.apCityName = apCityNameStyling(d.City);
d.years = yearColumnNames.map(function(name){
return {
name: name,
value: +d[name]
};
});
});
chart.attr('height',height)
.attr('transform','translate('+marginLeft+','+marginTop+')');
// X axis stuff
var xDomainMin = 400000;
var x = d3.scale.log()
.domain([xDomainMin,maxPopulation2014])
.range([0,width]); // Make range smaller than chart width so x axis doesn't go off the edge of chart's SVG canvas
var xTicks = [
// xDomainMin,
500000,
1000000,
2000000,
4000000,
8000000
];
var xAxis = d3.svg.axis()
.scale(x)
.tickValues(xTicks)
.tickFormat(d3.format('s'))
.innerTickSize(-height)
.orient('top');
chart.append('g')
.attr('class','x axis')
.call(xAxis)
.append('text')
.text('Population')
.style('text-anchor','end')
.attr('class','axis-label')
.attr('x',widthWithMargins/2)
.attr('y',-25);
var cityNameArray = data.map(function(d){
return d.apCityName;
});
var y0 = d3.scale.ordinal()
.domain(cityNameArray)
.rangeRoundBands([0,height-marginTop],0.15);
var y1 = d3.scale.ordinal()
.domain(yearColumnNames)
.rangeRoundBands([0,y0.rangeBand()]);
var yAxis = d3.svg.axis()
.scale(y0)
.orient('left');
chart.append('g')
.attr('class','y axis')
.call(yAxis);
var city = chart.selectAll('.city')
.data(data)
.enter()
.append('g')
.attr('class','city')
.attr('transform',function(d){
return 'translate('+0+','+y0(d.apCityName)+')';
});
city.selectAll('rect')
.data(function(d){
return d.years;
})
.enter()
.append('rect')
.attr('height',function(d){
return y1.rangeBand();
})
.attr('width',function(d){
return x(d.value);
})
.attr('y',function(d){
return y1(d.name);
})
.style('fill',function(d){
return color(d.name);
})
var popThreshold = 1500000; // Proxy for bar width
city.selectAll('.city')
.data(function(d){
return d.years;
})
.enter()
.append('text')
.attr('x',function(d){
var population = d.value;
var xPop = x(population);
return population>=popThreshold ? xPop-3 : xPop+3;
})
.attr('y',function(d){
return y1(d.name)+(y1.rangeBand()/1.3);
})
.attr('text-anchor',function(d){
return d.value>=popThreshold ? 'end' : 'start';
})
.attr('fill',function(d){
return d.value>=popThreshold ? '#eee' : '#000';
})
.attr('stroke',function(d){
return d.value<popThreshold ? '#fff' : '';
})
.attr('stroke-width',function(d){
return d.value<popThreshold ? 5 : '';
})
.attr('paint-order','stroke')
.text(function(d){
return numberWithCommas(d.value);
});
var insertLinebreaks = function (d,i) {
// Get `y` value for `g` element containing each cluster of bars
var gCityY = d3.selectAll('.city')[0][i]
.getAttribute('transform')
.split(',')[1]
.replace(')','');
var yTick = d3.selectAll('.y .tick')[0][i];
var el = d3.select(this);
var words = d.indexOf(', ') > -1 ? d.split(', ').map(function(w,i){return i===0 ? w+',' : w}) : d.split(' ');
el.text('');
for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if(i===0){
tspan.attr('dx',5);
} else if (i > 0){
tspan.attr('x', 0).attr('dy', '15');
}
}
if(words.length>1){
var cityNameY = words.length===3 ? +gCityY+10 : +gCityY+20;
yTick.setAttribute('transform','translate(-2.5,'+cityNameY+')');
}
};
chart.selectAll('.y .tick text').each(insertLinebreaks);
var legend = chart.selectAll('.legend')
.data(yearColumnNames.slice())
.enter()
.append('g')
.attr('class','legend')
.attr('transform',function(d,i){
return 'translate(0,'+(i*20)+')';
})
var legendX = width - width - marginLeft;
legend.append('rect')
.attr('x',legendX)
.attr('y',-marginTop)
.attr('height',18)
.attr('width',18)
.style('fill',color);
legend.append('text')
.attr('x', legendX + 55)
.attr('y',-35)
.style('text-anchor','end')
.text(function(d){
return d.replace(/\D/g,'');
});
});
</script>
Here are pictures of how the chart looks in Firefox vs Chrome.
Why is this compatible with Firefox v41, but not Chrome v45?
It seems the svg element was being set to transform
chart.attr('height',height).attr('transform','translate(' + marginLeft + ',' + marginTop + ')');
In answer to the Question of why as #Robert Longson stated:
SVG 2 supports setting a transform on an <svg> element. SVG 1.1 does
not. Firefox has implemented this part of SVG 2, Chrome has not yet
done so although it has implemented other parts that Firefox has not.
This situation will continue as the unfinished SVG 2 specification
evolves and browsers evolve with it
So the following changes hopefully help:
var legend = chart.selectAll('.legend')
.data(yearColumnNames.slice())
.enter()
.append('g')
.attr('class','legend')
.attr('transform',function(d,i){
return 'translate('+marginLeft+','+(marginTop+(i*20))+')';
})
chart.attr('height',height);
// .attr('transform','translate('+marginLeft+','+marginTop+')');
//added
chart.selectAll('.y')
.attr('transform','translate('+marginLeft+')');
//added
chart.selectAll('.x')
.attr('transform','translate('+marginLeft+','+marginTop+')');
var city = chart.selectAll('.city')
.data(data)
.enter()
.append('g')
.attr('class','city')
.attr('transform',function(d){
return 'translate('+marginLeft+','+(y0(d.apCityName)+(marginTop/2))+')';
The snippet will not show the display results (so only code) because it can not pull the remote data... So a gist was created here is the bl.ock.org link http://bl.ocks.org/CrandellWS/153bcfa208e2bde8a353
// Add commas to numbers. Example: 1000 becomes 1,000.
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function apCityNameStyling(cityStateString){
// This isn't exact AP styling, but pretty close
var apCities = {
'Atlanta, Georgia': 'Atlanta',
'Baltimore, Maryland': 'Baltimore',
'Boston, Massachusetts': 'Boston',
'Chicago, Illinois': 'Chicago',
'Cincinnati, Ohio': 'Cincinnati',
'Cleveland, Ohio': 'Cleveland',
'Dallas, Texas': 'Dallas',
'Denver, Colorado': 'Denver',
'Detroit, Michigan': 'Detroit',
'Honolulu, Hawaii': 'Honolulu',
'Houston, Texas': 'Houston',
'Indianapolis (balance), Indiana': 'Indianapolis',
'Las Vegas, Nevada': 'Las Vegas',
'Los Angeles, California': 'Los Angeles',
'Miami, Florida': 'Miami',
'Milwaukee, Wisconsin': 'Milwaukee',
'Minneapolis, Minnesota': 'Minneapolis',
'Nashville-Davidson (balance), Tennessee': 'Nashville, Tennessee', // Not a standalone city, but the key here is how the Census calls Nashville, TN
'New Orleans, Louisiana': 'New Orleans',
'New York, New York': 'New York City',
'Oklahoma City, Oklahoma': 'Oklahoma City',
'Philadelphia, Pennsylvania': 'Philadelphia',
'Phoenix, Arizona': 'Phoenix',
'Pittsburgh, Pennsylvania': 'Pittsburgh',
'Salt Lake City, Utah': 'Salt Lake City',
'San Antonio, Texas': 'San Antonio',
'San Diego, California': 'San Diego',
'San Francisco, California': 'San Francisco',
'Seattle, Washington': 'Seattle',
'St. Louis, Missouri': 'St. Louis',
'Washington, District of Columbia': 'Washington, D.C.'
};
if(cityStateString in apCities){
return apCities[cityStateString];
} else {
return cityStateString;
}
}
var colorArray = [
'#9ecae1',
'#3182bd'
];
var color = d3.scale.ordinal()
.range(colorArray);
var margin = {
top: 50,
right: 30,
bottom: 100,
left: 105
};
var marginTop = margin.top;
var marginLeft = margin.left;
var marginBottom = margin.bottom;
var marginRight = margin.right;
var widthWithMargins = document.getElementsByTagName('body')[0].clientWidth;
var width = widthWithMargins*0.95 - marginLeft - marginRight;
var height = 2000;
var chart = d3.select('.chart')
.attr('width',widthWithMargins);
d3.csv('http://localhost/demo/dataset.php', function(error,csvData){
var data = csvData.filter(function(d){
return +d.population2014>=500000;
});
var maxPopulation2014 = d3.max(
data,
function(d){
return +d.population2014;
}
);
var dataCount = data.length;
var yearColumnNames = d3.keys(data[0]).filter(function(key){
return (key!=="Id" && key!=="City");
});
data.sort(
function(a,b){
return +b.population2014 - +a.population2014; // Sort by 2014 population, highest to lowest
}
);
data.forEach(function(d){
d.apCityName = apCityNameStyling(d.City);
d.years = yearColumnNames.map(function(name){
return {
name: name,
value: +d[name]
};
});
});
chart.attr('height',height);
// .attr('transform','translate('+marginLeft+','+marginTop+')');
// X axis stuff
var xDomainMin = 400000;
var x = d3.scale.log()
.domain([xDomainMin,maxPopulation2014])
.range([0,width]); // Make range smaller than chart width so x axis doesn't go off the edge of chart's SVG canvas
var xTicks = [
// xDomainMin,
500000,
1000000,
2000000,
4000000,
8000000
];
var xAxis = d3.svg.axis()
.scale(x)
.tickValues(xTicks)
.tickFormat(d3.format('s'))
.innerTickSize(-height)
.orient('top');
chart.append('g')
.attr('class','x axis')
.call(xAxis)
.append('text')
.text('Population')
.style('text-anchor','end')
.attr('class','axis-label')
.attr('x',widthWithMargins/2)
.attr('y',-25);
var cityNameArray = data.map(function(d){
return d.apCityName;
});
var y0 = d3.scale.ordinal()
.domain(cityNameArray)
.rangeRoundBands([0,height-marginTop],0.15);
var y1 = d3.scale.ordinal()
.domain(yearColumnNames)
.rangeRoundBands([0,y0.rangeBand()]);
var yAxis = d3.svg.axis()
.scale(y0)
.orient('left');
chart.append('g')
.attr('class','y axis')
.call(yAxis);
chart.selectAll('.y')
.attr('transform','translate('+marginLeft+')');
chart.selectAll('.x')
.attr('transform','translate('+marginLeft+','+marginTop+')');
var city = chart.selectAll('.city')
.data(data)
.enter()
.append('g')
.attr('class','city')
.attr('transform',function(d){
return 'translate('+marginLeft+','+(y0(d.apCityName)+(marginTop/2))+')';
});
city.selectAll('rect')
.data(function(d){
return d.years;
})
.enter()
.append('rect')
.attr('height',function(d){
return y1.rangeBand();
})
.attr('width',function(d){
return x(d.value);
})
.attr('y',function(d){
return y1(d.name);
})
.style('fill',function(d){
return color(d.name);
})
var popThreshold = 1500000; // Proxy for bar width
city.selectAll('.city')
.data(function(d){
return d.years;
})
.enter()
.append('text')
.attr('x',function(d){
var population = d.value;
var xPop = x(population);
return population>=popThreshold ? xPop-3 : xPop+3;
})
.attr('y',function(d){
return y1(d.name)+(y1.rangeBand()/1.3);
})
.attr('text-anchor',function(d){
return d.value>=popThreshold ? 'end' : 'start';
})
.attr('fill',function(d){
return d.value>=popThreshold ? '#eee' : '#000';
})
.attr('stroke',function(d){
return d.value<popThreshold ? '#fff' : '';
})
.attr('stroke-width',function(d){
return d.value<popThreshold ? 5 : '';
})
.attr('paint-order','stroke')
.text(function(d){
return numberWithCommas(d.value);
});
var insertLinebreaks = function (d,i) {
// Get `y` value for `g` element containing each cluster of bars
var gCityY = d3.selectAll('.city')[0][i]
.getAttribute('transform')
.split(',')[1]
.replace(')','');
var yTick = d3.selectAll('.y .tick')[0][i];
var el = d3.select(this);
var words = d.indexOf(', ') > -1 ? d.split(', ').map(function(w,i){return i===0 ? w+',' : w}) : d.split(' ');
el.text('');
for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if(i===0){
tspan.attr('dx',5);
} else if (i > 0){
tspan.attr('x', 0).attr('dy', '15');
}
}
if(words.length>1){
var cityNameY = words.length===3 ? +gCityY+10 : +gCityY+20;
yTick.setAttribute('transform','translate(-2.5,'+cityNameY+')');
}
};
chart.selectAll('.y .tick text').each(insertLinebreaks);
var legend = chart.selectAll('.legend')
.data(yearColumnNames.slice())
.enter()
.append('g')
.attr('class','legend')
.attr('transform',function(d,i){
return 'translate('+marginLeft+','+(marginTop+(i*20))+')';
})
var legendX = width - width - marginLeft;
legend.append('rect')
.attr('x',legendX)
.attr('y',-marginTop)
.attr('height',18)
.attr('width',18)
.style('fill',color);
legend.append('text')
.attr('x', legendX + 55)
.attr('y',-35)
.style('text-anchor','end')
.text(function(d){
return d.replace(/\D/g,'');
});
});
.axis {
font: 10px sans-serif;
}
.x line,
.y .domain {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x line {
opacity: 0.2;
}
.x .domain {
display: none;
}
.legend text,
.axis text {
font: 14px sans-serif;
font-weight: bold;
fill: #000;
}
.city text {
font: 16px sans-serif;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js" charset="utf-8"></script>
<h3>How population changed in American cities with 500,000+ people, 2010-2014</h3>
<svg class="chart"></svg>
I am new to d3.js. Trying to understand the cartogram example give in http://prag.ma/code/d3-cartogram/ . Here they gave example for USA map. I am trying the same for World Map to see how things works. My cartogram map has lines in between. My data has values for only few countries so I am setting the rest of the country's value as low or 0.
<!DOCTYPE html>
<html>
<head>
<title>Cartograms with d3 & TopoJSON</title>
<meta charset="utf-8">
<meta property="og:image" content="placeholder.png">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="lib/colorbrewer.js"></script>
<script src="lib/topojson.js"></script>
<script src="cartogram.js"></script>
<style type="text/css">
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4em;
padding: 0;
margin: 0;
}
#container {
width: 960px;
margin: 20px auto;
}
h1 {
font-size: 200%;
margin: 0 0 15px 0;
}
h2 {
font-size: 160%;
margin: 0 0 10px 0;
}
p {
margin: 0 0 10px;
}
form, form > * {
margin: 0;
}
#status {
color: #999;
}
#map-container {
height: 700px;
text-align: center;
position: relative;
margin: 20px 0;
}
#map {
display: block;
position: absolute;
background: #fff;
width: 100%;
height: 100%;
margin: 0;
}
path.state {
stroke: #666;
stroke-width: .5;
}
path.state:hover {
stroke: #000;
}
form {
font-size: 120%;
}
select {
font-size: inherit;
}
#placeholder {
position: absolute;
z-index: -1;
display: block;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="container">
<h1>Cartograms with d3 & TopoJSON</h1>
<form>
<p>
<label>Scale by <select id="field"></select></label>
<span id="status"></span>
</p>
</form>
<div id="map-container">
<svg id="map"></svg>
</div>
</div>
<script>
var margin = 1,
width = 970 - margin,
height = 700 - margin;
if (!document.createElementNS) {
document.getElementsByTagName("form")[0].style.display = "none";
}
var percent = (function() {
var fmt = d3.format(".2f");
return function(n) { return fmt(n) + "%"; };
})(),
fields = [
{name: "(no scale)", id: "none"},
{name: "Internet_Users", id: "internet", key: "Internet_Users", format : percent},
{name: "GDP", id: "gdp", key: "GDP"},
{name: "Literacy_rates", id: "literacy", key: "Literacy_rates", format : percent},
{name: "female_male", id: "fm", key: "female_male"},
{name: "Population", id: "pop", key: "Population"},
],
fieldsById = d3.nest()
.key(function(d) { return d.id; })
.rollup(function(d) { return d[0]; })
.map(fields),
field = fields[0],
colors = colorbrewer.RdYlBu[3]
.reverse()
.map(function(rgb) { return d3.hsl(rgb); });
var body = d3.select("body"),
stat = d3.select("#status");
var fieldSelect = d3.select("#field")
.on("change", function(e) {
field = fields[this.selectedIndex];
location.hash = "#" + [field.id]
});
fieldSelect.selectAll("option")
.data(fields)
.enter()
.append("option")
.attr("value", function(d) { return d.id; })
.text(function(d) { return d.name; });
var map = d3.select("#map").attr("width", width + margin)
.attr("height", height + margin),
zoom = d3.behavior.zoom()
.translate([-38, 32])
.scale(.95)
.scaleExtent([0.5, 10.0])
.on("zoom", updateZoom),
layer = map.append("g")
.attr("id", "layer"),
states = layer.append("g")
.attr("id", "states")
.selectAll("path");
updateZoom();
function updateZoom() {
var scale = zoom.scale();
layer.attr("transform",
"translate(" + zoom.translate() + ") " +
"scale(" + [scale, scale] + ")");
}
var proj = d3.geo.mercator().scale(145).translate([width / 2, height / 1.5]),
topology,
geometries,
rawData,
dataById = {},
carto = d3.cartogram()
.projection(proj)
.properties(function(d) {
return dataById[d.id];
})
.value(function(d) {
return +d.properties[field];
});
window.onhashchange = function() {
parseHash();
};
d3.json("data/world_countries_topo.json", function(topo) {
topology = topo;
// console.log("T",topology)
geometries = topology.objects.countries.geometries;
d3.csv("data/parallel_score.csv", function(data) {
rawData = data;
dataById = d3.nest()
.key(function(d) { return d.Id; })
.rollup(function(d) { return d[0]; })
.map(data);
init();
});
});
function init() {
var features = carto.features(topology, geometries),
path = d3.geo.path()
.projection(proj);
states = states.data(features)
.enter()
.append("path")
.attr("class", "state")
.attr("id", function(d) {
return d.Id;
})
.attr("fill", "#000")
.attr("d", path);
states.append("title");
parseHash();
}
function reset() {
stat.text("");
body.classed("updating", false);
var features = carto.features(topology, geometries),
path = d3.geo.path()
.projection(proj);
states.data(features)
.transition()
.duration(750)
.ease("linear")
.attr("fill", "#fafafa")
.attr("d", path);
states.select("title")
.text(function(d) {
return d.Id;
});
}
function update() {
var start = Date.now();
body.classed("updating", true);
var key = field.key
var fmt = (typeof field.format === "function")
? field.format
: d3.format(field.format || ","),
value = function(d) {
if(d.properties == undefined){}
else {
return +d.properties[key];
}
},
values = states.data()
.map(value)
.filter(function(n) {
return !isNaN(n);
})
.sort(d3.ascending),
lo = values[0],
hi = values[values.length - 1];
console.log("L",lo)
console.log("H",hi)
var color = d3.scale.linear()
.range(colors)
.domain(lo < 0
? [lo, 0, hi]
: [lo, d3.mean(values), hi]);
// normalize the scale to positive numbers
var scale = d3.scale.linear()
.domain([lo, hi])
.range([1, 1000]);
// tell the cartogram to use the scaled values
carto.value(function(d) {
if( value(d) == undefined) {
return lo
}
else {
console.log("SCale", (value(d)))
return scale(value(d));
}
});
// generate the new features, pre-projected
var features = carto(topology, geometries).features;
// update the data
states.data(features)
.select("title")
/*.text(function(d) {
return [d.properties.Id, fmt(value(d))].join(": ");
});*/
states.transition()
.duration(750)
.ease("linear")
.attr("fill", function(d) {
if(d.properties == undefined){
return color(lo)
}
else {
return color(value(d));
}
})
.attr("d", carto.path);
var delta = (Date.now() - start) / 1000;
stat.text(["calculated in", delta.toFixed(1), "seconds"].join(" "));
body.classed("updating", false);
}
var deferredUpdate = (function() {
var timeout;
return function() {
var args = arguments;
clearTimeout(timeout);
stat.text("calculating...");
return timeout = setTimeout(function() {
update.apply(null, arguments);
}, 10);
};
})();
var hashish = d3.selectAll("a.hashish")
.datum(function() {
return this.href;
});
function parseHash() {
var parts = location.hash.substr(1).split("/"),
desiredFieldId = parts[0],
field = fieldsById[desiredFieldId] || fields[0];
fieldSelect.property("selectedIndex", fields.indexOf(field));
if (field.id === "none") {
reset();
} else {
deferredUpdate();
location.replace("#" + [field.id].join("/"));
hashish.attr("href", function(href) {
return href + location.hash;
});
}
}
</script>
</body>
</html>
Here is the link of my map: My Map
Can Someone please explain me why I am getting this line.
Thanks.
We had the same problem a year or so back and it is due to the arcs in the topojson file moving from 180 or 360 back to 0, basically wrapping at the ends of the map.
We needed to manually go into the map file and edit it using QGIS.
This resolved the issue of the lines.
You will also find if you are using the Cartogram code that the map of the world is far to detailed than you will need given you will be distorting the map anyway. If you are generating the cartogram in real time then you will face delays in the code.
You should probably reduce the complexity of the map too.
Here is an example of the JSON we used to create real time hexagonal cartograms in the browser.
Simplified World Map JSON
Demo jsFiddle
I have a basic d3 line graph, using a simple JSON array of UNIX timestamp and float value data, e.g:
"value": 10.04,"time": 1401185375354 [...]
This timestamp (time) data is converted into a Date() object before the graph is generated. All is good until I want to add a marker on hover. In order to get the correct y value, I am resorting to using bisector and passing the value of the x at the current hover point. As such I currently have:
var bisect = d3.bisector(function(data) {
return data.time;
}).right;
Then in the hover function:
var timestamp = xScale.invert(mouse[0]),
index = bisect(data, timestamp),
startDatum = data[index - 1],
endDatum = data[index],
interpolate = d3.interpolateNumber(startDatum.value, endDatum.value),
range = endDatum.timestamp - startDatum.timestamp,
valueY = interpolate((timestamp % range) / range);
marker.attr('cy', yScale(valueY));
But the bisector returns invalid values..suggesting it cannot equate the passed argument (date object) against an array item. Any help would be appreciated, full code below:
HTML
<svg id="graph"></svg>
d3
var JSONData = [{
"value": 10.04,
"time": 1401185375354
}, {
"value": 0.02,
"time": 1401185374854
}, {
"value": 1.61,
"time": 1401185373854
}, {
"value": 8.47,
"time": 1401185373353
}, {
"value": 1.65,
"time": 1401185372852
}, {
"value": 0.46,
"time": 1401185371852
}, {
"value": 3.17,
"time": 1401185370888
}]
JSONData.forEach(function (d) {
d.time = new Date(d.time);
});
data = JSONData.slice();
var margin = 45,
width = parseInt(d3.select("#graph").style("width")) - margin * 2,
height = parseInt(d3.select("#graph").style("height")) - margin * 2;
var timeFormat = d3.time.format("%I:%M:%S");
var xMin = d3.min(data, function (d) {
return Math.min(d.time);
});
var xMax = d3.max(data, function (d) {
return Math.max(d.time);
});
var yMin = d3.min(data, function (d) {
return Math.min(d.value);
});
var yMax = d3.max(data, function (d) {
return Math.max(d.value);
});
var xScale = d3.time.scale().domain(d3.extent(data, function (d) {
return d.time;
})).range([0, width]);
var yScale = d3.scale.linear().domain(d3.extent(data, function (d) {
return d.value;
})).range([height, 0]);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickFormat(timeFormat);
var yAxis = d3.svg.axis().scale(yScale).orient("left");
var line = d3.svg.line().x(function (d) {
return xScale(d.time);
}).y(function (d) {
return yScale(d.value);
});
var line2 = d3.svg.line().x(function (d) {
return xScale(d.time);
}).y(function (d) {
return yScale(d.askPrice);
});
var graph = d3.select("#graph").attr("width", width + margin * 2).attr("height", height + margin * 2).append("g").attr("transform", "translate(" + margin + "," + margin + ")");
graph.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
graph.append("g").attr("class", "y axis").call(yAxis).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", ".71em").style("text-anchor", "end");
graph.append("path").datum(data).attr("class", "line").style('pointer-events', 'none').attr("d", line);
graph.append("path").datum(data).attr("class", "line2").style('pointer-events', 'none').attr("d", line2)
var marker = graph.append('circle').attr('r', 7).style('fill', '#FFFFFF').style('pointer-events', 'none').style('stroke', '#FB5050').style('stroke-width', '3px');
graph.append("rect").attr("class", "overlay").attr("width", width).attr("height", height).on("mouseover", function () {
focus.style("display", "block");
}).on("mouseout", function () {
focus.style("display", "none");
}).on("mousemove", update);
var bisect = d3.bisector(function (date) {
return date.time;
}).right;
function update() {
var mouse = d3.mouse(this);
marker.attr('cx', mouse[0]);
/* the issue is with the below */
var timestamp = xScale.invert(mouse[0]),
index = bisect(data, timestamp),
startDatum = data[index - 1],
endDatum = data[index],
interpolate = d3.interpolateNumber(startDatum.value, endDatum.value),
range = endDatum.timestamp - startDatum.timestamp,
valueY = interpolate((timestamp % range) / range);
marker.attr('cy', yScale(valueY));
}
CSS
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family:arial;
font-size:11px;
color:#AAAAAA;
overflow:hidden;
}
.overlay {
fill: none;
pointer-events: all;
}
.axis path, .axis line {
fill: none;
stroke: #555;
shape-rendering: crispEdges;
}
.axis text {
fill: #555;
}
.line {
fill: none;
stroke: red;
stroke-width: 1.5px;
}
There are a few problems with your current code:
d3.bisect works only on sorted data. Fixed with data.sort(function(a, b) { return a.time - b.time; });
In the mouseover function, you're referencing a non-existent .timestamp attribute instead of .time.
The calculation of the interpolation value isn't correct. Should be interpolate((timestamp - startDatum.time) / range);
Complete demo with fixes here. You may also find this example helpful.