D3 line chart and adding data points real time - javascript

I am beginner to d3 and trying to create real time chart which adds in new values on the go. I want the chart to shift the old points to the left as new points are added. Below is my code but for some reason, the browser freezes with the code (I have commented out the .on() line in the end that causes the freeze).
What am I doing wrong?
<!DOCTYPE html>
<head></head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js">
</script>
<script>
<!DOCTYPE html>
<head></head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js">
</script>
<script>
var t = -1;
var n = 40;
var duration = 750;
var data = [];
console.log('hello');
function next() {
return {
time: ++t,
value: Math.random() * 10
}
}
var margin = {
top: 6,
right: 0,
bottom: 20,
left: 40
},
width = 560 - margin.right,
height = 120 - margin.top - margin.bottom;
var xScale = d3.scaleTime()
.domain([t - n + 1, t])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
var line = d3.line()
.x((d) => xScale(d.time))
.y((d) => yScale(d.value));
var svg = d3.select('body').append('p').append('svg');
var chartArea = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
chartArea.append('defs').append('clipPath')
.attr('id', 'clip2')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);
chartArea.append('rect')
.attr('class', 'bg')
.attr('x', 0)
.attr('y', 0)
.attr('width', this.chartWidth)
.attr('height', this.chartHeight);
var xAxis = d3.axisBottom(xScale);
var xAxisG = chartArea.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`);
xAxisG.call(xAxis);
d3.selectAll('x-axis path').style('stroke', 'red')
.style('stroke-width', 2);
var yAxis = d3.axisLeft(yScale);
var yAxisG = chartArea.append('g').attr('class', 'y-axis');
yAxisG.call(yAxis);
var grids = chartArea.append('g')
.attr('class', 'grid')
.call(d3.axisLeft(yScale).tickSize(-(width)).tickFormat((domain, number) => {
return ""
}));
var pathsG = chartArea.append('g')
.attr('id', 'paths')
.attr('class', 'paths')
.attr('clip-path', 'url(#clip2)');
tick();
function tick() {
console.log('working');
var newValue = {
time: ++t,
value: Math.random() * 10
};
data.push(newValue);
xScale.domain([newValue.time - n + 2, newValue.time]);
xAxisG.transition().duration(500).ease(d3.easeLinear).call(xAxis);
console.log('is it?');
var minerG = pathsG.selectAll('.minerLine').data([data]);
var minerGEnter = minerG.enter()
.append('g')
.attr('class', 'minerLine')
.merge(minerG);
var minerSVG = minerGEnter.selectAll('path').data((d) => [d]);
var minerSVGEnter = minerSVG.enter()
.append('path')
.attr('class', 'line')
.merge(minerSVG)
.transition()
.duration(500)
.ease(d3.easeLinear, 2)
.attr('d', line(data))
.on('end', () => {
requestAnimationFrame(tick)
})
}
</script>
</body>
</html>

The problem is caused by the fact that tick is called immediately and synchronously at the end of the transition. The CPU process executing the JavaScript remains busy updating data and chart, and is not available to do anything else on this tab.
One way to fix this is to use Window.requestAnimationFrame().
.on('end', () => {
requestAnimationFrame(tick)
})
The updated snippet below shows this solution in action.
It does not fix other issues not mentioned in the question, like the fact that no data is shown in the chart.
<!DOCTYPE html>
<head></head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js">
</script>
<script>
var t = -1;
var n = 40;
var duration = 750;
var data = [];
console.log('hello');
function next() {
return {
time: ++t,
value: Math.random() * 10
}
}
var margin = {
top: 6,
right: 0,
bottom: 20,
left: 40
},
width = 560 - margin.right,
height = 120 - margin.top - margin.bottom;
var xScale = d3.scaleTime()
.domain([t - n + 1, t])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, 10])
.range([height, 0]);
var line = d3.line()
.x((d) => xScale(d.time))
.y((d) => yScale(d.value));
var svg = d3.select('body').append('p').append('svg');
var chartArea = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
chartArea.append('defs').append('clipPath')
.attr('id', 'clip2')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);
chartArea.append('rect')
.attr('class', 'bg')
.attr('x', 0)
.attr('y', 0)
.attr('width', this.chartWidth)
.attr('height', this.chartHeight);
var xAxis = d3.axisBottom(xScale);
var xAxisG = chartArea.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`);
xAxisG.call(xAxis);
d3.selectAll('x-axis path').style('stroke', 'red')
.style('stroke-width', 2);
var yAxis = d3.axisLeft(yScale);
var yAxisG = chartArea.append('g').attr('class', 'y-axis');
yAxisG.call(yAxis);
var grids = chartArea.append('g')
.attr('class', 'grid')
.call(d3.axisLeft(yScale).tickSize(-(width)).tickFormat((domain, number) => {
return ""
}));
var pathsG = chartArea.append('g')
.attr('id', 'paths')
.attr('class', 'paths')
.attr('clip-path', 'url(#clip2)');
tick();
function tick() {
console.log('working');
var newValue = {
time: ++t,
value: Math.random() * 10
};
data.push(newValue);
xScale.domain([newValue.time - n + 2]);
xAxisG.transition().duration(500).ease().call(xAxis);
console.log('is it?');
var minerG = pathsG.selectAll('.minerLine').data([data]);
var minerGEnter = minerG.enter()
.append('g')
.attr('class', 'minerLine')
.merge(minerG);
var minerSVG = minerGEnter.selectAll('path').data((d) => [d]);
var minerSVGEnter = minerSVG.enter()
.append('path')
.attr('class', 'line')
.merge(minerSVG)
.transition()
.duration(500)
.ease(d3.easeLinear, 2)
.attr('d', line(data))
.on('end', () => {
requestAnimationFrame(tick)
})
}
</script>
</body>
</html>

Related

D3.js rect no display on chart

I started the D3.js challenge on FreeCodeCamp, the problem is that I solved it with the chart but it only gives me a display on the rectum, only one with the width and height that it I put, I'll show the code below.
The entire code on
<script>
//set d3
var w = 1000, h = 500;
var padding = 50;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
//title
svg.append('text')
.attr('x', w / 2)
.attr('y', 50)
.text('United States GDP')
fetch('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((result)=>result.json())
.then((data)=>{
var the_data = data['data']
//get vals
var get_max = d3.max(data['data'])
var get_mix = d3.min(data['data'])
//for x
var max_x = Number(get_max[0].split('-')[0])
var min_x = Number(get_mix[0].split('-')[0])
//for y
var max_y = get_max[1]
var min_y = get_mix[1]
var xScale = d3.scaleLinear()
.domain([min_x, max_x])
.range([padding, w-padding])
var yScale = d3.scaleLinear()
.domain([min_y, max_y])
.range([h-padding, padding])
//the_chars
for(var i in the_data){
var get_year = Number(the_data[i][0].split('-')[0])
the_data[i][0] = get_year
}
svg.selectAll('rect')
.data(the_data)
.enter()
.append('rect')
.attr("x", (d) => { xScale(d[0]) })
.attr('y', (d)=>{ yScale(d[1]) })
.attr("width", 200)
.attr("height", 20)
//axis
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
//display axis
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append('g')
.attr('transform', 'translate(' + padding + ', 0)')
.call(yAxis)
})
Now, what I need to do to display the charts!
I mention that the script tags are embedded in the body
Problem: Arrow functions without a return value. Solution: Instead use an explicit or an implicit return.
.attr("x", (d) => { xScale(d[0]) }) // returns undefined
.attr("x", (d) => xScale(d[0])) // implicit return
.attr("x", (d) => { return xScale(d[0]) }) // explicit return
Problem: Fixed height value. Solution Evaluate the height of each based on the GDP value (d[1]) instead.
.attr('height', 20) // fixed height
.attr('height', d => yScale(min_y) - yScale(d[1]))
// subtract from min range to account for padding and inverted y coordinates in SVG
Full solution in this codepen

Why is it showing only one rect for the last value of each year in my d3js bar chart?

Trying to build a bar chart and, I don't know why, it's only showing the last value for each year and not all the values in both arrays, and that's what I thought that it was supposed to happen. How can I fix that?
let url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json";
const padding = 50;
const height = 460;
const width = 940;
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var arr = [];
var years = [];
d3.json(url, function(data) {
for (let i = 0; i < data.data.length; i++) {
arr[i] = data.data[i];
years[i] = parseInt(data.data[i][0].slice(0,4));
}
const yScale = d3.scaleLinear()
.domain([0, d3.max(arr, (d) => d[1])])
.range([height - padding, padding]);
const xScale = d3.scaleLinear()
.domain([d3.min(years, d => d), d3.max(years, (d) => d)])
.range([padding, width - padding]);
let bandScale = d3.scaleBand().domain(years, d => d).range([padding, width - padding]);
const xAxis = d3.axisBottom(bandScale)
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(xAxis);
svg.append('g')
.attr('transform', 'translate(' + padding + ', 0)')
.call(yAxis)
svg.selectAll('rect')
.data(arr)
.enter()
.append('rect')
.attr('fill', 'blue')
.attr('height', d => height - padding - yScale(d[1]))
.attr('width', d => bandScale.bandwidth())
.attr('x', (d, i) => (bandScale(years[i])))
.attr('y', d => yScale(d[1]))
.append('title')
.text((d, i) => years[i] + ': ' + d[1])
});
<script src="https://d3js.org/d3.v4.min.js"></script>

D3 Grouped Bar Chart From Arrays

I'm really having trouble with D3 and need some help changing my existing barchart to be a grouped barchart The barchart is being used within a tooltip and currently looks like:
Each colour represents a sector of industry (pink = retail, teal = groceries...etc).
I need to change the bar chart so that it compares the percentage change in each industry with the world average percentage change in this industry.
At the moment the bar chart is being created from an array of data. I also have an array with the world percentage values.
So imagine:
countryData = [10,-20,-30,-63,-23,20],
worldData = [23,-40,-23,-42,-23,40]
Where index 0 = retail sector, index 1 = grocery sector, etc.
I need to plot a grouped barchart comparing each sector to the world average (show the world average in red). This is a bit tricky to explain so I drew it for you (...excuse the shoddy drawing).
Please can someone help me change my existing tooltip?
Here's the current code. If you want to simulate the data values changing.
If you want to scrap my existing code that's fine.
.on('mouseover', ({ properties }) => {
// get county data
const mobilityData = covid.data[properties[key]] || {};
const {
retailAverage,
groceryAverage,
parksAverage,
transitAverage,
workplaceAverage,
residentialAverage,
} = getAverage(covid1);
let avgArray = [retailAverage, groceryAverage, parksAverage, transitAverage, workplaceAverage, retailAverage];
let categoriesNames = ["Retail", "Grocery", "Parks", "Transit", "Workplaces", "Residential"];
// create tooltip
div = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
div.html(properties[key]);
div.transition()
.duration(200)
.style('opacity', 0.9);
// calculate bar graph data for tooltip
const barData = [];
Object.keys(mobilityData).forEach((industry) => {
const stringMinusPercentage = mobilityData[industry].slice(0, -1);
barData.push(+stringMinusPercentage); // changing it to an integer value, from string
});
//combine the two lists for the combined bar graph
var combinedList = [];
for(var i = 0; i < barData.length; i++) {
const stringMinusPercentage2 = +(avgArray[i].slice(0, -1));
const object = {category: categoriesNames[i], country: barData[i], world: stringMinusPercentage2}
combinedList.push(object); //Push object into list
}
console.log(combinedList);
// barData = barData.sort(function (a, b) { return a - b; });
// sort into ascending ^ keeping this in case we need it later
const height2 = 220;
const width2 = 250;
const margin = {
left: 50, right: 10, top: 20, bottom: 15,
};
// create bar chart svg
const svgA = div.append('svg')
.attr('height', height2)
.attr('width', width2)
.style('border', '1px solid')
.append('g')
// apply the margins:
.attr('transform', `translate(${[`${margin.left},${margin.top}`]})`);
const barWidth = 30; // Width of the bars
// plot area is height - vertical margins.
const chartHeight = height2 - margin.top - margin.left;
// set the scale:
const yScale = d3.scaleLinear()
.domain([-100, 100])
.range([chartHeight, 0]);
// draw some rectangles:
svgA
.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', (d, i) => i * barWidth)
.attr('y', (d) => {
if (d < 0) {
return yScale(0); // if the value is under zero, the top of the bar is at yScale(0);
}
return yScale(d); // otherwise the rectangle top is above yScale(0) at yScale(d);
})
.attr('height', (d) => Math.abs(yScale(0) - yScale(d))) // the height of the rectangle is the difference between the scale value and yScale(0);
.attr('width', barWidth)
.style('fill', (d, i) => colours[i % 6]) // colour the bars depending on index
.style('stroke', 'black')
.style('stroke-width', '1px');
// Labelling the Y axis
const yAxis = d3.axisLeft(yScale);
svgA.append('text')
.attr('class', 'y label')
.attr('text-anchor', 'end')
.attr('x', -15)
.attr('y', -25)
.attr('dy', '-.75em')
.attr('transform', 'rotate(-90)')
.text('Percentage Change (%)');
svgA.append('g')
.call(yAxis);
})
.on('mouseout', () => {
div.style('opacity', 0);
div.remove();
})
.on('mousemove', () => div
.style('top', `${d3.event.pageY - 140}px`)
.style('left', `${d3.event.pageX + 15}px`));
svg.append('g')
.attr('transform', 'translate(25,25)')
.call(colorLegend, {
colorScale,
circleRadius: 10,
spacing: 30,
textOffset: 20,
});
};
drawMap(svg1, geoJson1, geoPath1, covid1, key1, 'impact1');
drawMap(svg2, geoJson2, geoPath2, covid2, key2, 'impact2');
};
In short I would suggest you to use two Band Scales for x axis. I've attached a code snippet showing the solution.
Enjoy ;)
//Assuming the following data final format
var finalData = [
{
"groupKey": "Retail",
"sectorValue": 70,
"worldValue": 60
},
{
"groupKey": "Grocery",
"sectorValue": 90,
"worldValue": 90
},
{
"groupKey": "other",
"sectorValue": -20,
"worldValue": 30
}
];
var colorRange = d3.scaleOrdinal().range(["#00BCD4", "#FFC400", "#ECEFF1"]);
var subGroupKeys = ["sectorValue", "worldValue"];
var svg = d3.select("svg");
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = +svg.attr("width") - margin.left - margin.right;
var height = +svg.attr("height") - margin.top - margin.bottom;
var container = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// The scale spacing the groups, your "sectors":
var x0 = d3.scaleBand()
.domain(finalData.map(d => d.groupKey))
.rangeRound([0, width])
.paddingInner(0.1);
// The scale for spacing each group's bar, your "sector bar":
var x1 = d3.scaleBand()
.domain(subGroupKeys)
.rangeRound([0, x0.bandwidth()])
.padding(0.05);
var yScale = d3.scaleLinear()
.domain([-100, 100])
.rangeRound([height, 0]);
//and then you will need to append both, groups and bars
var groups = container.append('g')
.selectAll('g')
.data(finalData, d => d.groupKey)
.join("g")
.attr("transform", (d) => "translate(" + x0(d.groupKey) + ",0)");
//define groups bars, one per sub group
var bars = groups
.selectAll("rect")
.data(d => subGroupKeys.map(key => ({ key, value: d[key], groupKey: d.groupKey })), (d) => "" + d.groupKey + "_" + d.key)
.join("rect")
.attr("fill", d => colorRange(d.key))
.attr("x", d => x1(d.key))
.attr("width", (d) => x1.bandwidth())
.attr('y', (d) => Math.min(yScale(0), yScale(d.value)))
.attr('height', (d) => Math.abs(yScale(0) - yScale(d.value)));
//append x axis
container.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
//append y axis
container.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale))
.append("text")
.attr("x", 2)
.attr("y", yScale(yScale.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Values");
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg width="600" height="400"></svg>

D3 data do not rescale properly after pan or zoom

I am working with D3 v4 and JS. I have a scatter plot with a predefined set of data loaded along with axes with the ability to pan and zoom. I need to be able to then dynamically add points and eventually output them in data space not pixel space. I am using the "rescaleX" and "rescaleY" methods of the zoom object. They work fine for rescaling the axes but, when I try to add new points, the location of the plotted point does correspond to the mouse location. Here is a simplified version of the code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var data = [{x:17,y:3},
{x:20,y:16},
{x:2,y:13},
{x:19,y:10},
{x:13,y:15},
{x:2,y:2},
{x:5,y:8},
{x:11,y:19},
{x:20,y:12},
{x:10,y:20}];
var width = 600;
var height = 600;
var padding = 50;
var newXscale, newYscale;
var dataScale = d3.scaleLinear()
.domain([0,21])
.range([0, width]);
var svg = d3.select('body').append('svg')
.attr('width', width+2*padding)
.attr('height', height+2*padding)
.on('click', clicked);
var xAxis = d3.axisTop()
.scale(dataScale);
var gX = svg.append('g')
.attr('transform','translate(50,50)')
.call(xAxis);
var yAxis = d3.axisLeft()
.scale(dataScale);
var gY = svg.append('g')
.attr('transform','translate(50,50)')
.call(yAxis);
var canvas = svg.append('g')
var points = canvas.append('g');
points.selectAll('circle').data(data)
.enter().append('circle')
.attr('cx', function(d) {return dataScale(d.x)+padding})
.attr('cy', function(d) {return dataScale(d.y)+padding})
.attr('r', 5);
var zoom
var zoomOn = false;
window.addEventListener('keydown', function (event) {
if (event.key=='z') {
if (zoomOn) {
d3.select('#zoomBox').remove();
zoomOn = false;
} else {
zoom = d3.zoom()
.scaleExtent([0.1, 10])
.on('zoom', zoomed);
svg.append("rect")
.attr('cursor','move')
.attr("width", width+padding*2)
.attr("height", height+padding*2)
.attr('id','zoomBox')
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
zoomOn = true;
}
}
});
function zoomed() {
canvas.attr("transform", d3.event.transform)
newXscale = d3.event.transform.rescaleX(dataScale);
newYscale = d3.event.transform.rescaleY(dataScale);
gX.call(xAxis.scale(newXscale));
gY.call(yAxis.scale(newYscale));
}
function clicked() {
var coords = d3.mouse(this);
points.append('circle')
.attr('cx',coords[0])
.attr('cy',coords[1])
.attr('r',5);
var x = newXscale.invert(coords[0]-padding);
var y = newYscale.invert(coords[1]-padding);
console.log(x+' '+y);
}
</script>
</body>
</html>
Create a variable to store the zoom level:
newZscale = d3.event.transform.k;
And, in your clicked function, use the dateScale to plot the new circles, dividing the padding by the zoom level:
function clicked() {
var coords = d3.mouse(this);
if (newXscale && newYscale) {
var x = newXscale.invert(coords[0] - padding);
var y = newYscale.invert(coords[1] - padding);
};
console.log(newZscale);
points.append('circle')
.attr('cx', (!x) ? coords[0] : dataScale(x) + (padding / newZscale))
.attr('cy', (!y) ? coords[1] : dataScale(y) + (padding / newZscale))
.attr('r', 5);
console.log(x + ' ' + y);
}
Here is the demo:
var data = [{
x: 17,
y: 3
}, {
x: 20,
y: 16
}, {
x: 2,
y: 13
}, {
x: 19,
y: 10
}, {
x: 13,
y: 15
}, {
x: 2,
y: 2
}, {
x: 5,
y: 8
}, {
x: 11,
y: 19
}, {
x: 20,
y: 12
}, {
x: 10,
y: 20
}];
var width = 600;
var height = 600;
var padding = 50;
var newXscale, newYscale, newZscale;
var dataScale = d3.scaleLinear()
.domain([0, 21])
.range([0, width]);
var svg = d3.select('body').append('svg')
.attr('width', width + 2 * padding)
.attr('height', height + 2 * padding)
.on('click', clicked);
var xAxis = d3.axisTop()
.scale(dataScale);
var gX = svg.append('g')
.attr('transform', 'translate(50,50)')
.call(xAxis);
var yAxis = d3.axisLeft()
.scale(dataScale);
var gY = svg.append('g')
.attr('transform', 'translate(50,50)')
.call(yAxis);
var canvas = svg.append('g')
var points = canvas.append('g');
points.selectAll('circle').data(data)
.enter().append('circle')
.attr('cx', function(d) {
return dataScale(d.x) + padding
})
.attr('cy', function(d) {
return dataScale(d.y) + padding
})
.attr('r', 5);
var zoom
var zoomOn = false;
window.addEventListener('keydown', function(event) {
if (event.key == 'z') {
if (zoomOn) {
d3.select('#zoomBox').remove();
zoomOn = false;
} else {
zoom = d3.zoom()
.scaleExtent([0.1, 10])
.on('zoom', zoomed);
svg.append("rect")
.attr('cursor', 'move')
.attr("width", width + padding * 2)
.attr("height", height + padding * 2)
.attr('id', 'zoomBox')
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
zoomOn = true;
}
}
});
function zoomed() {
canvas.attr("transform", d3.event.transform)
newXscale = d3.event.transform.rescaleX(dataScale);
newYscale = d3.event.transform.rescaleY(dataScale);
newZscale = d3.event.transform.k;
gX.call(xAxis.scale(newXscale));
gY.call(yAxis.scale(newYscale));
}
function clicked() {
var coords = d3.mouse(this);
if (newXscale && newYscale) {
var x = newXscale.invert(coords[0] - padding);
var y = newYscale.invert(coords[1] - padding);
};
points.append('circle')
.attr('cx', (!x) ? coords[0] : dataScale(x) + (padding / newZscale))
.attr('cy', (!y) ? coords[1] : dataScale(y) + (padding / newZscale))
.attr('r', 5);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I figured it out. The problem lied in the fact that I was removing the zoom box when toggling the zoom. I switched the event listener to just hide the box and unbind the pointer-events. Here is the final code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var data = [{x:17,y:3},
{x:20,y:16},
{x:2,y:13},
{x:19,y:10},
{x:13,y:15},
{x:2,y:2},
{x:5,y:8},
{x:11,y:19},
{x:20,y:12},
{x:10,y:20}];
var width = 600;
var height = 600;
var padding = 50;
var newXscale, newYscale;
var zoomOn = false;
var xScale = d3.scaleLinear()
.domain([0,21])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0,21])
.range([0, width]);
var svg = d3.select('body').append('svg')
.attr('width', width+2*padding)
.attr('height', height+2*padding)
.on('click', clicked)
.attr('cursor','crosshair');
var xAxis = d3.axisTop()
.scale(xScale);
var gX = svg.append('g')
.attr('transform','translate(50,50)')
.call(xAxis);
var yAxis = d3.axisLeft()
.scale(yScale);
var gY = svg.append('g')
.attr('transform','translate(50,50)')
.call(yAxis);
var canvas = svg.append('g')
var points = canvas.append('g');
points.selectAll('circle').data(data)
.enter().append('circle')
.attr('cx', function(d) {return xScale(d.x)+padding})
.attr('cy', function(d) {return yScale(d.y)+padding})
.attr('r', 5);
var zoom = d3.zoom()
.scaleExtent([0.1, 10])
.on('zoom', zoomed);
var zoombox = svg.append("rect")
.attr("width", width+padding*2)
.attr("height", height+padding*2)
.attr('id','zoomBox')
.style("fill", "none")
.style("pointer-events", "none")
.style('visibility','off')
.call(zoom);
window.addEventListener('keydown', function (event) {
if (event.key=='z') {
if (zoomOn) {
d3.select('#zoomBox')
.attr('cursor','auto')
.style('pointer-events','none')
.style('visibility','off');
zoomOn = false;
} else {
d3.select('#zoomBox')
.attr('cursor','move')
.style('pointer-events','all')
.style('visibilty','on')
zoomOn = true;
}
}
});
function zoomed() {
canvas.attr("transform", d3.event.transform)
newXscale = d3.event.transform.rescaleX(xScale);
newYscale = d3.event.transform.rescaleY(yScale);
gX.call(xAxis.scale(newXscale));
gY.call(yAxis.scale(newYscale));
newZscale = d3.event.transform.k;
}
function clicked() {
var coords = d3.mouse(this);
if (newXscale && newYscale) {
var x = newXscale.invert(coords[0] - padding);
var y = newYscale.invert(coords[1] - padding);
};
console.log(newZscale);
points.append('circle')
.attr('cx', (!x) ? coords[0] : xScale(x) + (padding / newZscale))
.attr('cy', (!y) ? coords[1] : yScale(y) + (padding / newZscale))
.attr('r', 5);
console.log(x + ' ' + y);
}
</script>
</body>
</html>

Plotting normal density function with d3 js

I'm trying to plot this continuous function with D3 js version 4 but i'm having many problems with arrays. this is my javascript code:
var x = d3.range(-4., 4.1, 0.1)
var fnorm = x => (1. / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * Math.pow(x, 2))
var y = new Array()
for (var i = 0 ; i < x.length ; i++) {
y[i] = fnorm(x[i])
}
var dataset = []
for (var j = 0; j < x.length; j++) {
dataset[j] = []
dataset[j][0] = x[j]
dataset[j][1] = y[j]
}
console.log(dataset[0])
console.log(dataset[1])
var w = 500
var h = 500
var padding = 50
var xScale = d3.scaleLinear()
.domain([d3.min(x, function(d) { return d }), d3.max(x, function(d) { return d })])
.range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, 0.4])
.range([h - padding, padding])
function mycanvas() {
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
svg.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'blue')
// Define the axis
var xAxis = d3.axisBottom().scale(xScale).ticks(9)
var yAxis = d3.axisLeft().scale(yScale).ticks(9)
// Create the axis
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (h - padding) + ')')
.call(xAxis)
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + padding + ',0)')
.call(yAxis)
svg.selectAll('line')
.data(dataset)
.enter()
.append('line')
.attr('x1', function(d) {
return xScale(d[0])
})
.attr('y1', function(d) {
return yScale(d[1])
})
.attr('x2', function(d) {
return xScale(d[1])
})
.attr('y2', function(d) {
return yScale(d[0])
})
.attr('stroke', 'white')
}
function main() {
mycanvas()
}
window.onload = main
<script src="https://d3js.org/d3.v4.min.js"></script>
The main problem is when i try to plot with svg line i don't know how to fix the right way to call and apply dataset array with x1,y1,x2,y2
You could use d3.line to produce a path from dataset:
var line = d3.line()
.x(function(d) { return xScale(d[0]);})
.y(function(d) { return yScale(d[1]);});
svg.append("path")
.attr("d", line(dataset))
.attr("stroke", "white")
.attr("fill", "none");
Demo: https://jsfiddle.net/LukaszWiktor/kkxe5sbc/

Categories

Resources