I'm getting weird clipping behavior when using d3.js and conrec.js to plot contours:
This is a contour plot that is correctly depicted
This is a plot with clipping. The paths are filling incorrectly
Another example of a plot with unwanted behavior
Here is my code and approach. Data is a 31x31 value array. I make the contour and then data-bind the contour's contourList. I'm guessing something weird with the pathing is happening there!
var cliff = -1000;
data.push(d3.range(data[0].length).map(function() {
return cliff;
}));
data.unshift(d3.range(data[0].length).map(function() {
return cliff;
}));
data.forEach(function(d) {
d.push(cliff);
d.unshift(cliff);
});
var c = new Conrec,
xs = d3.range(0, data.length),
ys = d3.range(0, data[0].length),
zs = [0],
width = 300,
height = 300,
x = d3.scale.linear().range([0, width]).domain([0,
data.length
]),
y = d3.scale.linear().range([height, 0]).domain([0,
data[0].length
]);
c.contour(data, 0, xs.length - 1, 0, ys.length - 1, xs,
ys, zs.length, zs);
contourDict[key] = c;
d3.select("svg").remove();
var test = d3.select("#contour").append("svg")
.attr("width", width)
.attr("height", height)
.selectAll("path")
.data(c.contourList())
.enter().append("path")
.style("stroke", "black")
.attr("d", d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
}));
Thank you for your time!
Related
I have found the following chart which uses V3 of d3:
http://mbostock.github.io/d3/talk/20111018/area-gradient.html
I have tried to port that sample to V5, but I got stuck with scaleTime(). The chart is not displayed correctly.
I have difficulties to debug var x as it is a function ... however if I look at the element svg:clipPath I see this (note the strange values):
<clipPath id="clip"><rect x="-8119106.125" y="0" width="0.000008575618267059326" height="361"></rect></clipPath>
These are the most relevant parts of my code:
var w=1280, h=800;
var svg = d3.select("#zoomable-area-chart").append("svg:svg");
(....)
// Scales
var x = d3.scaleTime().range([0, w]),
y = d3.scaleLinear().range([h, 0]),
svg.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x", x(0))
.attr("y", y(1))
.attr("width", x(1) - x(0))
.attr("height", y(0) - y(1));
d3.csv("http://localhost/data.csv").then((data) => {
// Parse dates and numbers.
data.forEach(function(d) {
d.date = parse(d.date);
d.value = +d.value;
});
// Compute the maximum price.
x.domain([new Date(1999, 0, 1), new Date(2003, 0, 0)]);
y.domain([0, d3.max(data, function(d) {
return d.value;
})]);
draw();
});
Here you can find a playground for testing:
https://plnkr.co/edit/qWlLIg2avCe2Kt88r0T5?p=preview
the default domain of scaleTime is
Constructs a new time scale with the domain [2000-01-01, 2000-01-02], the unit range [0, 1]
Why use x(v) and y(v) to get there ranges when you set them as constants and you can get the range and use [0] and [1].
Make your zoom-rect fill none, it hides the graph
I am trying to create a radar chart similar to the link here (
http://www.larsko.org/v/euc/).
I was able to create axes (my work so far), but I am having a problem to draw lines in it.
For instance, if I have a list of values something like below, how can I draw a line in the radar chart?
var tempData = [56784, 5.898, 3417, 0, 0, 0]
Edit: I have included code. I am having a problem finding XY coordinates and I think XY value has to be derived from "scales".
var width = 1000,
height = 960,
r = (960 / 2) - 160;
var svg = d3.select("#radar")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + ", " + height / 2 + ")");
d3.csv("data/results.csv", function(data) {
var headerNames = d3.keys(data[0]);
headerNames.splice(0, 1); //Remove 'scenario'
var minList = $.map(headerNames, function(h) {
return d3.min($.map(data, function(d) {
return d[h];
}));
}),
maxList = $.map(headerNames, function(h) {
return d3.max($.map(data, function(d) {
return d[h];
}));
}),
scales = $.map(headerNames, function(h, i) {
return d3.scale.linear()
.domain([minList[i], maxList[i]])
.range([50, r]);
}),
axes = $.map(headerNames, function(h, i) {
return d3.svg.axis()
.scale(scales[i])
.tickSize(4);
});
function angle(i) {
return i * (2 * Math.PI / headerNames.length) + Math.PI / headerNames.length;
}
var line = d3.svg.line()
.interpolate("cardinal-closed")
/* computing X and Y: I am having a problem here
.x(function(d){ return scales(d); })
.y(function(d){ return scales(d); }); */
$.each(axes, function(i, a) {
svg.append("g")
.attr("transform", "rotate(" + Math.round(angle(i) * (180 / Math.PI)) + ")")
.call(a)
.selectAll("text")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "rotate(" + -angle(i) * (180 / Math.PI) + ")";
})
//Drawing line
svg.selectAll(".layer")
.data(data)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) {
return line(d);
})
}) // End CSV
Example results.csv
scenario,n_dead_oaks,percent_dead_oaks,infected_area_ha,money_spent,area_treated_ha,price_per_oak
baseline,56784,5.898,3417,0,0,0
scen2,52725,5.477,3294,382036,35,94.12071939
RS_1,58037,6.028,3407,796705,59,-635.8379888
RS_2,33571,3.487,2555,1841047,104,79.31103261
RS_3,46111,4.79,2762,1176461,61,110.227771
As Squeegy suggested, you should share some code showing your current progress and how you have achieved to create the axes.
Anyways, this is how I would go about this:
For a given list of values that you want to represent as a line, find the [x,y] coordinates of every point of the line, i.e. place your data-points on each axis. If you have a scale system in place already to draw your axes, this shouldn't be too hard.
Use d3.svg.line to draw a line that goes through all these points.
The code would end up looking like this:
var tempData = [56784, 5.898, 3417, 0, 0, 0];
/** compute tempPoints from tempData **/
var tempPoints = [[123, 30], [12, 123], [123, 123], [0,0], [0,0], [0,0]];
var line = d3.svg.line();
d3.select('svg').append('path').attr('d', line(tempPoints) + 'Z'); // the trailing Z closes the path
I think I have a solution for now and I appreciate all of your response! Here is my current solution for my posting.
function getRowValues(data) {
return $.map(data, function(d, i) {
if (i != "scenario") {
return d;
}
});
}
function getCoor(data) {
console.log(data);
var row = getRowValues(data),
x,
y,
coor = [];
for (var i = 0; i < row.length; i++) {
x = Math.round(Math.cos(angle(i)) * scales[i](row[i]));
y = Math.round(Math.sin(angle(i)) * scales[i](row[i]));
coor.push([x, y]);
}
return coor;
}
var line = d3.svg.line()
.interpolate("cardinal-closed")
.tension(0.85);
svg.selectAll(".layer")
.data(data)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) { return line(getCoor(d)) + "Z"; })
.style("stroke", function(d, i){ return colors[i]; })
.style("fill", "none");
What I have works but I am wondering if there is a better way.
I have this page as my working example. The source code is here and this question deals specifically drawing x axis and y axis with positive and negative values.
I have a drawAxes method that looks like this:
drawAxes(data) {
const xAxis = d3.svg.axis()
.scale(this.xScale);
const yAxis = d3.svg.axis()
.orient('left')
.scale(this.yScale);
const dimensions = this.getDimensions();
this.xScale.domain(d3.extent(data, (d) => d.x));
const minY = d3.min(data, (d) => d.y);
const maxY = d3.max(data, (d) => d.y);
const nonNegativeXAxis = minY >= 0 && maxY >= 0;
const positiveAndNegativeXAxis = minY < 0 && maxY > 0;
let yScaleDomain, xAxisPosition;
if(nonNegativeXAxis) {
yScaleDomain = [0, d3.max(data, (d) => d.y)];
} else {
yScaleDomain = d3.extent(data, (d) => d.y);
}
this.yScale.domain(yScaleDomain);
const findZeroTick = (data) => {
return data === 0;
};
this.svg.append('g')
.attr('class', 'axis y-axis')
.style('visibility', 'hidden')
.call(yAxis);
if(nonNegativeXAxis) {
yScaleDomain = [0, d3.max(data, (d) => d.y)];
xAxisPosition = dimensions.height;
} else if(positiveAndNegativeXAxis) {
xAxisPosition = this.svg.selectAll(".tick").filter(findZeroTick).map((tick) => {
return d3.transform(d3.select(tick[0]).attr('transform')).translate[1];
});
} else {
yScaleDomain = d3.extent(data, (d) => d.y);
xAxisPosition = 0;
}
this.svg.append('g')
.attr('class', 'axis x-axis')
.attr('transform', `translate(0, ${xAxisPosition})`)
.call(xAxis);
d3.select('.y-axis').remove();
const minX = d3.min(data, (d) => d.x);
const maxX = d3.max(data, (d) => d.y);
const positiveXOnly = minX > 0 && maxX > 0;
const negativeXOnly = minX < 0 && maxX < 0;
let yAxisPosition;
if(positiveXOnly) {
yAxisPosition = 0;
} else if(negativeXOnly) {
yAxisPosition = dimensions.width;
} else {
yAxisPosition = this.svg.selectAll(".x-axis .tick").filter(findZeroTick).map((tick) => {
return d3.transform(d3.select(tick[0]).attr('transform')).translate[0];
});
}
this.svg.append('g')
.attr('class', 'axis y-axis')
.attr('transform', `translate(${yAxisPosition}, 0)`)
.call(yAxis);
}
WHat I found great difficulty in was positioning the y axis x axis at 0 on the opposite axis.
How I did this was to first of all draw the y axis but keep it hidden:
this.svg.append('g')
.attr('class', 'axis y-axis')
.style('visibility', 'hidden')
.call(yAxis);
I then found the position of 0 on the y axis by selecting all the ticks and finding the y coordinate of the 0 tick, I use this value to position the x axis:
const findZeroTick = (data) => {
return data === 0;
};
xAxisPosition = this.svg.selectAll(".tick").filter(findZeroTick).map((tick) => {
return d3.transform(d3.select(tick[0]).attr('transform')).translate[1];
});
this.svg.append('g')
.attr('class', 'axis x-axis')
.attr('transform', `translate(0, ${xAxisPosition})`)
.call(xAxis);
To position the yAxis, I first of all remove the yAxis and then do a similar calculation by selecting all the ticks on the x-axis and then adding the yAxis again with and using the retrieved value to position the yAxis:
d3.select('.y-axis').remove();
yAxisPosition = this.svg.selectAll(".x-axis .tick").filter(findZeroTick).map((tick) => {
return d3.transform(d3.select(tick[0]).attr('transform')).translate[0];
});
this.svg.append('g')
.attr('class', 'axis y-axis')
.attr('transform', `translate(${yAxisPosition}, 0)`)
.call(yAxis);
Is there a more efficient way of achieving the same result or failing that, can I move the axis without having to remove it and re-add it?
As soon as your linear scale has a domain and a range you can use it to get the pixel value of any domain value. For example, to get the pixel of point (0 , 0) you would ask xScale(0) and yScale(0).
If these values lie outside the defined range (read: svg canvas dimensions in pixels), you know that the Y-axis should be on the far left or right respectively (min or max range plus margin).
I have a bar graph in my program, but it doesn't seem to be displaying properly. All of the bars seem to be a lot bigger than they are supposed to be. Here's the relevant code:
//Bar Graph
var canvas = d3.select("#canvas");
canvas.width = 500;
canvas.height = 500;
var values = [1, 2, 3]
var colours = ['#FA0', '#0AF', '#AF0']
var data = []
var yOffset = 0
//create scale
yRange2 = d3.scale.linear().range([canvas.height - MARGINS.top,
MARGINS.bottom]).domain([0, 6]);
//Process the data
for (var i = 0; i < values.length; i++) {
var datum = {
value: yRange2(values[i]),
colour: colours[i],
x: 0,
y: yOffset
}
yOffset += datum.value;
data.push(datum)
}
//setup y
yAxis2 = d3.svg.axis()
.scale(yRange2)
.tickSize(5)
.orient("left")
.tickSubdivide(true);
canvas.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis2);
var bars = canvas.selectAll('rect').data(data)
bars
.enter()
.append('rect')
.attr({
width: 30,
x: 60,
y: function (d) {
return d.y
},
height: function (d) {
return d.value;
}
})
.style({
fill: function (d) {
return d.colour
}
})
//updates when slider changes
$("#myRange").change(function () {
slider = $("#myRange").val();
updateXs();
updateLineData();
displayVals();
d3.select(".myLine").transition()
.attr("d", lineFunc(lineData));
});
And here's the full code:
http://jsfiddle.net/tqj5maza/7/
To me, it looks like the bars are starting at the top for some reason, and then going downwards, hence the cutoff. The height for each seems too large, though.
You aren't setting the rects height property correctly. Generally this is height of the plotting area minus y position. The way you have your code structured its:
height: function (d) {
return canvas.height - MARGINS.top - d.value;
}
To fix the overlapping x value, you should set up an x d3.scale but a quick and dirty way would be:
x: function(d,i){
return (i + 1) * 60; //<-- move each bar over 60 pixels
}
Updated code here.
Working in d3.js, I am looking for a good way to display categorical time series data. The data values cannot co-occur, and are not evenly spaced, so I've data exactly like:
location = [[time1: home], [time4: work], [time5: cafe], [time7: home]]
and so on. My ideal resulting graph is something like what might be called an evolustrip - one way of seeing this chart is as a time series chart with variable width bars, bar color corresponding to category (e.g. 'home').
Can anyone point me in the right direction? Thank you so much!
So I ended up crafting my own d3.js solution:
I used a d3.time.scale scale for the time dimension, and then a d3.scale.category20 scale to provide colors for the categories. I then plotted the categorical data as same-height rects on the time axis by start time, and used the d3.time.scale scale to compute the appropriate bin width for each rect.
A reusable component (following the pattern at http://bost.ocks.org/mike/chart/) example can be seen here:
function timeSeriesCategorical() {
var w = 860,
h = 70,
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var xValue = function(d) { return d[0]; },
yValue = function(d) { return d[1]; };
var yDomain = null;
var xScale = d3.time.scale()
.range([0, width]);
var yScale = d3.scale.category20();
var xAxis = d3.svg.axis()
.scale(xScale)
.tickSubdivide(1)
.tickSize(-height)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.ticks(5)
.orient('left');
var binwidth = 20;
function chart(selection) {
selection.each(function(data) {
// convert data to standard representation
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
//return d;
});
// scale the x and y domains based on the actual data
xScale.domain(d3.extent(data, function(d) { return d[0]; }));
if (!yDomain) {
yScale.domain(d3.extent(data, function(d) { return d[1]; }));
} else {
yScale.domain(yDomain);
}
// compute binwidths for TODO better comment
// d looks like {timestamp, category}
data.forEach(function(d, i) {
if (data[i+1]) {
w_current = xScale(data[i][0]);
w_next = xScale(data[i+1][0]);
binwidth = w_next - w_current;
}
d.binwidth = binwidth;
});
// create chart space as svg
// note: 'this' el should not contain svg already
var svg = d3.select(this).append('svg').data(data);
// external dimensions
svg.attr('width', w)
.attr('height', h);
// internal dimensions
svg = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// x axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// TODO bars legend
// bars
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('x', function(d, i) { return xScale(d[0]); })
.attr('width', function(d, i) { return d.binwidth; })
.attr('height', height)
.attr('fill', function(d, i) { return yScale(d[1]); })
.attr('stroke', function(d, i) { return yScale(d[1]); });
});
}
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
chart.yDomain = function(_) {
if (!arguments.length) return yDomain;
yDomain = _;
return chart;
};
return chart;
}
and is callable with something like:
d3.csv('./data.csv', function(data) {
var chartActivity = timeSeriesCategorical()
.x(function(d) { return d.when; })
.y(function(d) { return d.activity; })
.yDomain([0,1]);
d3.select('#chart-activity')
.datum(data)
.call(chartActivity);
});
Hopefully this is helpful to someone! The project this was made for is at https://github.com/interaction-design-lab/stress-sense-portal