I have the following array
var data = [
[{"time": 1, "value": 2.1}, {"time": 2, "value": 1.1}],{"time": 3, "value": 3.1}],
[{"time": 1, "value": 5.3}, {"time": 2, "value": 0.1}, {"time": 3, "value": 6.1}]
];
and I need to find the maximum time and value out of the entire array. the code that doesn't quite work is
var x = d3.scale.linear()
.domain([1, d3.max(data, function(d,i){ return d[i].time;})])
.range([0, width]);
for some reason I get a maximum time of 2, and not 3. even if I use a larger dataset with more point I still don't get the actual maximum value.
Any help is appreciated.
Your data is an array or arrays. If you want the "max of the maxes", you'll need to account for the nesting. One way to do it:
.domain([1, d3.max(data, function(arrayOfDs, i) {
return d3.max(arrayOfDs, function(d, i) { return d.time; });
})])
Related
I am working with D3.js and react-hooks to create charts, So I tried creating one Line chart by searching around and got one.
But the one I am working with is using Sample data, here in my case I have JSON data.
I have made the charts responsive as well using resize-observer-polyfill this library.
Now I am struggling to implement it with JSON data, to renders it with dynamic data.
What I did
const svgRef = useRef();
const wrapperRef = useRef();
const dimensions = useResizeObserver(wrapperRef); // for responsive
// will be called initially and on every data change
useEffect(() => {
const svg = select(svgRef.current);
const { width, height } =
dimensions || wrapperRef.current.getBoundingClientRect();
// scales + line generator
const xScale = scaleLinear()
.domain([0, data.length - 1]) // here I need to pass the data
.range([0, width]);
const yScale = scaleLinear()
.domain([0, max(data)])
.range([height, 0]);
const lineGenerator = line()
.x((d, index) => xScale(index))
.y((d) => yScale(d))
.curve(curveCardinal);
// render the line
svg
.selectAll('.myLine')
.data([data])
.join('path')
.attr('class', 'myLine')
.attr('stroke', 'black')
.attr('fill', 'none')
.attr('d', lineGenerator);
svg
.selectAll('.myDot')
.data(data)
.join('circle')
.attr('class', 'myDot')
.attr('stroke', 'black')
.attr('r', (value, index) => 4)
.attr('fill', (value, index) => 'red')
.attr('cx', (value, index) => xScale(index))
.attr('cy', yScale);
// axes
const xAxis = axisBottom(xScale);
svg
.select('.x-axis')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
const yAxis = axisLeft(yScale);
svg.select('.y-axis').call(yAxis);
}, [data, dimensions]);
<React.Fragment>
<div ref={wrapperRef} style={{ marginBottom: '2rem' }}>
<svg ref={svgRef}>
<g className="x-axis" />
<g className="y-axis" />
</svg>
</div>
</React.Fragment>
Here I am not able to pass the data, my data is below
[
{ anno: 2014, consumo: 300, color: "#ff99e6" },
{ anno: 2015, consumo: 290, color: "blue" },
{ anno: 2016, consumo: 295, color: "green" },
{ anno: 2017, consumo: 287, color: "yellow" },
{ anno: 2018, consumo: 282, color: "red" },
{ anno: 2019, consumo: 195, color: "white" }
]
Here in my data I have color for each data, which I want to show in each dot generated.
working code sandbox of line chart
Similarly I tried doing bar chart and it is working fine
I did some dynamic rendering to labels, when we resize the window the labels gets adjusted automatically.
Here is the full working bar chart what I am trying to implement to line chart
I have also commented the lines where I am doing what.
Edit / Update
I ahve been following #MGO 's answer and it helped me Alot, but still I am facing issue to align labels and filling the color to dots.
actually it is obvious that it will overlap because of the text size, but just to overcome that In bar chart I have used below code
const tickWidth = 40;
const width = xScaleLabels.range()[1];
const tickN = Math.floor(width / tickWidth);
const keepEveryNth = Math.ceil(xScaleLabels.domain().length / tickN);
const xScaleLabelDomain = xScaleLabels
.domain()
.filter((_, i) => i % keepEveryNth === 0);
xScaleLabels.domain(xScaleLabelDomain);
what it is doing is when the device size is small it will filter some labels and will not show labels.
And also I am using below code to give color
.attr("fill", ({ color }) => color)
But is is not taking any color, but it is taking by default black color.
I have data to show as label as July.9.2021 11:18:28 but I only want to show time so what I am doing in my bar chart code is below
const xScaleLabels = scaleBand()
.domain(
data.map(
({ sensorValueAddedTime }) => sensorValueAddedTime.split(' ')[2] // this I am using to show only time
)
)
.range([0, dimensions.width])
.padding(padding);
Same I am trying to do with Line chart, In a simple way, I do not want to change this to any time and all.
This is the second data, so basically the Answer I got is only working for 1st data not for second, I want that to be dynamic if data comes in any of these format I want to show.
sensorValueAddedTime I want to show on x-axis
sensorValue On y-axis
I have already added my bar chart full working code, Same I want to do with line chart.
[
{
sensorValue: 32,
sensorValueAddedTime: "July.9.2021 10:56:22",
color_code: null,
condition: null,
__typename: "sensorData"
},
{
sensorValue: 32,
sensorValueAddedTime: "July.9.2021 10:56:23",
color_code: null,
condition: null,
__typename: "sensorData"
},
{
sensorValue: 35,
sensorValueAddedTime: "July.9.2021 11:17:51",
color_code: null,
condition: null,
__typename: "sensorData"
},
{
sensorValue: 35,
sensorValueAddedTime: "July.9.2021 11:17:52",
color_code: null,
condition: null,
__typename: "sensorData"
},
{
sensorValue: 36,
sensorValueAddedTime: "July.9.2021 11:18:08",
color_code: null,
condition: null,
__typename: "sensorData"
},
{
sensorValue: 36,
sensorValueAddedTime: "July.9.2021 11:18:09",
color_code: null,
condition: null,
__typename: "sensorData"
},
{
sensorValue: 38,
sensorValueAddedTime: "July.9.2021 11:18:27",
condition: null,
color_code: null,
__typename: "sensorData"
},
{
sensorValue: 38,
sensorValueAddedTime: "July.9.2021 11:18:28",
condition: null,
color_code: null,
__typename: "sensorData"
}
]
In the original example from Muri's D3 with React Hooks video series data is a flat single-dimensional array: [10, 25, 30, 40, 25, 60].
In the new array you're providing (data1), we're asking D3 to render a chart based on an array of objects. So when we pass data1 in as a prop to our chart function, we need to do a little more work to access the values inside of our array of objects.
We'll need to scale the data values into the appropriate ranges. The original example looked up the values by their index in the array. We're going to need to access the values in the objects contained in the data1 array based on those objects' keys. Like this, to create our scales:
const yScale = scaleLinear()
.domain([0, max(data, (d) => d.sensorValue)])
.range([height, 0]);
// scale the values of sensorValueAddedTime into the range for our x axis values
// since it's a date you'll need to convert the strings to Date objects:
const xScale = scaleTime()
.domain([
new Date(min(data, (d) => d.sensorValueAddedTime)),
new Date(max(data, (d) => d.sensorValueAddedTime))
])
.range([0, width]);
And like this, in our lineGenerator function:
const lineGenerator = line()
.x((d) => xScale(new Date(d.sensorValueAddedTime))
.y((d) => yScale(d.sensorValue));
You'll notice above that if we're going to use scaleTime(), we'll have to convert sensorValueAddedTime into a value that D3 can make sense of -- right now there's an extra space in the string in your object, but if you strip that space you can convert to a Date object and use d3.scaleTime().
There's an updated, working Sandbox that's rendering this data to a line chart with additional comments here.
Update: OP was asking to plot a different set of data and asked that the points "appear in that order".
There's a different working Sandbox rendering this different set of data to a line chart correctly here.
It seems like what you're asking for -- the "line graph version of the bar chart" may not produce what you're after. Just talking it through:
If you use the index position of each object on your xScale, like this:
const xScale = scaleLinear()
.domain([0, data.length - 1])
.range([0, width]);
that will produce a pretty boring chart -- because you're saying "put a dot at every whole number representing the index of the array". In other words, a dot at 1, at 2, at 3, and so on.
For this chart, we should choose meaningful values in our data -- like data1.anno -- and use those to communicate.
For example:
Scaling the values into the range according to the index positions of the array and plotting them according to their index position, like this:
const xScale = scaleLinear()
.domain([0, data.length - 1])
.range([0, width]);
....chart code....
.attr("cx", (value, index) => xScale(index))
Produces this plot:
Solution
Scaling the values of the data into the range and plotting the points according to their values. We can use d3's extent method, like this:
const xScale = scaleLinear()
.domain(extent(data, (d) => d.anno))
.range([0, width]);
const yScale = scaleLinear()
.domain([0, max(data, (d) => d.consumo)])
.range([height, 0]);
....chart code...
.attr("cx", (value) => xScale(value.anno))
.attr("cy", (value) => yScale(value.consumo));
Produces this plot:
data1.anno is clearly a year. The option to parse the years as date obects is a good one. var parse = d3.timeParse("%Y"), then use this to set the domain of your time scale: .domain([parse(minDate), parse(maxDate)]), as shown here.
Otherwise, if you'd like to preserve that value on the x-axis and not treat it as a Date object, you can use Javascript's toFixed() method to remove the decimal places, remove the comma delimiter with d3.format and change the number of ticks that appear with axis.ticks().
That produces this chart:
To generate a line with these points, use the values in the data, not the indexes.
const lineGenerator = line()
// .x((d, index) => xScale(index)) // old
// .y((d) => yScale(d)) // old
.x((d) => xScale(d.anno)) // new
.y((d) => yScale(d.consumo)) // new
.curve(curveCardinal);
That produces this chart:
Finally, if your goal is to assign the color value in each object to a point, access those values and assign their values to the fill, like this:
.attr("fill", (value) => value.color)
That produces this chart:
Here's the working sandbox.
Hope this helps! ✌️
I am getting a NaN error on the r value in a d3 scatterplot.
Console error: Error: Invalid value for attribute r="NaN"
From this section of the code:
g.selectAll(".response")
.attr("r", function(d){
return responseScale(d.responses);
})
.attr("cx", function(d){
return x(d.age);
})
.attr("cy", function(d){
return y(d.value);
})
Here is how the scale is set up:
var responseScale = d3.scale.linear()
.domain(d3.extent(data, function(d){
return d.responses;
}))
.range(2, 15);
Here is a sample of the data:
var data = [
{glazed: 3.14, jelly: 4.43, powdered: 2.43, sprinkles: 3.86, age: 18, responses: 7},
{glazed: 3.00, jelly: 3.67, powdered: 2.67, sprinkles: 4.00, age: 19, responses: 3},
{glazed: 2.00, jelly: 4.00, powdered: 2.33, sprinkles: 4.33, age: 20, responses: 3},
I have tried putting a plus sign in front of d.responses and using parseFloat().
The code is an example used in this course, Learning to Visualize Data with D3.js (chapter on Creating a Scatterplot)
Any suggestions would be greatly appreciated!
In your code:
var responseScale = d3.scale.linear()
.domain(d3.extent(data, function(d){
return d.responses;
}))
.range(2, 15);
The parameter for the range() function should be an array of values, like this: .range([2,15]);
corrected scale:
var responseScale = d3.scale.linear()
.domain(d3.extent(data, function(d){
return d.responses;
}))
.range([2, 15])
;
More info on scales can be found here. If you are still in trouble, let me know!
I'm new to d3 and haven't much web frontend development experience. For a web application I have I'm trying to draw a force directed graph. I've been trying the last few hours to get it to work. I've been looking at lots of different code example and what I'm doing looks very similar. I eventually got nodes to draw but the links between the nodes don't show up and I was trying different things and nothing seems to work. I don't know why my code wouldn't draw the edges.
From printing the nodes and links to the console I saw that the nodes got extra attributes like the d3 docs had mentioned but the links never seem to get these attributes. Below is my javascript file and the JSON file. I reduced the JSON file to only 3 entries to try and make it easier to solve the problem.
var height = 1080;
var width = 1920;
var color = d3.scale.category20();
var force = d3.layout.force()
.linkDistance(-120)
.linkStrength(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("/static/javascript/language_data.json", function(data){
force
.nodes(data.languages)
.links(data.language_pairs)
.start();
var link = svg.selectAll(".link")
.data(data.language_pairs)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(data.languages)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.language; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
Here is the JSON file:
From looking at few examples my understanding is that the source and target are index positions from the list of nodes.
{
"languages":[
{"language": "TypeScript", "group": 1},
{"language": "Java", "group": 2},
{"language": "VHDL", "group": 3}
],
"language_pairs":[
{"source": "0", "target": "1", "value": 5},
{"source": "1", "target": "2", "value": 5},
{"source": "2", "target": "0", "value": 5}
]
}
Sorry if I left out anything! Thanks for any help!
Two issues:
1.) Your "language_pairs" source/target indexes are strings and not numbers. Get rid of the quotes:
"language_pairs":[
{"source": 0, "target": 1, "value": 5},
{"source": 1, "target": 2, "value": 5},
{"source": 2, "target": 0, "value": 5}
]
2.) Your linkDistance and linkStrength parameters don't make sense:
var force = d3.layout.force()
.linkDistance(-120) // negative distance?
.linkStrength(30) // according to the docs, this must be between 0 and 1?
.size([width, height]);
Here's an example that fixes these problems.
I'm very new to doing anything with d3 and jSon. Here is a pice of data I'm trying to get out from json and I would just like to know if I'm even on the right path.
Basically each status group would have more servers inside than just one like at the moment and the idea would be to get rectangle graph for one server and list these nicely next to each other.
I've been reading a lot of tutorials and trying to browse for similiar kind of issues other people might've had, but so far had really no luck...
jSon data I'm trying to pull out
[
{
"status": "ok",
"servers":
[
{
"id": "VR01",
"servername": "Server_1",
"cpu": 45, "mem": 25,
"diskIO": 0, "bandwith": 200
}
]
},
{
"status": "attention",
"servers":
[
{
"id": "VR10",
"servername": "Server_10",
"cpu": 55, "mem": 35,
"diskIO": 1, "bandwith": 2000
}
]
},
{
"status": "warning",
"servers":
[
{
"id": "VR02",
"servername": "Server_02",
"cpu": 98, "mem": 85,
"diskIO": 1,
"bandwith": 2000
}
]
},
{
"status": "dead",
"servers":
[
{
"id": "VR20",
"servername": "Server_20",
"cpu": 0, "mem": 0,
"diskIO": 0,
"bandwith": 0
}
]
}
]
the D3 bit
<script>
var width = ("width", 1000);
var height = ("height", 800);
d3.json("mydata.json", function(data) {
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var status = function sortData(data){
for (i = 0; i < d.length; i++) {
if(d.status ==="ok")
canvas.selectAll("rect")
.data(d.server)
.enter()
.append("rect")
.attr("x", 25)
.attr("y", function(d, i){return 25 * i;})
.attr("fill", "purple")
}
}
})
</script>
Really appreciate any suggestions you might have!
I think that it would be better to use nested selections to create your dashboard.
// Create one group for each server group
var serverGroup = svg.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', function(d, i) { return 'translate(0, ' + 50 * i + ')');
// Create the inner elements for each group
var servers = serverGroup.selectAll('rect')
.data(function(d) { return d.servers; })
.enter()
.append('rect')
// ... more settings here ...
This will create three groups, one for each group of servers and translate each one vertically. Each group contains the group data, so we can use the group data to create elements inside each group. Also, you can add a title, background color and other settings for each group using this structure. This article contains the concepts that you need to work on your problem: How Selections Work. Regards,
tl;dr
Can I create elements using data that looks like:
[{"id": 1, ...}, {"id: 2, ...}, ..]
and update attributes of the elements using data that looks like:
[2, 4, 7]
(where the elements of the array are a subset of the ids of the initial data set).
Long version
I have data that looks like this:
[
{
"id": 1,
"latitude": 38.314552,
"longitude": -88.9025347755102,
"name": "JEFFERSON COUNTY JAIL ",
"official_name": "Jefferson County Justice Center",
"url": "https://www.ice.gov/detention-facilities/facilities/jeffeil.htm"
},
{
"id": 2,
"latitude": 41.875702,
"longitude": -87.63072,
"name": "CHICAGO HOLD ROOM ",
"official_name": "Chicago Hold Room",
"url": ""
},
{
"id": 3,
"latitude": 43.407029,
"longitude": -88.704349,
"name": "DODGE COUNTY JAIL, JUNEAU ",
"official_name": "Dodge Detention Facility",
"url": "https://www.ice.gov/detention-facilities/facilities/dodgewi.htm"
},
...
]
I put it on an SVG map like this:
var projection = d3.geo.albersUsa()
.scale(scale)
.translate([width / 2, height / 2]);
var facilityCircles = svg.append("svg:g")
.attr("id", "facilities");
d3.json("data/facilities.json", function(facilities) {
var positions = [];
var positionsByFacilityId = {};
var pointSize = 5;
facilities.forEach(function(facility) {
var loc = [facility.longitude, facility.latitude];
var pos = projection(loc);
positions.push(pos);
positionsByFacilityId[facility.id] = pos;
});
facilityCircles.selectAll("circle")
.data(facilities, function(d) { return d.id; })
.enter().append("svg:circle")
.attr("data-facility-id", function(d, i) { return d.id; })
.attr("cx", function(d, i) { return positionsByFacilityId[d.id][0]; })
.attr("cy", function(d, i) { return positionsByFacilityId[d.id][1]; })
.attr("r", function(d, i) { return pointSize; });
}
However, later on, I want to update attributes of the circles based on another data object, which would just be an array of ids representing a subset of the id properties from the initial data, e.g. [2, 4, 5]
Can I do something like this to update the attributes of only selected elements mapped to data objects with the given ids?
facilitiesSubset = [2, 4, 5];
facilityCircles.selectAll("circle")
.data(facilitiesSubset)
.attr("fill", "green");
or, should I just extract the ids from the initial data and use those in the call to data() that is used to create the elements?
To make this a little easier, I'd suggest changing the naming convention a little bit:
var facilityCircleContainer = svg.append("svg:g")
.attr("id", "facilities");
Since the svg:g object isn't actually the circles, it is just holding them. So then:
var facilityCircles = facilityCircleContainer.selectAll("circle")
.data(facilities, function(d) { return d.id; })
.enter().append("svg:circle")
.attr("data-facility-id", function(d, i) { return d.id; })
.ect(...)
facilityCircles now refers to the circles with their attached data now. Updating the fill based on the array is pretty simple:
facilityCircles.attr("fill", function(d){
facilitiesSubset.indexOf(d.id) != -1 ? "green" : "red"});