How to zoom in a graph in JavaScript D3 - javascript

I am trying to get Zoom to work, I am new to D3 and I find it very abstract and not intuitive. I recently finished a beginners course in JavaScript but D3 feels like a completely new language.
I found this topic which might help a bit.
D3 Zooming in graph
I also found the following code that created the graph on the web, the simplest I could find and I don't understand all of it. Now I wanna zoom in and used code that I also found on the web but which has to be adapted. I understood that much that the zoom variable at the top is calling a function called NeuerChart which has the actual zooming behaviour in it. It needs to zoom the graph and the axes when I spin the mousewheel.
In the end I need to implement this into a real problem, thanks. Using D3.v5.
<script>
let zoom = d3.zoom()
.scaleExtent([0.5, 10])
.extent([[0, 0], [width, height]])
.on('zoom', NeuerChart);
// Step 1
let min = 0;
let max = 100;
let x_arr = [];
let y_arr = [];
let s_arr = [];
let z_arr = [];
for (let i = 0; i < 360; i++) {
var r = Math.round(Math.random() * (max - min)) + min;
x_arr[i]= i;
y_arr[i]= r;
z_arr.push([x_arr[i],y_arr[i]]);
}
s_arr = y_arr.sort(function(a, b){return a - b});
let neu_arr = [];
let zz_arr = [];
for (let i = 0; i < 360; i++) {
neu_arr[i]= i;
zz_arr.push([neu_arr[i], s_arr[i]]);
}
console.log(z_arr);
console.log(zz_arr);
var dataset1 = zz_arr;
// Step 3
var svg = d3.select("svg"),
margin = 200,
width = svg.attr("width") - margin, //1700
height = svg.attr("height") - margin //700
// Step 4
var xScale = d3.scaleLinear().domain([0 , 365]).range([0, width]),
yScale = d3.scaleLinear().domain([0, 105]).range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + 100 + "," + 100 + ")");
// Step 5
// Title
svg.append('text')
.attr('x', width/2 + 100)
.attr('y', 100)
.attr('text-anchor', 'middle')
.style('font-family', 'Helvetica')
.style('font-size', 20)
.text('Line Chart');
// X label
svg.append('text')
.attr('x', width/2 + 100)
.attr('y', height - 15 + 150)
.attr('text-anchor', 'middle')
.style('font-family', 'Helvetica')
.style('font-size', 12)
.text('Zeitachse');
// Y label
svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(60,' + 500 + ')rotate(-90)')
.style('font-family', 'Helvetica')
.style('font-size', 12)
.text('Wert');
// Step 6
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7).tickValues([0, 60, 120, 180, 240, 300, 360]));
g.append("g")
.call(d3.axisLeft(yScale));
// Step 7
svg.append('g')
.selectAll("dot")
.data(dataset1)
.enter()
.append("circle")
.attr("cx", function (d) { return xScale(d[0]); } )
.attr("cy", function (d) { return yScale(d[1]); } )
.attr("r", 3)
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.style("fill", "#CC0000");
// Step 8
var line = d3.line()
.x(function(d) { return xScale(d[0]); })
.y(function(d) { return yScale(d[1]); })
.curve(d3.curveMonotoneX)
svg.append("path")
.datum(dataset1)
.attr("class", "line")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.attr("d", line)
.style("fill", "none")
.style("stroke", "#CC0000")
.style("stroke-width", "2")
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.call(zoom);
function NeuerChart () {
// recover the new scale
var newX = d3.event.transform.rescaleX(xScale);
var newY = d3.event.transform.rescaleY(yScale);
// update axes with these new boundaries
xAxis.call(d3.axisBottom(newX))
yAxis.call(d3.axisLeft(newY))
}
</script>
I added the code here in Codepen:
https://codepen.io/Dvdscot/pen/zYjpzVP
This is how it should work:
https://codepen.io/Dvdscot/pen/BaxJdKN

Problem is solved, see the code at Codepen:
`
Reset zoom
`https://codepen.io/Dvdscot/pen/zYjpzVP

Related

d3js beeswarm with force simulation

I try to do a beeswarm plot with different radius; inspired by this code
The issue I have, is that my point are offset regarding my x axis:
The point on the left should be at 31.7%. I don't understand why, so I would appreciate if you could guide me. This could be improved by changing the domain of x scale, but this can't match the exact value; same issue if I remove the d3.forceCollide()
Thank you,
Data are available here.
Here is my code:
$(document).ready(function () {
function tp(d) {
return d.properties.tp60;
}
function pop_mun(d) {
return d.properties.pop_mun;
}
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 1280 - margin.right - margin.left,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var z = d3.scaleThreshold()
.domain([.2, .3, .4, .5, .6, .7])
.range(["#35ff00", "#f1a340", "#fee0b6",
"#ff0000", "#998ec3", "#542788"]);
var loading = svg.append("text")
.attr("x", (width) / 2)
.attr("y", (height) / 2)
// .attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");
var formatPercent = d3.format(".0%"),
formatNumber = d3.format(".0f");
d3.json('static/data/qp_full.json').then(function (data) {
features = data.features
//1 create scales
var x = d3.scaleLinear()
.domain([0, d3.max(features, tp)/100])
.range([0, width - margin.right])
var y = d3.scaleLinear().domain([0, 0.1]).range([margin.left, width - margin.right])
var r = d3.scaleSqrt().domain([0, d3.max(features, pop_mun)])
.range([0, 25]);
//2 create axis
var xAxis = d3.axisBottom(x).ticks(20)
.tickFormat(formatPercent);
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
var nodes = features.map(function (node, index) {
return {
radius: r(node.properties.pop_mun),
color: '#ff7f0e',
x: x(node.properties.tp60 / 100),
y: height + Math.random(),
pop_mun: node.properties.pop_mun,
tp60: node.properties.tp60
};
});
function tick() {
for (i = 0; i < nodes.length; i++) {
var node = nodes[i];
node.cx = node.x;
node.cy = node.y;
}
}
setTimeout(renderGraph, 10);
function renderGraph() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
// Of course, don't run too long—you'll hang the page!
const NUM_ITERATIONS = 1000;
var force = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().strength(-3))
.force('center', d3.forceCenter(width / 2, height/2))
.force('x', d3.forceX(d => d.x))
.force('y', d3.forceY(d => d.y))
.force('collide', d3.forceCollide().radius(d => d.radius))
.on("tick", tick)
.stop();
force.tick(NUM_ITERATIONS);
force.stop();
svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.radius)
.style("fill", d => z(d.tp60/100))
.on("mouseover", function (d, i) {
d3.select(this).style('fill', "orange")
console.log(i.tp60,i)
svg.append("text")
.attr("id", "t")
.attr("x", function () {
return d.x - 50;
})
.attr("y", function () {
return d.y - 50;
})
.text(function () {
return [x.invert(i.x), i.tp60]; // Value of the text
})
})
.on("mouseout", function (d, i) {
d3.select("#t").remove(); // Remove text location
console.log(i)
d3.select(this).style('fill', z(i.tp60/100));
});
loading.remove();
}
})
})

Why is d appearing as undefined in my d3 script?

edited to include code where cell is defined
I am attempting to create a chart that pulls data from an XHR request, but d is undefined in certain functions in my script:
var svg = d3.select("svg"),
margin = {top: 30, right: 40, bottom: 60, left: 40},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom;
var formatValue = d3.format(",d");
var x = d3.scaleTime()
//var x = d3.scaleLinear()
.domain([new Date(2017, 0, 1), new Date(2020, 11, 31)])
//.rangeRound([0, width]);
.range([0, width-300]);
/*var xScale = d3.scaleTime()
.domain([new Date(2018, 0, 1), new Date(2020, 11, 31)])
.range([50, 1150]);*/
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
//variables for legends
var legendRectSize = 18;
var legendSpacing = 4;
d3.request("https://api.parliament.uk/sparql?query=PREFIX%20rdfs%3A%20%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E%0APREFIX%20%3A%20%3Chttps%3A%2F%2Fid.parliament.uk%2Fschema%2F%3E%0APREFIX%20id%3A%20%3Chttps%3A%2F%2Fid.parliament.uk%2F%3E%0Aselect%20%3FTreaty%20%3FTreatyname%20%3FLeadOrg%20%3FSeries%20%3FLink%20%3FworkPackage%20%3FprocStepName%20%3Fdate%20where%20%7B%0A%20%3FTreaty%20a%20%3ATreaty%20.%20%20%0A%20%20%20%20%20%3FTreaty%20rdfs%3Alabel%20%3FTreatyname%20.%0A%20%20OPTIONAL%7B%20%3FTreaty%20%3AtreatyHasLeadGovernmentOrganisation%2F%20rdfs%3Alabel%20%3FLeadOrg%20.%7D%20%0A%20%20OPTIONAL%20%7B%3FTreaty%20%3AtreatyHasSeriesMembership%2F%20%3AseriesItemCitation%20%3FSeries.%7D%0A%20%20OPTIONAL%20%7B%3FTreaty%20%3AworkPackagedThingHasWorkPackagedThingWebLink%20%3FLink.%7D%0A%09%3FTreaty%20%3AworkPackagedThingHasWorkPackage%20%3FworkPackage%20.%0A%20%20%09%3FworkPackage%20%3AworkPackageHasProcedure%2Frdfs%3Alabel%20%3Fproc%0A%20%20FILTER(%3Fproc%20IN%20(%22Treaties%20subject%20to%20the%20Constitutional%20Reform%20and%20Governance%20Act%202010%22))%0A%20%20%20%3FworkPackage%20%3AworkPackageHasBusinessItem%20%3FprocStep%20.%0A%20%20%20%20%20%3FprocStep%20%3AbusinessItemDate%20%3Fdate%20.%0A%20%20%3FprocStep%20%3AbusinessItemHasProcedureStep%20id%3Acspzmb6w%20.%0A%20%20%3FprocStep%20%3AbusinessItemHasProcedureStep%2Frdfs%3Alabel%20%3FprocStepName.%20%0A%20%20%20%20%0A%20%20%20%7D%0A")
.mimeType('application/sparql-results+json')
.response(function(xhr) { return JSON.parse(xhr.responseText); })
.get(function(data) {
console.log(data);
var data1 = data.results.bindings;
//if (error) alert(error);
data1.forEach(function(d){
d.datetrimmed = d.date.value.split('+')[0];
console.log(d.datetrimmed)
d.Date1 = Date.parse(d.datetrimmed)
console.log(d.Date1)
d.Date2 = d3.timeMonth(d.Date1)
console.log(d.Date2)
console.log(d.LeadOrg.value)
})
x.domain(d3.extent(data.results.bindings, function(d) { return d.Date2; }));
var myColor = d3.scaleOrdinal()
.range(['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', 'red', '#e6beff', '#9a6324', '#fffac8', '#800000']);
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) { return x(d.Date2); }).strength(1))
.force("y", d3.forceY(height / 2))
.force("collide", d3.forceCollide(14))
.stop();
for (var i = 0; i < 120; ++i) simulation.tick();
g.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 5))
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-family", "Tahoma")
.style("text-decoration", "underline")
.style("fill", "#f2f0f7")
.text("All Treaties Subject to CRAG 2010 by Lead Organisation");
g.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top - 45))
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-family", "Tahoma")
.style("fill", "#f2f0f7")
.text("Click a point on the chart or hover over a square on the legend to highlight all treaties from a given department");
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(20));
g.append("text")
.attr("transform",
"translate(" + (width/2-100) + " ," +
(height + margin.top + 10) + ")")
.style("text-anchor", "middle")
.style("font-size", "14px")
.style("font-family", "Tahoma")
.style("fill", "#f2f0f7")
.text("Date the treaty was laid before the House of Commons");
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(data1)).enter().append("g");
The error occurs at this point further in, where it says that it cannot read property data of undefined:
cell.append("circle")
.attr("class", function(d){
console.log(d)
org = d.data.results.bindings.LeadOrg.value;
return org.replace(/[\s,]/g,'') })
Is there a way I should be defining d further in to the code?
This part of the code is still within the get request from the d3.request function. Is this going to cause problems?
Any help would be appreciated.
You never pass the data from cell to "circle". To do that, consider the following:
cell.append("circle")
.datum(function(d) { return d; })
.attr("class", function(d){
console.log(d)
org = d.data.results.bindings.LeadOrg.value;
return org.replace(/[\s,]/g,'') })
This takes the object d you assigned to cell, and returns it as the data object to assign to the circle as well.
Note that .datum is very related to .data, except that it expects one singular value, not an array of values.

D3.js: Line chart - tooltip and vertical line of hover

I have been working on an interactive line chart built using D3.js. One hover I would like a tool tip to be displayed with a vertical line. The vertical line comes out fine, however, I have problems related to the tool tip. The tool tip position is not on the graph and I am only getting the first data element.
Here is my code:
margin = {
top: 20,
right: 20,
bottom: 20,
left: 50
};
var width = Math.max(250, Math.min(700, d3.select("#content").width- margin.left - margin.right)),
height = 500;
var vis = d3.select("#line_chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
max_x = 0, max_y = 0, min = 100;
d3.csv("line.csv", function(error, data) {
for(i=0; i < data.length; i++){
max_y = Math.max(max_y, data[i].number);
max_x = Math.max(max_x, data[i].class);
min = Math.min(min, data[i].class);
}
xScale = d3.scale.linear().range([margin.left, width - margin.right]).domain([min, max_x]),
yScale = d3.scale.linear().range([height - margin.top, margin.bottom]).domain([0, max_y]),
xAxis = d3.svg.axis()
.scale(xScale),
yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (margin.left) + ",0)")
.call(yAxis);
var lineGen = d3.svg.line()
.x(function(d) {
return xScale(d.class);
})
.y(function(d) {
return yScale(d.number);
})
.interpolate("basis");
var pth = vis.append('svg:path')
.attr('d', lineGen(data))
.attr('stroke', '#000')
.attr('stroke-width', 3.5)
.attr('fill', 'none');
var totalLength = pth.node().getTotalLength();
pth
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2400)
.ease("linear")
.attr("stroke-dashoffset", 0);
//Line chart mouse over
var hoverLineGroup = vis.append("g")
.attr("class", "hover-line");
var hoverLine = hoverLineGroup
.append("line")
.attr("stroke", "#000")
.attr("x1", 10).attr("x2", 10)
.attr("y1", 0).attr("y2", height);
var hoverTT = hoverLineGroup.append('text')
.attr("class", "hover-tex capo")
.attr('dy', "0.35em");
var cle = hoverLineGroup.append("circle")
.attr("r", 4.5);
var hoverTT2 = hoverLineGroup.append('text')
.attr("class", "hover-text capo")
.attr('dy', "0.35em");
hoverLineGroup.style("opacity", 1e-6);
var rectHover = vis.append("rect")
.data(data)
.attr("class", "overlay")
.attr("width", width)
.attr("height", height);
rectHover
.on("mouseout", hoverMouseOff)
.on("mousemove", hoverMouseOn);
function hoverMouseOn(d) {
var mouse_x = d3.mouse(this)[0];
var mouse_y = d3.mouse(this)[1];
var graph_y = yScale.invert(mouse_y);
var graph_x = xScale.invert(mouse_x);
hoverTT.text("Marks: " + Math.round(graph_x * 100)/100);
hoverTT.attr('x', mouse_x + 10);
hoverTT.attr('y', yScale(d.class));
hoverTT2.text("Frequency: " + Math.round(d.number * 100)/100)
.attr('x', mouse_x + 10)
.attr('y', yScale(d.class) +15);
cle
.attr('x', mouse_x)
.attr('y', mouse_y);
hoverLine.attr("x1", mouse_x).attr("x2", mouse_x)
hoverLineGroup.style("opacity", 1);
}
function hoverMouseOff() {
hoverLineGroup.style("opacity", 1e-6);
};
});
}
The data:
class,number
25,1
30,7
35,11
45,13
50,21
55,23
60,30
65,41
75,39
80,24
85,14
90,4
95,8
100,2
I am not able to figure out what the issue is.
How can I solve this?
Thanks in advance.
EDIT: Here is the working code: https://jsfiddle.net/kan83q0m/1/
In your hoverMouseOn method, the variable d is undefined. You'll need to use d3.bisector to find the closest data point, like this:
var bisectDate = d3.bisector(function(d) { return d.class; }).left;
var mouseDate = xScale.invert(mouse_x);
var i = bisectDate(data, mouseDate);
var d0 = data[i - 1]
var d1 = data[i];
var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;
Also, I put the mousemove listener on 'vis' instead of 'rectHover':
vis
.on("mouseout", hoverMouseOff)
.on("mousemove", hoverMouseOn);
and used d.number instead of d.class for the y values. If you want the tooltip to always be on the line it gets a bit more complicated. Here's a working fiddle.
Might be easier to just put the tooltip at your mouse coordinates like in this fiddle.

d3.js x-coord displaying incorrectly iOS

Using d3.js the x coordinates of the graph are displaying at x=0 or on the y axis. The x axis represents a date and time and the y axis is the temperature. But this is only on an ipad or iphone. On my own machine, Linux, it displays correctly.
The graphs and all file can be seen at,
http://shanespi.no-ip.biz
The ipad/iphone display
While the correct graph is,
Here is the javascript,
var xScale = d3.scaleTime()
.domain([new Date(datahourly[0].date), d3.max(datahourly, function(d) {return new Date(d.date)})])
.range([0, (w-2*padding)]); // max x screen space is width - twice padding
var yScale = d3.scaleLinear()
.domain([0, d3.max(datahourly, function(d) {return d.temp})])
.range([(h-2*padding), 0]); // max y screen space is height - twice padding
var xAxis = d3.svg.axis(xScale) // d3 v.4
.ticks(9) // specify the number of ticks
/*.ticks(d3.time.days, 1) */
.tickFormat(d3.time.format('%H:00'))
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis(yScale)
.ticks(7)
.scale(yScale)
.orient("left");
var svg = d3.select('#hourly-readings')
.append('svg') // create an <svg> element
.attr('id', 'svgDaily')
.attr('width', w) // set its dimensions
.attr('height', h);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (2*padding - 15) + "," + (padding - 15) + ")")
.call(yAxis);
svg.append('g') // create a <g> element
.attr('class', 'axis') // specify classes
.attr("transform", "translate(" + (2*padding - 15) + "," + (h - padding - 15) + ")")
.call(xAxis); // let the axis do its thing
var lineFunctionStart = d3.svg.line()
.x(function(d) {return xScale(new Date(d.date)); })
.y(h - 2*padding - 5)
.interpolate("cardinal");
var lineFunction = d3.svg.line()
.x(function(d) {return xScale(new Date(d.date)); })
.y(function(d) {return yScale(d.temp); })
.interpolate("cardinal");
svg.append("path")
.attr('d', lineFunctionStart(datahourly))
.attr('stroke', "grey")
.attr('stroke-width', 1)
.style('fill', "white")
.attr("transform","translate(" + (2*padding - 13) + "," + (padding - 10) + ")")
.transition()
.duration(3000)
.attr('d', lineFunction(datahourly));
//var svg = d3.select('svg');
var svg = d3.select('#svgDaily');
svg.append("text") // text label for the x axis
.attr("x", 310)
.attr("y", h)
.style("font-size", "12")
.style("text-anchor", "middle")
.text("Time (1 hr. intervals)");
svg.append("text") // text label for the x axis
.attr('transform', 'rotate(-90)')
.attr("x", -85) // Because rotate is first x and y coordinates are transaposed
.attr("y", padding-17)
.style("font-size","10")
.style("text-anchor", "middle")
.text("Temp. Celcius");
var rects = svg.selectAll('circle')
.data(datahourly);
var newRects = rects.enter();
newRects.append('circle')
.attr('cx', function(d, i) { return (Math.random() * (w - 2*padding)) })
//.attr('cx', function(d, i) {
//return (5 + xScale(new Date(d.date)));
//})
.attr('cy', (h - (2*padding)))
.attr('r', 5)
.style('fill', "lightblue")
.attr("transform","translate(" + (2*padding - 18) + "," + (padding - 20) + ")")
.transition()
.duration(3000)
.delay(function(d, i) {return i * 300})
.attr('cx', function(d, i) {
return (5 + xScale(new Date(d.date)));
})
.attr('cy', function(d, i) {
return 10 + yScale(d.temp);
});
Here is the 'datahourly' data,
[
{
"date":"2016-12-14 22:01:01.799830",
"temp":"24.04"
},
{
"date":"2016-12-15 00:01:02.362875",
"temp":"23.03"
},
......................
{
"date":"2016-12-15 21:01:01.868593",
"temp":"21.93"
},
{
"date":"2016-12-15 22:01:02.278817",
"temp":"15.9"
},
{
"date":"2016-12-15 23:01:01.963714",
"temp":"21.63"
}
]
I am using Chrome on Linux and Safari on the ipad and iphone. But I did install chrome on the iphone and the graph is still incorrect.
Are there svg issues with iOS?
EDIT:
The main issue was that the time data was not parsed correctly,
This is the correct solution,
var data = [];
$.getJSON("data/data.json",
function(info){
data = info[0].fiveMinReadings;
//console.log(data);
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S.%L");
data.forEach(function(d) {
d.date = d.date.slice(0,-3);// remove microseconds
d.date = parseTime(d.date);
d.temp = +d.temp;
});
// Beginning of graph for 5 minute readings
var padding = 25;
var w = 600;
var h = 300;
var xScale = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([0, (w-2*padding)]); // max x screen space is width - twice padding
var yScale = d3.scaleLinear()
.domain([0,d3.max(data, function(d) {return d.temp})])
.range([(h-2*padding), 0]); // max y screen space is height - twice padding
var xAxis = d3.axisBottom(xScale) // d3 v.4
.tickFormat(d3.timeFormat('%H:%M '))
.scale(xScale);
var yAxis = d3.axisLeft(yScale)
.scale(yScale);
var svg = d3.select('#five-min-readings')
.append('svg') // create an <svg> element
.attr('id','svgHourly')
.attr("align","center")
.attr('width', w) // set its dimensions
.attr('height', h);
var valueline = d3.line()
.curve(d3.curveCardinal)
.x(function(d) { return xScale(d.date); })
.y(h - 2*padding - 4);
var valueline2 = d3.line()
.curve(d3.curveCardinal)
.x(function(d) { return xScale(d.date); })
.y(function(d) {return yScale(d.temp); });
svg.append("text") // text label for the x axis
.attr("x", 310)
.attr("y", h)
.style("font-size", "12")
.style("text-anchor", "middle")
.text("Time (5 min. intervals)");
svg.append("text") // text label for the x axis
.attr('transform', 'rotate(-90)')
.attr("x", -85) // Because rotate is first, x and y coordinates are transaposed
.attr("y", padding-17)
.style("font-size","10")
.style("text-anchor", "middle")
.text("Temp. Celcius");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (2*padding-15) + "," + (padding-15) + ")")
.call(yAxis);
svg.append('g') // create a <g> element
.attr('class', 'axis') // specify class
.attr("transform", "translate(" + (2*padding-15) + "," + (h - padding - 15) + ")")
.call(xAxis); // let the axis do its thing
svg.append('path')
.data([data])
.attr("class","line")
.attr('d', valueline)
.attr('stroke', "grey")
.attr('stroke-width', 1)
.style('fill', "white")
.attr("transform","translate(" + (2*padding - 13) + "," + (padding -10) + ")")
.transition()
.duration(3000)
.attr('d', valueline2 );
var svg = d3.select('#svgHourly');
var rects = svg.selectAll('circle')
.data(data);
var newRects = rects.enter();
newRects.append('circle')
.attr('cx', function(d, i) { return (Math.random() * (w - 2*padding)) })
.attr('cy', h - 2*padding)
.attr('r', 5)
.attr("id", function(d,i){return "circle" + i})
.style('fill', "lightblue")
.attr("transform","translate(" + (2*padding - 18) + "," + (padding - 20) + ")")
.transition()
.duration(3000)
.delay(function(d, i) {return i * 300})
.attr('cx', function(d, i) { return (5 + xScale(d.date)); })
.attr('cy', function(d, i) { return 10 + yScale(d.temp); });
}); // closes getJSON()
You can clearly see that your circles are getting the correct y ("cy") value, the error lies in the x ("cx") value.
The problem seems to be the use of new Date() in Safari with this pattern: yyyy-MM-dd.
In your code, given your data structure, you'll end up having something like this in the line generator:
.x(function(d) {
return xScale(new Date("2016-12-15 23:01:01.963714"));
})//the first date in your data ---^
And the same for your circles:
.attr('cx', function(d, i) {
return (5 + xScale(new Date("2016-12-15 23:01:01.963714")));
})
Apparently, this is supported by Chrome and Firefox, but not by Safari. Curiously, the pattern (yyyy-MM-dd) is included in the ECMA standard, so this is probably a Safari specific issue.
According to this answer, it will work if you include a T (I didn't test it):
.x(function(d) {
return xScale(new Date("2016-12-15T23:01:01.963714"));
})
Alternatively, remove the new Date() and parse the dates using D3 (d3.timeParse() in d3 v4.x and format.parse() in d3 v3.x).
EDIT: Summarising, you have two possible solutions:
Solution 1: Remove all new Date functions (both on the line generator and in all scales that use it) and parse the date using d3. You said that you're using d3 v3.x, but your code is using d3 v4.x instead. Nevertheless, here is how to do it using d3 v3.x:
var date = "2016-12-14 22:01:01.799830".slice(0,-3);
var format = d3.time.format("%Y-%m-%d %H:%M:%S.%L");
var myDate = format.parse(date);
console.log(myDate);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Note: I'm removing the last 3 digits because d3 can parse onli miliseconds, not microseconds.
Solution 2: Keep your new Date functions, but add a T as already discussed above:
var date = "2016-12-14 22:01:01.799830".replace(/\s/, 'T');
console.log(date);
console.log(new Date(date));
Seems there's a issue with compatibility between dc.js 1.7.5 the stable version and d3.js v4. dc.js compatibility with v4 of d3.js and my new post on this https://stackoverflow.com/questions/41389307/dc-js-1-7-5-not-compatible-with-d3-js-v4
Wow, this method works commonly in both android and iOS, saved my hours of browsing about this issue
var commonDate = item.created_at.replace(/\s/, "T");
var date = new Date(commonDate);
Long story short: I have used d3 in the ionic angular version and had the same issue in iOS alone

Add legend to d3 scatterplot matrix

I'm trying to add a legend to a d3 scatterplot matrix (using this example as a template: http://bl.ocks.org/mbostock/4063663), and while the scatterplot itself is displaying as expected, I have been unable to successfully add a legend. The code for the plot and one of the attempts at adding a legend are below:
var width = 960,
size = 150,
padding = 19.5;
var x = d3.scale.linear()
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear()
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.category10();
d3.csv(datafilename, function(error, dataset) {
var domainByTrait = {},
traits = d3.keys(dataset[0]).filter(function(d) { return d !== "class"; }),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(dataset, function(d) { return d[trait]; });
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#visualizationDiv").append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]); d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
// Titles for the diagonal.
cell.filter(function(d) { return d.i === d.j; }).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) { return d.x; });
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
cell.selectAll("circle")
.data(dataset)
.enter().append("circle")
.attr("cx", function(d) { return x(d[p.x]); })
.attr("cy", function(d) { return y(d[p.y]); })
.attr("r", 3)
.style("fill", function(d) { return color(d.class); });
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function(d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0]
|| e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty()) svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
d3.select(self.frameElement).style("height", size * n + padding + 20 + "px");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr('transform', 'translate(-20,50)');
legend.selectAll('rect')
.data(dataset)
.enter()
.append("rect")
.attr("x", width - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
legend.selectAll('text')
.data(dataset)
.enter()
.append("text")
.attr("x", width - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.text(function(d) { return d.class; });
});
Among my other unsuccessful attempts at adding a legend are
var legend = svg.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 28)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d.class); });
legend.append("text")
.attr("x", width - 34)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d.class; });
and
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
legend.append("rect")
.attr("x", width - 45)
.attr("y", 25)
.attr("height", 50)
.attr("width", 50)
.each(function(d, i) {
var g = d3.select(this);
g.append("rect")
.attr("x", width - 65)
.attr("y", i*25)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
g.append("text")
.attr("x", width - 50)
.attr("y", i * 25 + 8)
.attr("height",30)
.attr("width",100)
.style("fill", function(d) { return color(d.class); })
.text(function(d) { return d.class; });
all based on examples I've found on the web. None of these approaches seem to be working - I must be missing something here. Any insights or suggestions would be greatly appreciated.
The problem is right at the beginning:
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
The selectAll('g') is going to select one of the groups already in your diagram, and then nothing will happen because enter() indicates that everything from there on (including the value that gets saved to the legend variable) only applies to groups that don't exist yet.
I'm pretty sure this legend code is supposed to be run from within its own <g> element. That way, it won't interfere with the rest of your graph.
var legendGroup = svg.append('g')
.attr('class', 'legend')
.attr('transform', /* translate as appropriate */);
var legendEntry = legendGroup.selectAll('g')
.data(dataset);
//create one legend entry for each series in the dataset array
//if that's not what you want, create an array that has one
//value for every entry you want in the legend
legendEntry.enter().append("g")
.attr("class", "legend-entry")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
//shift each entry down by approx 1 line (20px)
legendEntry.append("rect") //add a square to each entry
/* and so on */

Categories