I am playing with d3.js to draw a curve like the below. The below was drawn in Excel. As I am trying to achive the below result, I am facing an issue where my Y axis data lives in nested array and I am having a trouble to expose that.
Using the code below, I am able to get the x axis but whenever I try to bring my y axis, the data would come up as undefined.
var w = 900;
var h = 180;
var margin = {
'top': 5,
'right': 20,
'bottom': 20,
'left': 50
};
//creating axis
function buildLine(data) {
var xScale = d3.scale.linear()
.domain([100, d3.max(data, function(d) {
return d.spend;
})])
.range([margin.left, w - margin.right], 100);
/*var yScale=d3.scale.linear()
.domain([0,d3.max(data.arr,function(d){return d.y})])
.range([h-padding,10])
.nice();*/ //getting error as it is nested
var xAxisGen = d3.svg.axis().scale(xScale).orient("bottom").ticks(15);
/* var yAxisGen=d3.svg.axis().scale(yScale).orient("left");*/
var svg = d3.select("body").append("svg").attr({
width: w,
height: h
});
/*var yAxis= svg.append("g")
.call(yAxisGen)
.attr("class","axis")
.attr("transorm","translate("+padding+",0)");*/
var xAxis = svg.append("g")
.call(xAxisGen)
.attr("class", "axis")
.attr("transorm", "translate(" + padding + ",0)");
}
d3.json("sample-data.json", function(error, data) {
//check the file loaded properly
if (error) { //is there an error?
console.log(error); //if so, log it to the console
} else { //If not we're golden!
//Now show me the money!
ds = data; //put the data in the global var
}
buildLine(data);
data.forEach(function(jsonData) {
var lineData = d3.range(0, jsonData.spend, 100)
.map(x => [x, jsonData.alpha * (1 - 2.71828 * (-jsonData.beta * x))]);
/* console.log("this is the data:",lineData);*/
//i think line date returns an array with each item in it also an array of 2 items
var arr = [];
for (var i = 0; i < lineData.length; i++) {
arr.push({
x: lineData[i][0],
y: lineData[i][1]
});
}
jsonData.arr = arr;
console.log(jsonData);
});
});
*********The data structure as below ******************
So there is one object and there needs to be a line for each object and therefore each object has nested array which holds x, y and I need to get the Y to build y.axis. My brain feels like a marshmallow right now.
{x: 10000, y: 286.89585120000004}
{x: 10100, y: 288.364809712}
{x: 10200, y: 289.833768224}
{x: 10300, y: 291.30272673600007}
{x: 10400, y: 292.771685248}
Related
Learning Javascript and D3.
Trying to create a graph where it draws each line in a series, one at a time or on a delayed interval.
What I've got so far (relevant code at the end of this post)
https://jsfiddle.net/jimdholland/5n6xrLk0/180/
My data is structures with each series as one row, with 12 columns. When you read it in, the object approximately looks like
mydata = [
{NumDays01: "0", NumDays02: "0", NumDays03: "0", NumDays04: "0", NumDays05: "0",Numdays06: 30}
1: {NumDays01: "0", NumDays02: "0", NumDays03: "0", NumDays04: "0",...
]
I can get it to create line, but it draws all the paths at once. Likewise, I've tried a loop, but that still draws them all at once. Also tried d3.timer and similar results. But I'm having a hard time understanding the timer callback stuff and creating those functions correctly.
I haven't found another example to really study other than a chart from FlowingData which has a lot of bells and whistles out of my league.
https://flowingdata.com/2019/02/01/how-many-kids-we-have-and-when-we-have-them/
Any help or tips would be appreciated.
var svg = d3.select("#chart").select("svg"),
margin = { top: 30, right: 20, bottom: 10, left: 40 },
width = parseInt(d3.select("#chart").style('width'), 10) - margin.left - margin.right;
d3.tsv("https://s3.us-east-2.amazonaws.com/jamesdhollandwebfiles/data/improvementTest.tsv", function(error, data) {
if (error) throw error;
console.log(data);
myData = data;
var t = d3.timer(pathMaker);
}); // #end d3.tsv()
function pathMaker() {
var peeps = myData[rowToRun];
var coords = [];
var lineToRemove = [];
for (var nameIter in dayList) {
coords.push({ x: x(nameIter), y: y(peeps[dayList[nameIter]])})
}
var improvementPath = g.append("path")
.datum(coords)
.attr("id", "path" + peeps.caseid)
.attr("d", lineMaker)
.attr("stroke", "#90c6e4")
.attr("fill", "none")
.attr("stroke-width", 2);
var total_length = improvementPath.node().getTotalLength();
var startPoint = pathStartPoint(improvementPath);
improvementPath = improvementPath
.attr("stroke-dasharray", total_length + " " + total_length)
.attr("stroke-dashoffset", total_length)
.transition() // Call Transition Method
.duration(4000) // Set Duration timing (ms)
.ease(d3.easeLinear) // Set Easing option
.attr("stroke-dashoffset", 0); // Set final value of dash-offset for transition
rowToRun += 1;
if (rowToRun == 5) {rowToRun = 0;}
}
//Get path start point for placing marker
function pathStartPoint(Mypath) {
var d = Mypath.attr("d"),
dsplitted = d.split(/M|L/)[1];
return dsplitted;
}
there are a few problems. I tried to configure your code as little as I could to make it work. If you need further explanations, please let me know
d3.tsv("https://s3.us-east-2.amazonaws.com/jamesdhollandwebfiles/data/improvementTest.tsv", function(error, data) {
if (error) throw error;
console.log(data);
myData = data;
pathMaker()
}); // #end d3.tsv()
function pathMaker() {
var peeps = myData[rowToRun];
var coords_data = [];
var lineToRemove = [];
for (let i = 0; i < myData.length; i++) {
var coords = [];
for (var nameIter in dayList) {
coords.push({ x: x(nameIter), y: y(myData[i][dayList[nameIter]])})
}
coords_data.push(coords)
}
console.log(coords_data)
var improvementPath = g.selectAll("path")
.data(coords_data)
.enter()
.append("path")
.attr("d", lineMaker)
.attr("stroke", "#90c6e4")
.attr("fill", "none")
.attr("stroke-width", 2);
improvementPath = improvementPath.each(function (d,i) {
var total_length = this.getTotalLength();
var startPoint = pathStartPoint(improvementPath);
const path = d3.select(this)
.attr("stroke-dasharray", total_length + " " + total_length)
.attr("stroke-dashoffset", total_length)
.transition() // Call Transition Method
.duration(4000) // Set Duration timing (ms)
.delay(i*4000)
.ease(d3.easeLinear) // Set Easing option
.attr("stroke-dashoffset", 0); // Set final value of dash-offset for transition
})
rowToRun += 1;
if (rowToRun == 5) {rowToRun = 0;}
}
I currently have two variables that I can map to two different colours on two colours scales s1 and s2. s1 gives me the shade of red corresponding to my a value of my variable X (4 different possible colours). s1 gives me the shade of blue corresponding to my a value of my variable Y (4 different possible colours too).
Now what I would like to get is something that allows me to combine these two to get a unique colour for a combination of the variables. So for a pair (X,Y) I get a colour on the scale. So I get a scale of 16 possible colours.
Here is a legend that illustrate the kind of thing I am looking for:
I have been looking at online examples but cannot figure out how to achieve this.
You could combine two threshold scales fairly easily into a new scale function. The core of the function could look like:
d3.scaleBivariate = function() {
function scaleBivariate(value) {
var r = reds(value[0]);
var b = blues(value[1]);
return "rgb("+r+","+((r+b)/2)+","+b+")";
}
var blues = d3.scaleThreshold()
.range([255,205,155,105,55])
.domain([0,1,2,3,4,5]);
var reds = d3.scaleThreshold()
.range([255,205,155,105,55])
.domain([0,1,2,3,4,5]);
return scaleBivariate;
}
This sets the red and blue channels with the help of two d3 threshold scales. The green is simply set as the average between the two, though you could set that to whatever is desirable, say 0 or the minimum of the two other channels. My red/blue ranges are arbitrary and easily changed as well.
The above could be used as:
d3.scaleBivariate = function() {
function scaleBivariate(value) {
var r = reds(value[0]);
var b = blues(value[1]);
return "rgb("+r+","+((r+b)/2)+","+b+")";
}
var blues = d3.scaleThreshold()
.range([255,205,155,105,55])
.domain([0,1,2,3,4,5]);
var reds = d3.scaleThreshold()
.range([255,205,155,105,55])
.domain([0,1,2,3,4,5]);
return scaleBivariate;
}
// Dummy data:
var data = d3.range(16).map(function(d) {
return {x: d%4, y: Math.floor(d/4) }
})
var svg = d3.select("svg");
var size = 30;
var color = d3.scaleBivariate();
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return d.x * size })
.attr("y", function(d) { return d.y * size })
.attr("width",size)
.attr("height",size)
.attr("fill",function(d) {
return color([d.x,d.y]);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<svg></svg>
Of course you might want to add some flexibility by adding methods to modify what datum property sets what colors are associated with what properties, what the thresholds should be, etc. To provide a basic example, the example below has added accessors for setting what property should be mapped to blue and red channels:
d3.scaleBivariate = function() {
function scaleBivariate(value) {
var r = reds(red(value));
var b = blues(blue(value));
return "rgb("+r+","+((r+b)/2)+","+b+")";
}
var blues = d3.scaleThreshold()
.range([255,205,155,105,55])
.domain([0,1,2,3,4,5]);
var reds = d3.scaleThreshold()
.range([255,205,155,105,55])
.domain([0,1,2,3,4,5]);
var red = function(d) { return d[0]; }
var blue = function(d) { return d[1];}
// Accessors:
scaleBivariate.red = function(_) {
return arguments.length ? (red = _, scaleBivariate): red;
}
scaleBivariate.blue = function(_) {
return arguments.length ? (blue = _, scaleBivariate): blue;
}
return scaleBivariate;
}
var data = d3.range(16).map(function(d) {
return {x: d%4, y: Math.floor(d/4) }
})
var svg = d3.select("svg");
var size = 30;
// set up the color scale:
var color = d3.scaleBivariate()
.red(function(d) { return d.x; })
.blue(function(d) { return d.y; });
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return d.x * size })
.attr("y", function(d) { return d.y * size })
.attr("width",size)
.attr("height",size)
.attr("fill",color);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<svg></svg>
This question is a bit tricky. I have some example code working with the following data. It plots it on a line graph.
var dataset = [{
y: 0.1
},
{
y: 0.6
},
{
y: 0.6
},
{
y: 0.7
}
];
D3 uses the following code to put this on the X axis:
var xScale = d3.scaleLinear()
.domain([0, n - 1]) // input
.range([0, width]); // output
However I'm really looking to get dat
var dataset = [{
"1pm": 0.1
},
{
"2pm": 0.6
},
{
"3pm": 0.6
},
{
"4pm": 0.7
}
];
How do I put these values into the graph X axis instead of it just being based on the array numbers? When I try the code as it is above it breaks the graph it says:
Error: <path> attribute d: Expected number, "M0,NaNC17.222222222…".
This is the full code:
http://jsfiddle.net/spadez/e6hv9x8m/17/
Here is your modified fiddle.
First off, you can include an additional value in your dataset objects to represent the x value, I've called this date:
var dataset = [{
y: 0.1,
date: new Date(2012,0,1)
},
{
y: 0.6,
date: new Date(2012,0,2)
}
];
Next, the x scale needs to be changed from linear to time:
var minDate = new Date(2012,0,1);
var maxDate = new Date(2012,0,2);
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range([0, width]);
And finally, you must update the callback here to pull the date field from the dataset object, rather than just it's position in the array:
var line = d3.line()
.x(function(d, i) {
return xScale(d.date);
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.y);
}) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
i made this map using datamaps by #markmarkoh
i love how it turned out but i wish i could get it to rotate when you drag it with the cursor, so you can see all of the continents. like the examples here and here.
here's a snippet of my code:
//basic map config with custom fills, mercator projection
var series = [
["USA",36.2],["GBR",7.4],["CAN",6.2],["DEU",5.7],["FRA", 4.1],["ESP",4.1],["ITA",3.3],["MEX",3.0],["AUS",2.5],["NLD",2.4],
["IND",2.1],["BRA",2.0],["GRC",1.4],["AUT",1.2],["ROU",1.2],["SRB",1.0],["COL",0.8],["POL",0.8],["ZAF",0.7],["SWE",0.7],
["DNK",0.6],["VEN",0.6],["JPN",0.6],["KOR",0.6],["BEL",0.5],["RUS",0.5],["PRT",0.5]
];
var dataset = {};
// We need to colorize every country based on "percent"
// colors should be uniq for every value.
// For this purpose we create palette(using min/max series-value)
var onlyValues = series.map(function(obj){ return obj[1]; });
var minValue = Math.min.apply(null, onlyValues),
maxValue = Math.max.apply(null, onlyValues);
// create color palette function
// color can be whatever you wish
var paletteScale = d3.scale.linear()
.domain([minValue,maxValue])
.range(["rgb(0,0,0)","rgb(219,219,219)"]); // color
// fill dataset in appropriate format
series.forEach(function(item){ //
// item example value ["USA", 36.2]
var iso = item[0],
value = item[1];
dataset[iso] = { percent: value, fillColor: paletteScale(value) };
});
var map = new Datamap({
scope: 'world',
element: document.getElementById('world'),
projection: 'orthographic',
projectionConfig: {
rotation: [90,-30]
},
fills: {defaultFill: 'rgba(30,30,30,0.1)'},
data: dataset,
geographyConfig: {
borderColor: 'rgba(222,222,222,0.2)',
highlightBorderWidth: 1,
// don't change color on mouse hover
highlightFillColor: function(geo) {
return geo['fillColor'] || 'rgba(30,30,30,0.5)';
},
// only change border
highlightBorderColor: 'rgba(222,222,222,0.5)',
// show desired information in tooltip
popupTemplate: function(geo, data) {
// don't show tooltip if country don't present in dataset
if (!data) { return ; }
// tooltip content
return ['',
'<div style="opacity:0.7;" class="hoverinfo">% of visitors in ' + geo.properties.name,
': ' + data.percent,
''].join('');
}
}
});
//draw a legend for this map
map.legend();
map.graticule();
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"></script>
<script src="http://unilogue.github.io/js/map/datamaps.world.min.js"></script>
<div id="world" style="fill-opacity:0.7; height: 600px; width: 500px; margin-top:-100px;"></div>
edit: apparently the done callback lets you use events, i created this zoom/pan function as a test but how can i use this to rotate my map with d3.behavior.drag and euler angles?
var map = new Datamap({
done: function(datamap) {
datamap.svg.call(d3.behavior.zoom().on("zoom", redraw));
function redraw() {
datamap.svg.selectAll("g").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
},
edit 2: this looks like it could work! taken from here.
i tried copying it into a done callback but nothing happened, any ideas?
var dragBehaviour = d3.behavior.drag()
.on('drag', function(){
var dx = d3.event.dx;
var dy = d3.event.dy;
var rotation = projection.rotate();
var radius = projection.scale();
var scale = d3.scale.linear()
.domain([-1 * radius, radius])
.range([-90, 90]);
var degX = scale(dx);
var degY = scale(dy);
rotation[0] += degX;
rotation[1] -= degY;
if (rotation[1] > 90) rotation[1] = 90;
if (rotation[1] < -90) rotation[1] = -90;
if (rotation[0] >= 180) rotation[0] -= 360;
projection.rotate(rotation);
redraw();
})
var livemap;
scope.rotation = [97, -30];
function redraw() {
d3.select("#map-wrapper").html('');
init();
}// redraw
function init() {
livemap = new Datamap({...})
var drag = d3.behavior.drag().on('drag', function() {
var dx = d3.event.dx;
var dy = d3.event.dy;
var rotation = livemap.projection.rotate();
var radius = livemap.projection.scale();
var scale = d3.scale.linear().domain([-1 * radius, radius]).range([-90, 90]);
var degX = scale(dx);
var degY = scale(dy);
rotation[0] += degX;
rotation[1] -= degY;
if (rotation[1] > 90) rotation[1] = 90;
if (rotation[1] < -90) rotation[1] = -90;
if (rotation[0] >= 180) rotation[0] -= 360;
scope.rotation = rotation;
redraw();
})
d3.select("#map-wrapper").select("svg").call(drag);
}// init
I are trying to create a Qlikview extension object to build a D3 Tree chart.
Below is the link for the Tree chart in D3
https://gist.github.com/mbostock/2949981
So for I am able to read the parent and child nodes and also able to plot the nodes(dots) on the Qlikview Object Window.
However the code is throwing an error while generating the links when the d3.svg.diagonal() function is being called in the d3.v2.js/d3.js/d3.v3.min.js
Below is the code snippet where my script throws an error.
var diagonal = d3.svg.diagonal().projection(function(d) { return [d.y, d.x]; });
// Create the link lines.
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
Error: "Unable to get value of the property 'y': the object is null or undefined
Below is the diagonal function from the d3.v2.js where the error is thrown at line 3
d3.svg.diagonal = function() {
function diagonal(d, i) {
var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
x: p0.x,
y: m
}, {
x: p3.x,
y: m
}, p3 ];
p = p.map(projection);
return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
}
var source = d3_svg_chordSource, target = d3_svg_chordTarget, projection = d3_svg_diagonalProjection;
diagonal.source = function(x) {
if (!arguments.length) return source;
source = d3_functor(x);
return diagonal;
};
diagonal.target = function(x) {
if (!arguments.length) return target;
target = d3_functor(x);
return diagonal;
};
diagonal.projection = function(x) {
if (!arguments.length) return projection;
projection = x;
return diagonal;
};
return diagonal;
};
Kindly help
Here is the data I have used. It is a Simple .csv file with no x, y values:
source,target
flare,animate
flare,analytics
analytics,cluster
analytics,graph
animate,sequence
animate,transition
animate,interpolate
cluster,agglomerativeCluster
cluster,hierarchicalCluster
interpolate,arrayInterpolator
interpolate,colorInterpolator
interpolate,dateInterpolator