I'm trying to figure out if I can work with my current data structure to create a scatterplot in d3. What I have is a three-dimensional array, thetas, with dimensions i, j, and k. Dimension j expands with user input. Dimensions i and k are fixed at i=2 and k=3. For a given i, I want to plot the first two entries of k as the x and y values for a scatterplot for all j. I can't figure out how to do this. Currently, to plot i = 1, I have
xx = d3.scale.linear()
.domain([0,1])
.range([margins.yylMarginFactor * width, (1 - margins.yyrMarginFactor) * width]);
yy = d3.scale.linear()
.domain([0,1])
.range([(1 - margins.xxbMarginFactor) * height, margins.xxtMarginFactor * height]);
var tmp = chart.selectAll(".vis3points").data(thetas[0][0], function (d, i) { return i; } );
tmp
.enter().insert("svg:circle")
.attr("class", "vis3points")
.attr("cx", function (d, i) { return xx(d); })
.attr("cy", yy(d))
.attr("r", 3);
tmp.transition()
.duration(10)
.attr("cx", function (d, i) { return xx(d); })
.attr("cy", yy(d))
tmp.exit()
.transition().duration(10).attr("r",0).remove();
Is this the right direction? What structure should I be using?
Related
Using D3, I want to take the data visualization type of a classical heatmap...
.. onto a compartmentalized version of several heatmap groups drawing data from a single data source.
Technically this should be one heatmap element drawing its data from a single source - separation and thus clustering/grouping is supposed to happen through sorting the data in the *.csv file (group one, group two, group three..) and the D3 *.JS file handling the styling.
While generating a single map:
// Build X scales and axis:
const x = d3.scaleBand()
.range([0, width])
.domain(myGroups)
.padding(0.00);
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
// Build Y scales and axis:
const y = d3.scaleBand()
.range([height, 0])
.domain(myVars)
.padding(0.00);
svg.append('g')
.call(d3.axisLeft(y));
assigning a color:
// Assign color scale
const myColor = d3.scaleLinear()
.range(['red', '#750606'])
.domain([1, 100]);
and fetching (sample) data:
// Read the data
d3.csv('https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/heatmap_data.csv', (data) => {
data.sort(function(a, b) {
return myVars.indexOf(b.variable) - myVars.indexOf(a.variable) || myGroups.indexOf(a.group) - myGroups.indexOf(b.group)
});
Has been working like a charm:
CodePen
I'm struggling to expand this basic structure onto the generation of multiple groups as described above. Expanding the color scheme, trying to build several additional X and Y axis that cover different ranges result in a complete break of the D3 element rendering the map unable to be displayed at all.
Can someone point me in the right direction on how to generate multiple heatmap groups without breaking the heatmap?
I was able to solve the compartmentalization using a row and column based procedure to construct the compartments:
// Dimensions
const numCategoryCols = 4;
const numCategoryRows = Math.ceil(grouped.length / numCategoryCols);
const numEntryCols = 3;
const numEntryRows = Math.ceil(grouped[0].values.length / numEntryCols);
const gridSize = 20;
const width = gridSize * numCategoryCols * numEntryCols;
const height = gridSize * numCategoryRows * numEntryRows;
const tooltipArrowSize = 8;
// Containers
const container = d3
.select("#" + containerId)
.classed("heatmap-grid", true)
.style("position", "relative");
const svg = container
.append("svg")
.style("display", "block")
.style("width", "100%")
.attr("viewBox", [0, 0, width, height])
.style("opacity", 0);
svg.transition()
.duration(3000)
.delay((d,i) => i*200)
.style("opacity", 1)
// Heatmap
const gCategory = svg
.selectAll(".category-g")
.data(grouped, (d) => d.key)
.join("g")
.attr("class", "category-g")
.attr("fill", (d) => color(d.key))
.attr("transform", (_, i) => {
const y = Math.floor(i / numCategoryCols);
const x = i % numCategoryCols;
return `translate(${gridSize * numEntryCols * x},${
gridSize * numEntryRows * y
})`;
});
const gEntry = gCategory
.selectAll(".entry-g")
.data((d) => d.values)
.join("g")
.attr("class", "entry-g")
.attr("transform", (_, i) => {
const y = Math.floor(i / numEntryCols);
const x = i % numEntryCols;
return `translate(${gridSize * x},${gridSize * y})`;
});
const entry = gEntry
.append("rect")
.attr("width", gridSize)
.attr("height", gridSize)
.attr("fill-opacity", (d) => d.Severity / 100)
.on("mouseenter", showTooltip)
.on("mouseleave", hideTooltip);
I'm using d3's stack layout for the first time to produce a stream graph.
This code:
var w = 600,
h = 350;
var x = d3.scale.linear().range([0, w])
y = d3.scale.linear().range([h, 0])
//...
d3.csv("filePath", function(error, input)){
var data = parseData(input);
setDomains(data); //sets x and y domain values. These are return expected values
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h) //.attr("height", h*3) is used to produce second result
.append("g")
//.attr("transform", "translate(0, 250)"); is used to produce second result
var stack = d3.layout.stack().offset("silhouette"),
layers = stack(data);
var area = d3.svg.area()
.interpolate("basis")
.x(function (d, i) {return x(d.x);})
.y0(function (d) {return y(d.y0);})
.y1(function (d) {return y(d.y0 + d.y);});
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function (d) {return area(d);})
.style("fill", function (d, i) {return color(d[0].type);});
is giving me this output:
By increasing the height of the svg and translating the parent g down by 240px (see comments in code), I get my expected result:
The scale of those images is a bit off - in reality, I'm getting the same graphic, but it's placed ~250 pixels higher than expected.
I'm trying to understand why might that be?
I can post setDomains() if needed, but my x() and y() functions are returning expected outputs for given inputs in debugging - i.e. a call to y(maxDataVal) returns 0, as expected. I'm thinking this is related to something I'm not understanding about stack(), which hopefully is evident in the above.
I am attempting to plot a simple dataset consisting of an array of javascript objects. Here is the array in JSON format.
[{"key":"FITC","count":24},{"key":"PERCP","count":16},{"key":"PERCP-CY5.5","count":16},{"key":"APC-H7","count":1},{"key":"APC","count":23},{"key":"APC-CY7","count":15},{"key":"ALEXA700","count":4},{"key":"E660","count":1},{"key":"ALEXA647","count":17},{"key":"PE-CY5","count":4},{"key":"PE","count":38},{"key":"PE-CY7","count":18}]
Each object simply contains a String: "key", and a Integer: "count".
Now, I am plotting these in D3 as follows.
function key(d) {
return d.key;
}
function count(d) {
return parseInt(d.count);
}
var w = 1000,
h = 300,
//x = d3.scale.ordinal()
//.domain([count(lookup)]).rangePoints([0,w],1);
//y = d3.scale.ordinal()
//.domain(count(lookup)).rangePoints([0,h],2);
var svg = d3.select(".chart").append("svg")
.attr("width", w)
.attr("height", h);
var abs = svg.selectAll(".little")
.data(lookup)
.enter().append("circle")
.attr("cx", function(d,i){return ((i + 0.5)/lookup.length) * w;})
.attr("cy", h/2).attr("r", function(d){ return d.count * 1.5})
Here is what this looks like thus far.
What I am concerned about is how I am mapping my "cx" coordinates. Shouldn't the x() scaling function take care of this automatically, as opposed to scaling as I currently handle it? I've also tried .attr("cx", function(d,i){return x(i)}).
What I eventually want to do is label these circles with their appropriate "keys". Any help would be much appreciated.
Update:
I should mention that the following worked fine when I was dealing with an array of only the counts, as opposed to an array of objects:
x = d3.scale.ordinal().domain(nums).rangePoints([0, w], 1),
y = d3.scale.ordinal().domain(nums).rangePoints([0, h], 2);
Your code is doing what you want...I just added the text part. Here is the FIDDLE.
var txt = svg.selectAll(".txt")
.data(lookup)
.enter().append("text")
.attr("x", function (d, i) {
return ((i + 0.5) / lookup.length) * w;
})
.attr("y", h / 2)
.text(function(d) {return d.key;});
I commented out the scales, they were not being used...as already noted by you.
I have an array of generated values to plot a line function. However, when I call the array, the function only returns single values of the array, rather than the column, and hence draws a straight line (http://tributary.io/inlet/8822590). What is the correct syntax here? Thanks in advance.
// Create data
var v1 = 4.137,
t = 10,
x = [],
y = [];
for (i = 0.1; i < 190; i += 0.1) {
x.push(i);
y.push((Math.pow((v1 / i), 1 / t) - 1) * 100);
}
var data = [x, y];
// Scale data
var xscale = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d[0];
}))
var yscale = d3.scale.linear()
.domain(d3.extent(data, function (d) {
return d[1];
}))
var line = d3.svg.line()
.x(function (d) {
return xscale(d[0])
})
.y(function (d) {
return yscale(d[1])
});
var svg = d3.select("svg");
var path = svg.append("path")
.data([data])
.attr("d", line) //this calls the line function with this element's data
.style("fill", "none")
.style("stroke", "#000000")
.attr("transform", "translate(" + [96, 94] + ")")
D3 expects the data for a line to be an array of array where each element determines one line and each element within the inner array the point of the line. You've passed in a single array (for one line) with two elements, so you get two points.
To plot all the points, push the coordinates as separate elements:
for (i = 0.1; i < 190; i += 0.1) {
data.push([i, (Math.pow((v1/i),1/t)-1)*102]);
}
The rest of your code can remain basically unchanged. Complete example here.
I'm sure the solution is straight forward, but I'm trying to find out how to limit the range of radius when I plot circles onto a geomap. I have values that range in size significantly, and the larger values end up covering a significant amount of the map.
d3.csv("getdata.php", function(parsedRows) {
data = parsedRows
for (i = 0; i < data.length; i++) {
var mapCoords = this.xy([data[i].long, data[i].lat])
data[i].lat = mapCoords[0]
data[i].long = mapCoords[1]
}
vis.selectAll("circle")
.data(data)
.enter().append("svg:circle")
.attr("cx", function(d) { return d.lat })
.attr("cy", function(d) { return d.long })
.attr("stroke-width", "none")
.attr("fill", function() { return "rgb(255,148,0)" })
.attr("fill-opacity", .4)
.attr("r", function(d) { return Math.sqrt(d.count)})
})
This is what I have right now.
You'll probably want to use d3 scales by setting the domain (min/max input values) and the range (min/max output allowed values). To make this easy, don't hesitate to use d3.min and d3.max to setup the domain's values.
d3.scale will return a function that you can use when assigning the r attribute value. For example:
var scale = d3.scale.linear.domain([ inputMin, inputMax ]).range([ outputMin, outputMax ]);
vis.selectAll("circle")
// etc...
.attr( 'r', function(d) { return scale(d.count) });