How does one make a basic scatter plot like the one below using Plottable.js?
Is there something wrong with my JSON?
How to reveal the minus scales?
Would you have done anything else differently?
Style doesn't matter, the default Plottable.js one is fine.
window.onload = function() {
var coordinates = [
{
x:"-5",
y:"3"
}, {
x:"2",
y:"-1,5"
}, {
x:"5",
y:"2,5"
}
];
var xScale = new Plottable.Scale.Linear();
var yScale = new Plottable.Scale.Linear();
var colorScale = new Plottable.Scale.Color("10");
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axis.Numeric(yScale, "left");
var plot = new Plottable.Plot.Scatter(xScale, yScale)
.addDataset(coordinates)
.project("x", "", xScale)
.project("y", "", yScale)
.project("fill", "", colorScale);
var chart = new Plottable.Component.Table([
[yAxis, plot],
[null, xAxis]
]);
chart.renderTo("#my_chart");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="stylesheet" href="https://rawgit.com/palantir/plottable/develop/plottable.css">
</head>
<body>
<svg width="100%" height="600" id="my_chart"></svg>
<script src="https://rawgit.com/mbostock/d3/master/d3.min.js"></script>
<script src="https://rawgit.com/palantir/plottable/develop/plottable.min.js"></script>
</body>
</html>
Mark has the right idea - the table system doesn't natively support this layout, so you need to take some manual control over how they are laid out. However, using somewhat obscure parts of the Plottable API, there is a cleaner and better-supported way to lay out the chart you want, which doesn't have the problem of the axes being slightly offset.
The first change is we are going to stop using the table layout engine entirely, since it isn't able to do what we want. Instead, we will plop all the components together in a Component.Group. A Group just overlays components in the same space without trying to position them at all.
var chart = new Plottable.Component.Group([yAxis, xAxis, plot]);
Then we are going to use the alignment and offset methods that are defined on the base (abstract) component class. We set the x-alignment of the y axis to "center" and the y-alignment of the x axis to "center" This will put the axes in the center of the chart.
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom").yAlign("center");
var yAxis = new Plottable.Axis.Numeric(yScale, "left").xAlign("center");
We're not quite done at this point, since to really center the axes we need to shift them back by one half of their own width. The width is only calculated when the chart is rendered (strictly speaking, in the computeLayout call, but that is an internal detail), so we need to set an offset after the chart is rendered:
chart.renderTo("#plottable");
xAxis.yOffset(xAxis.height()/2);
yAxis.xOffset(-yAxis.width()/2);
You can see the final result here (it's a fork of Mark's plnkr). Note that now the axes are aligned on the center of the chart, as the center dot is perfectly on 0,0.
Here's a couple examples I just put together. The first is the straight d3 way of doing what you are asking. The second is a hacked up plottable.js. With plottable.js I can't find a way to position the axis outside of their table system, I had to resort to manually moving them. The table system they use is designed to relieve the developer of having to manually position things. This is great and easy, of course, until you want to control where to position things.
Here's the hack, after you render your plottable:
// move the axis...
d3.select(".y-axis")
.attr('transform',"translate(" + width / 2 + "," + 0 + ")");
d3.select(".x-axis")
.attr("transform", "translate(" + 48 + "," + height / 2 + ")");
Note, I didn't remove the left side margin (the 48 above) that plottable puts in. This could be hacked in as well, but at that point, what is plottable providing for you anyway...
It should be noted that the different appearance of each plot is entirely controlled through the CSS.
Complete d3 scatter plot:
// D3 EXAMPLE
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#d3").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([-100, 100]);
y.domain([-100, 100]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.style("fill", function(d) {
return d.c;
});
Plottable.js:
// PLOTTABLE.JS
var xScale = new Plottable.Scale.Linear();
var yScale = new Plottable.Scale.Linear();
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axis.Numeric(yScale, "left");
var plot = new Plottable.Plot.Scatter(xScale, yScale);
plot.addDataset(data);
function getXDataValue(d) {
return d.x;
}
plot.project("x", getXDataValue, xScale);
function getYDataValue(d) {
return d.y;
}
plot.project("y", getYDataValue, yScale);
function getRDataValue(d){
return d.r;
}
plot.project("r", getRDataValue);
function getFillValue(d){
return d.c;
}
plot.project("fill", getFillValue);
var chart = new Plottable.Component.Table([
[yAxis, plot],
[null, xAxis]
]);
chart.renderTo("#plottable");
d3.select(".y-axis")
.attr('transform',"translate(" + width / 2 + "," + 0 + ")");
d3.select(".x-axis")
.attr("transform", "translate(" + 48 + "," + height / 2 + ")");
Related
I'm trying to draw a circle with different data values as angles but for some reason, it's only the last data point that gets the color and display. I've tried to translate the svg but it seems not to budge.
I'm fairly new to D3 so I'm sure I've done something less intelligent without realizing it. As far I could tell, the angles in the g and path elements are as supposed to.
var height = 400, width = 600, radius = Math.min(height, width) / 2;
var colors = ["#red", "pink", "green", "yellow", "blue","magent","brown","olive","orange"];
var data = [1,2,1,2,1,2,1,3,1];
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
chart.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie = d3.layout.pie().sort(null).value(function (d) { return d; });
var arc = d3.svg.arc().startAngle(0).innerRadius(0).outerRadius(radius);
var grx = chart.selectAll(".sector").data(pie(data))
.enter().append("g").attr("class", "sector");
grx.append("path")
.attr("d", arc)
.style("fill", function (d, i) {
console.log(d);
return colors[i];
});
The problem is that you're appending all the sectors of the pie to the svg node when they should be appended to the translated g node, you have two options to solve this problem
make chart equal to the translated g node
select g before all the .sectors and store that in grx
The first solution is simpler e.g.
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
demo
I'm a beginner with D3.js and I want to display a dynamic line chart where the line is always growing with random fluctuations.
I don't need an X axis but I'd like to get a dynamic Y axis based on the last point inserted in the line.
var n = 40,
random = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; },
data = d3.range(n).map(random);
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([1, n - 2])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var min = 0, max = min + 40;
tick();
//Update the chart
function tick() {
// push a new data point onto the back
var r = random(min, max);
data.push(r);
min += 10;
max += 10;
// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(0) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
JSFiddle : https://jsfiddle.net/ugj8g9wu/
If I didn't increase the min / max and don't update the Y Axis everything is ok.
But with the code above, my line quickly go above the the Y axis, which doesn't make any sens since the randomized value is include in the domain of the Y axis...
Could you tell me what's going on and why my line isn't properly displayed?
The issue is a bit hidden. In tick(), you made a new y to handle the new domain and range, but you only updated yAxis with this y. What about the line which is still referencing the original y? It also needs update! You can either add code to update the line:
// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);
// NEW CODE
line.y(function(d, i) { return y(d); });
Or (better I think), instead of creating a new y every tick, you can modify the existing one, saving all the efforts to assign it to everywhere else using it. Just change this line:
var y = d3.scale.linear().domain([minY, maxY]).range([height, 0]);
into:
y.domain([minY, maxY]);
Then you'll be able to see the newest point coming in the right.
But there's one more problem with the code: you are increasing the value too quickly so that it's hard to see old points on the chart, so I tuned the arguments a bit to make it look better. Ideally, the minY and maxY should be calculated according to the values in data, not guessing magic boundarys. :)
Fiddle: https://jsfiddle.net/gbwycmrd/
I have currently have a quick test for a graph I'm about to create for website and I have made the most basic functionality. I have a graph, a 4 elements and an x and a y axis and a zoom functionality.
My problem lies in the fact that when I zoom on the graph, the elements are able to reach the axis and overlap it. I've pasted my source code below
//Setting generic width and height values for our SVG.
var margin = {top: 60, right: 0, bottom: 60, left: 40},
width = 1024 - 70 - margin.left - margin.right,
height = 668 - margin.top - margin.bottom;
//Other variable declarations.
//Creating scales used to scale everything to the size of the SVG.
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, 0]);
//Creates an xAxis variable that can be used in our SVG.
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Zoom command ...
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([1, 10])
.on("zoom", zoomTargets);
// The mark '#' indicates an ID. IF '#' isn't included argument expected is a tag such as "svg" or "p" etc..
var SVG = d3.select("#mainSVG")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
//Create background. The mouse must be over an object on the graph for the zoom to work. The rectangle will cover the entire graph.
var rect = SVG.append("rect")
.attr("width", width)
.attr("height", height);
//Showing the axis that we created earlier in the script for both X and Y.
var xAxisGroup = SVG.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxisGroup = SVG.append("g")
.attr("class", "y axis")
.call(yAxis);
//This selects 4 circles (non-existent, there requires data-binding) and appends them all below enter.
//The amount of numbers in data is the amount of circles to be appended in the enter() section.
var circle = SVG
.selectAll("circle")
.data([40,100,400,1900])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(d)})
.attr("cy",function(d){return yScale(d)})
.attr("r",20);
//Resets zoom when click on circle object. Zoom work now, should be changed to a button instead of click on circle though.
SVG.selectAll("circle").on("click", function() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
});
function zoomTargets() {
SVG.select(".x.axis").call(xAxis);
SVG.select(".y.axis").call(yAxis);
SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
}
function resetZoom() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
}
I've tried using "append("g2") before creating a circle to I can make g2 smaller than the entire svg, but that doesn't seem to work. As far as I have understood, you can just append a new element inside your existing one. I'm guessing I'm wrong since it hasn't worked for me.
Thank you in advance for your help.
Leave a small gap between the most extreme data point and the axis. In particular, you may want the range of your domain to take the margins into account:
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width-margin.right]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, margin.bottom]);
I'm new to d3 and I'm trying to do some data visualization with it. I found some examples about how to create a time scale in d3, but when I followed the examples and try to create the time scale, it failed. I was frustrated because I couldn't figure out where it went wrong... the example is like this:
how to format time on xAxis use d3.js
So my code is like this:
boxplotdata =[]
boxplotdata.push(
{"datetime":"2013-10-30 01:47",length: 500, start:100,deep1_a:130,deep1:50,deep2_a:200,deep2:60,deep3_a:280,deep3:50,deep4_a:350,deep4:60},
{"datetime":"2013-10-31 01:45",length: 600, start:200,deep1_a:230,deep1:60,deep2_a:300,deep2:60,deep3_a:380,deep3:50,deep4_a:450,deep4:60},
{"datetime":"2013-11-01 02:11",length: 550,start:150,deep1_a:180,deep1:50,deep2_a:250,deep2:60,deep3_a:350,deep3:50,deep4_a:410,deep4:60},
{"datetime":"2013-11-02 01:59",length: 500,start:160,deep1_a:190,deep1:80,deep2_a:300,deep2:60,deep3_a:370,deep3:50,deep4_a:430,deep4:60},
);
//SET MARGIN FOR THE CANVAS
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d %H:%M").parse;
//SET X AND Y
var x = d3.time.scale()
.domain([0,11])
.range([50, width]);
var y = d3.time.scale()
.domain([new Date(boxplotdata[0].datetime),d3.time.day.offset(new Date(boxplotdata[boxplotdata.length-1].datetime),1)])
.rangeRound([20, height-margin.top- margin.bottom]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.tickFormat(d3.time.format("%H:%M"))
//.ticks(d3.time.minutes,15)
//.tickPadding(8);
var yAxis = d3.svg.axis()
.scale(y)
.orient('right')
.ticks(d3.time.days,1)
.tickFormat(d3.time.format('%m-%d'))
.tickSize(0)
.tickPadding(8);
var line = d3.svg.line()
.x(function(d) { return x(d.datetime); });
var w=960,h=1000;
d3.select("#chart").selectAll("svg").remove(); //Old canvas must be removed before creating a new canvas.
var svg=d3.select("#chart").append("svg")
//.attr("width",w).attr("height",h);
.attr("width",w+margin.right+margin.left).attr("height",h+margin.top+margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
boxplotdata.forEach(function(d) {
d.datetime = parseDate(d.datetime);
});
x.domain(d3.extent(boxplotdata, function(d) { return d.datetime; }));
bars = svg.selectAll("g")
.data(boxplotdata)
.enter()
.append("g");
some drawing codes here..., and at last:
svg.append("g")
.attr("class", "x axis")
//.attr('transform', 'translate(0, ' + (height - margin.top - margin.bottom) + ')')
.call(xAxis);
svg.append("g")
.attr('class', 'y axis')
.call(yAxis);
However, when I tried, I could only get a graph with all time on the xAxis shown as "00:00". What's going wrong here? Hope someone can help me out. Thanks!
Your example data is from different days, so what's happening here is that D3 is picking representative values (i.e. the boundaries between days) and making ticks for that. As your date format only shows hour and minute, 00:00 is all you get.
You have basically two options here. You could either change the date format to show days (which is what D3 intends), or you could set the tick values explicitly to whatever you want. For the first, you could use e.g. d3.time.format("%a"). For the second, see the documentation.
Hi You can use the tickFormat function on the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%H"));
I am trying to develop a timeline chart on d3.js. As you will see on the image below, I cannot position the triangles on the same orientation with the y-axis values. The milestones are positioned in the middle of the related y-axis component.
yaxis initiation code fragment:
var x = d3.time.scale().rangeRound([0, self.config.width]);
var y = d3.scale.ordinal().rangeRoundBands([self.config.height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSubdivide(4).tickSize(6, 3, 0);//.ticks(d3.time.months,4)
var yAxis = d3.svg.axis().scale(y).orient("left").tickSize(4);
appending y axis to svg:
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
the code fragment for milestones:
var abs = svg.selectAll(".milestone")
.data(data)
.enter().append("g");
abs.selectAll("symbol")
.data(function(d) {
return d.milestoneList;
})
.enter().append("svg:path")
.attr("transform", function(d) {
return "translate(" + x(d.deadline) + "," + y(d.name) + ")";
})
.attr("d", d3.svg.symbol().type("triangle-down"));
For instance FG55 y-axis is set: translate(0,423) although the milestones from FG55 are set translate(<xValue for each>,376) so there are 47px difference on y
How can I position the yaxis labels and ticks properly?
I modified my code as it follows:
Old code
var y = d3.scale.ordinal().rangeRoundBands([self.config.height, 0]);
New Code
var y = d3.scale.ordinal().rangePoints([self.config.height, 0], 1.5);
When using bands in D3 the scale will give you the location of the top of the band, rather than the centre. The scale also provides you with the width of the bands which you can use to calculate the y position where your point should be placed.
Therefore in the code above, you would change your transform to this:
.attr("transform", function(d) {
var yPosition = y(d.name) + y.bandwidth() / 2;
return "translate(" + x(d.deadline) + "," + yPosition + ")";
})