I am working with d3.js and draw scatter plot chart using the scaleTime to compute the values on x-axis and y-axis
but the scales are not computing the data in right way.
( 1 )The interval of seconds in the y-axis should be only 15 seconds
and that should start at 37:00.
( 2 ) There should be a slight gap at the beginning of the x-axis
and the tick at the end of the x-axis should have a 2016 value.
as shown in the picture below
retriveData();
async function retriveData() {
let response = await fetch(
"https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/cyclist-data.json"
);
let json = await response.json();
chart(json);
}
function chart(dataset) {
const width = 800,
height = 500,
padding = 60;
let svg = d3
.select("body")
.append("svg")
.style("width", width)
.style("height", height);
const parsedDate = d3.timeParse("%Y");
const dates = [];
for (let i = 0; i < dataset.length; i++) {
dates.push(parsedDate(dataset[i].Year));
}
const extentDate = d3.extent(dates);
const xScale = d3
.scaleTime()
.domain(extentDate)
.range([padding, width - padding]);
const parsedTime = d3.timeParse("%M:%S");
const minutes = [];
for (let i = 0; i < dataset.length; i++) {
minutes.push(parsedTime(dataset[i].Time));
}
const extentTime = d3.extent(minutes).sort((a, b) => b - a);
const yScale = d3
.scaleTime()
.domain(extentTime)
.range([height - padding, padding]);
let format = d3.timeFormat("%M:%S");
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale).tickFormat(format);
svg
.append("g")
.attr("transform", `translate(0,${height - padding})`)
.call(xAxis);
svg.append("g").attr("transform", `translate(${padding},0)`).call(yAxis);
}
[solution][1]
[1]: https://i.stack.imgur.com/D40dF.png
Related
I have successfully drawn the audio volume on a line graph. (line 63)
I have also successfully drawn a vertical line on the line chart to indicate the position of the audio playback. (line 65)
The current position where the sound is being played was also successfully obtained. (line 16)
I want to synchronise the blue vertical line of the line graph with the current position where the audio is played.
How can I achieve this animation?
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();
var fileReader = new FileReader;
var audioData;
var source;
var currentTime;
var decodedData;
var channelDataL;
var channelDataR;
var volumeData;
var scale;
var svgArea;
document.getElementById('myButton').addEventListener('click', function (e) {
currentTime = audioCtx.currentTime;
console.log(audioCtx.currentTime);
});
document.getElementById("fileInput").onchange = () => {
var fileList = document.getElementById("fileInput").files;
fileReader.readAsArrayBuffer(fileList[0]);
}
fileReader.onload = function () {
var sampleRate;
var duration;
audioData = fileReader.result;
audioCtx.decodeAudioData(audioData).then(function (decodedDataTemp) {
decodedData = decodedDataTemp;
sampleRate = decodedData.sampleRate;
duration = decodedData.duration;
channelDataL = decodedData.getChannelData(0);
channelDataR = decodedData.getChannelData(1);
volumeData = [];
for (var i = 0; i < channelDataL.length; i++) {
var temp = channelDataL[i];
if (temp < 0) {
temp = - temp;
}
if (i == 0) {
volumeData[0] = temp;
} else {
volumeData.push(temp);
}
}
const contents = d3.select('#chart--wrapper');
svgArea = contents.append("svg").attr("id","svg");
var dataset = formatD3Data(volumeData);
const padding = 30;
const width = contents.node().clientWidth - padding;
const height = contents.node().clientHeight - padding;
scale = setupGraph(contents, svgArea, dataset, padding, width, height);
var color = d3.rgb("#a785cc");
console.log(dataset);
drawLine(svgArea, dataset, scale, color, "none");
svgArea.append('line')
.attr('id', 'lineY')
.attr('class', 'lineY')
.attr("stroke", "blue")
.attr('x1', scale.x(2000)).attr('y1', scale.y(0))
.attr('x2', scale.x(2000)).attr('y2', scale.y(0.1));
});
};
document.getElementById('playButton').addEventListener('click', function (e) {
source = audioCtx.createBufferSource();
source.buffer = decodedData;
source.connect(audioCtx.destination);
source.start();
});
document.getElementById('pauseButton').addEventListener('click', function (e) {
source.stop(0);
});
function setupGraph(contents, svg, dataset, padding, width, height) {
var x = svg
.append("g")
.attr("class", "axis axis-x")
var y = svg
.append("g")
.attr("class", "axis axis-y")
var d3scale = getD3Scale(dataset, padding, width, height);
const format = d3.format(",.2r");
const xTicks = 6;
const axisx = d3
.axisBottom(d3scale.x)
.ticks(xTicks)
.tickFormat(format);
const axisy = d3.axisLeft(d3scale.y);
x
.attr("transform", "translate(" + 0 + "," + (height) + ")")
.call(axisx);
y
.attr("transform", "translate(" + padding + "," + 0 + ")")
.call(axisy);
return d3scale;
}
function getD3Scale(dataset, padding, width, height) {
const xScale = d3
.scaleLinear()
.domain([
0,
d3.max(dataset.map(item => item[0].sample))
])
.range([padding, width]);
const yScale = d3
.scaleLinear()
.domain([
0,
d3.max(dataset.map(item => item[1].value))
])
.range([height, padding]);
return { x: xScale, y: yScale };
}
function formatD3Data(data) {
var d3Data = [];
for (var i = 0; i < data.length; i++) {
d3Data.push([{ sample: i }, { value: data[i] }]);
}
return d3Data;
}
function drawLine(svg, dataset, scale, color, fillColor) {
const path = svg.append("path");
const line = d3.line()
.x(d => scale.x(d[0].sample))
.y(d => scale.y(d[1].value));
path
.datum(dataset)
.attr("fill", fillColor)
.attr("stroke", color)
.attr("d", line);
}
I was trying to make a Bar Chart using D3 by calling the data from the JSON API, I was able to make a single bar visible but not able to make other bars visible. I hope there is some issue placing of the bars.
Different rectangles are visible in the developer tool, but all are coming up in a same place.
Any help would be much appreciated.
// javascript
const svg = d3.select('svg');
const width = +svg.attr('width');
const height = +svg.attr('height');
// Get the data from the JSON api
d3.json("https://api.covid19india.org/data.json")
.then( data => {
// Store the data in two variales
var stateNames = [];
var confirmedCases = [];
for (let i=1; i <= 10; i++){ //i <= (data.statewise.length) - 1
stateNames.push(data.statewise[i].state);
confirmedCases.push(+(data.statewise[i].confirmed));
}
//console.log(stateNames);
//console.log(confirmedCases);
// Max number of cases
let sortedData = [...confirmedCases];
let sortedCases = sortedData.sort(function(a,b){
return a-b;
})
// Measurement of the SVG Element
const margin = { top: 20, right: 20, bottom: 20, left: 100};
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
// Horizontal Visualization of Bar Graph
// X scale
const xScale = d3.scaleLinear()
.domain([0, sortedCases[sortedCases.length-1]])
.range([0, innerWidth]);
//console.log(xScale.domain());
//console.log(xScale.range());
const yScale = d3.scaleBand()
.domain(stateNames)
.range([0, innerHeight])
.padding(0.2);
//console.log(yScale.domain());
const yAxis = d3.axisLeft(yScale);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
g.append('g').call(d3.axisLeft(yScale));
g.append('g').call(d3.axisBottom(xScale))
.attr('transform', `translate(0,${innerHeight})`);
g.selectAll('rect').data(confirmedCases).enter()
.append('rect')
.attr('width', xScale)
.attr('height', 30)
})
Reference to the codepen link, where I have tried
https://codepen.io/Jagat_Nayan/pen/mdepwgZ
You need to defin y property to relate the states to Y asix, and for that you need to pass both stateName and cases while creating rect.
I created an array inputData which containes both stateName and their respective cases and used it while creating rectangles. Below is your updated working code.
// javascript
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
// Get the data from the JSON api
d3.json("https://api.covid19india.org/data.json").then((data) => {
var inputData = [];
for (let i = 1; i <= 10; i++) {
inputData.push({stateName: data.statewise[i].state, cases: +data.statewise[i].confirmed})
}
// Measurement of the SVG Element
const margin = { top: 20, right: 20, bottom: 20, left: 100 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
//const barWidth = innerWidth;
//const barPadding = 5;
// Horizontal Visualization of Bar Graph
// X scale
const xScale = d3
.scaleLinear()
.domain([0, d3.max(inputData, function(d){ return d.cases; })])
.range([0, innerWidth]);
const yScale = d3
.scaleBand()
.domain(inputData.map(function(d) { return d.stateName; }))
.range([0, innerHeight])
.padding(0.2);
const yAxis = d3.axisLeft(yScale);
const g = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
g.append("g").call(d3.axisLeft(yScale));
g.append("g")
.call(d3.axisBottom(xScale))
.attr("transform", `translate(0,${innerHeight})`);
g.selectAll("rect")
.data(inputData)
.enter()
.append("rect")
.attr("width", function(d) {return xScale(d.cases); })
.attr("y", function(d) {return yScale(d.stateName); })
.attr("height", 30);
});
Working graph on codepen - https://codepen.io/puneet2412/pen/LYpeOzd
I'm attempting to make a stream graph in d3 that is both zoomable and the paths are hoverable.
From reading around I see that as of v4 d3 zoom consumes the event and therefore may be why my mouseover event no longer fires, however no amount of reordering or pointer-events: all I set seems to have an effect.
Is anybody able to help me understand what I have to do to get both my zoom and hover working in the following example? (There's also a codesandbox with the example)
const width = 500;
const height = 500;
let numberOfDataPoints = 5;
const numberOfLayers = 3;
let data = [];
for (let i = 0; i < numberOfDataPoints; i++) {
let point = [];
for (let j = 0; j < numberOfLayers; j++) {
point.push(Math.floor(Math.random() * Math.floor(120)));
}
data.push(point);
}
const x = d3
.scaleLinear()
.domain([0, numberOfDataPoints - 1])
.range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const area = d3
.area()
.x((d, i) => x(i))
.y0(d => y(d[0]))
.y1(d => y(d[1]));
const stack = d3
.stack()
.keys(d3.range(numberOfLayers))
.offset(d3.stackOffsetWiggle)
.order(d3.stackOrderNone);
let layers = stack(data);
y.domain([
d3.min(layers, l => d3.min(l, d => d[0])),
d3.max(layers, l => d3.max(l, d => d[1]))
]);
update();
const zoomContainer = d3
.select('svg')
.append('rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'none')
.style('pointer-events', 'all');
const zoom = d3
.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
zoomContainer.call(zoom);
function update() {
let path = d3
.select('svg')
.selectAll('path')
.data(layers);
path
.enter()
.append('path')
.attr('fill', 'red')
.merge(path)
.attr('d', d => area(d))
.on('mouseover', () => {
d3.select('svg')
.selectAll('path')
.attr('fill', 'red');
d3.select(d3.event.target).attr('fill', 'green');
});
}
function zoomed() {
let transform = d3.event.transform;
transform.y = 0;
let updatedScale = transform.rescaleX(x);
d3.select('svg')
.selectAll('path')
.attr('d', area.x((d, i) => updatedScale(i)));
}
The hypothesis for why this didn't work was correct (the zoom container consumes the event), and the fix is to just remove the zoomContainer and apply the zoom to the svg instead i.e.
const zoom = d3
.zoom()
.on('zoom', zoomed);
d3.select('svg').call(zoom);
I want a ‘mirrored’ bar chart (i.e. one that looks like a sound wave) and have come up with the following using the d3 stack generator and a linear y scale:
import * as d3 from "d3";
const WIDTH = 300;
const HEIGHT = 300;
const LIMIT = 4;
const container = d3.select("svg").append("g");
var data = [];
for (var i = 0; i < 100; i++) {
data.push({
index: i,
value: Math.random() * LIMIT
});
}
var stack = d3
.stack()
.keys(["value"])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetSilhouette);
var series = stack(data);
var xScale = d3
.scaleLinear()
.range([0, WIDTH])
.domain([0, data.length]);
var yScale = d3
.scaleLinear()
.range([HEIGHT, 0])
.domain([0, LIMIT / 2]);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
container
.selectAll(".bar")
.data(series[0])
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => {
return xScale(d.data.index);
})
.attr("y", d => {
return yScale(d[0]) / 2 - HEIGHT / 2;
})
.attr("width", WIDTH / series[0].length)
.attr("height", d => yScale(d[1]));
However, I feel like I’ve hacked the calculations for both the y scale domain and for positioning the blocks.
For the domain I currently use 0 to the data's upper limit / 2.
For my y position I use yScale(d[0]) / 2 - HEIGHT / 2; despite the height being directly based off the scale i.e. d => yScale(d[1]).
Is there a better, more idiomatic way to achieve what I want?
It seems the way the stack function calculates values has changed since D3 v2, and therefore I had to do two things to achieve this in a nicer way.
I switched my y scale domain to be the extents of the data and then translated by -0.5 * HEIGHT
I modified my calculation for the y position and height:
.attr('y', d => yScale(d[1]))
.attr('height', d => yScale(d[0]) - yScale(d[1]));
Is there a way to find out the distance between the tick marks on the x axis? I'm using the ordinal scale with rangeRoundBands with tells me it doesn't have a tick function.
var x= d3.scale.ordinal().rangePoints([_margin.left, cWidth]);
x.domain(['Dec','Jan']);
var testTicks = x.ticks(2);
It generates the axis fine (can't post an image) but I can't figure out how to get the distance
(edit: added x.domain)
var data = [45, 31, 23], // whatever your data is
graphHeight = 400,
// however many ticks you want to set
numberTicksY = 4,
// set y scale
// (hardcoded domain in this example to min and max of data vals > you should use d3.max real life)
y = d3.scale.linear().range(graphHeight, 0]).domain(23, 45),
yAxis = d3.svg.axis().scale(y).orient("left").ticks(numberTicksY),
// eg returns -> [20, 30, 40, 50]
tickArr = y.ticks(numberTicksY),
// use last 2 ticks (cld have used first 2 if wanted) with y scale fn to determine positions
tickDistance = y(tickArr[tickArr.length - 1]) - y(tickArr[tickArr.length - 2]);
(2019) We can expand techjacker solution to cover non-linear scales, instead of calculate only the distance between two ticks, you can have an array with all distances between ticks.
// ticksDistance is constant for a specific x_scale
const getTicksDistance = (scale) => {
const ticks = scale.ticks();
const spaces = []
for(let i=0; i < ticks.length - 1; i++){
spaces.push(scale(ticks[i+1]) - scale(ticks[i]))
}
return spaces;
};
//you have to recalculate when x_scale or ticks change
const ticksSpacingPow = getTicksDistance(x_scale_pow);
ticksSpacingPow is array with all distances
The example below, draws an ellipse on half of the distance between the ticks.
// normal
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0,20)")
.call(x_axis_pow)
.selectAll('.tick')
.append('ellipse')
.attr('fill', 'red')
.attr('rx', '2px')
.attr('ry', '10px')
.attr('cx', (d,i)=> ticksSpacingPow[i]/2)
ps. Using the latest D3v5
const x_scale_pow = d3.scalePow().exponent(2)
.domain([0,20000])
.range([0, 960]);
const x_axis_pow = d3.axisBottom()
.scale(x_scale_pow)
.ticks(10)
// ticksDistance is constant for a specific x_scale
const getTicksDistance = (scale) => {
const ticks = scale.ticks();
const spaces = []
for(let i=0; i < ticks.length - 1; i++){
spaces.push(scale(ticks[i+1]) - scale(ticks[i]))
}
return spaces;
};
//you have to recalculate when x_scale or ticks change
const ticksSpacingPow = getTicksDistance(x_scale_pow);
const svg = d3.select("body").append("svg")
.attr("width", "500px")
.attr("height","350px")
.style("width", "100%")
.style("height", "auto");
// normal
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0,20)")
.call(x_axis_pow)
.selectAll('.tick')
.append('ellipse')
.attr('fill', 'red')
.attr('rx', '2px')
.attr('ry', '10px')
.attr('cx', (d,i)=> ticksSpacingPow[i]/2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>