I'm trying to customize my x axis on a d3 chart; I want to add two labels, "left" and "right" at both ends of it.
I tried this, but it doesn't work:
var xlabels = ["Left", "Right"]
var xAxis = d3.svg.axis()
.scale(xScale)
.tickValues(xScale.domain())
.orient("bottom")
.ticks(2)
.tickFormat(xlabels)
;
Do you know how to do it?
axis.tickFormat as the name implies defines the label for each of the ticks, if you want to add new labels at both ends you need to add them on your own:
Assuming that you have a reference to the root svg in the var svg
svg.append('text')
.attr('text-anchor', 'start')
.attr('y', function () {
// y position of the left label
// typically a value less than the height of the svg
})
.attr('x', function () {
// x position of the left label
// typically a value near to 0
})
.text('Left')
svg.append('text')
.attr('text-anchor', 'end')
.attr('y', function () {
// y position of the right label
// typically a value less than the height of the svg
})
.attr('x', function () {
// x position of the right label
// typically a value near the width of the svg
})
.text('Right')
Also have a look at http://bl.ocks.org/mbostock/1166403, these lines define a label like the one you need:
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(values[0].symbol);
Related
I have been able to make a scatter plot with zoom and pan functionality where the axes scale properly and everything works well. Now I am trying to figure out how to add gridlines, but running into some issues. I have started with only adding x-axis gridlines to figure things out. I have attached a fiddle with a working example to build from.
I commented out the initial gridlines when the graph is generated, because they would remain after zooming causing clutter, and I will add them back later when I get things working. When zooming the gridlines appear to be drawn correctly, but they do not match up with the x-axis labels, and the x-axis labels disappear after zooming or panning.
If you comment out line 163 and uncomment line 164 you can see the basic graph without any gridlines. Clicking the plot button will always generate a new graph. I have left behind some commented out code of different things that I have tried from searching through stackoverflow.
Example is using d3.js - 5.9.2
JSFiddle: https://jsfiddle.net/eysLvqkh/11/
HTML:
<div id="reg_plot"></div>
<button id="b" class="myButton">plot</button>
Javascript:
var theButton = document.getElementById("b");
theButton.onclick = createSvg;
function createSvg() {
// clear old chart when 'plot' is clicked
document.getElementById('reg_plot').innerHTML = ""
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 55},
svg_dx = 1200,
svg_dy =600,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// append svg to div element 'reg_plot' and set zoom to our function named 'zoom'
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom));
// clip path - sets boundaries so points will not show outside of the axes when zooming/panning
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "10px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom - margin.top})`)
.call(xAxis).style("font-size", "10px")
// add x and y grid lines
x_axis.call(xAxis.scale(xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(yScale).ticks(20).tickSize(-chart_dx));
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
// re-draw circles using new scales
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// re-scale axes and gridlines
x_axis.call(xAxis.scale(new_xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(new_yScale).ticks(20).tickSize(-chart_dx));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
For anyone looking, I have solved this problem. I have updated the javascript in the original post, and updated the jsfiddle. If you are copying this code to your local machine where you are using d3.js 7.4.4 or higher then you need to change the lines that say d3.event.transform.... to just e.transform.
Using D3.js Is there an option to overlay one bar over another?
I need two independent columns to indicate start and end time (x axis) against a count (y axis). I want BOTH columns to be overlaid over each other (similar start time e.g x1=8:45am, x2=10:05, and same end times. y1=90, y2=108), both columns will have transparency around 0.5, so each column can be seen over the common time/count range.
A similar example using Highcharts:
http://jsfiddle.net/gopinaghr/q8Udk/
// This page provides an example in highcharts
{ I need same for D3}
I need to create a chart where
Column width dependant on (end_time - start_time)
Column x origin is dependant on start time
Column height dependant on y value
The columns need opacity less than 1.
d3.js requires you to explicitly place your bars at coordinates, so, you can put your bars where ever you like:
// create a group for your overlapped bars
var g = svg.selectAll(".bars")
.data(data)
.enter().append("g")
// place the first bar
g.append("rect")
.attr("class", "bar1")
.attr("x", function(d) {
return x(d.letter) + 10; // center it
})
.attr("width", x.rangeBand() - 20) // make it slimmer
.attr("y", function(d) {
return y(d.col1);
})
.attr("height", function(d) {
return height - y(d.col1);
});
// place the second bar on top of it
g.append("rect")
.attr("class", "bar2")
.attr("x", function(d) {
return x(d.letter);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.col2);
})
.attr("height", function(d) {
return height - y(d.col2);
});
Here's a quick example.
EDITS
To add in time, you have to make a number of changes.
Set up a time formatter, to parse your date/times out of your file:
// say date/times are local 20160622 15:00
var timeFormatter = d3.time.format("%Y%m%d %H:%M")
Set up a time x-scale for your axis:
// x scale showing 1 day - 06/22/2015
var x = d3.time.scale()
.range([0,width])
.domain([timeFormatter.parse("20150621 00:00"), timeFormatter.parse("20150622 00:00")])
And when you draw the rect, with width is the number of pixels from endTime to startTime:
g.append("rect")
.attr("class", "bar1")
.attr("x", function(d) {
return x(d.startTime1); // start at startTime
})
.attr("width", function(d,i){
return x(d.endTime1) - x(d.startTime1); // pixel width from end to start
})
.attr("y", function(d) {
return y(d.col1);
})
.attr("height", function(d) {
return height - y(d.col1);
});
Example here.
I have a graph where I have both circles and rectangles along the ticks of my x-axis. I'd like to be able to center all elements on the ticks of the x-axis. Circles are automatically placed in the center and scaled with a radius attribute, but with rectangles, I am not able to move it as I am using an ordinal scale to create my x-axis values.
This is how much graph looks like: http://puu.sh/gmCkZ/37ab176161.png
It can be seen that the squares upper-left corner is the position I want to be it's center.
My code for the x-axis looks like the following:
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 1);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
d3.tsv("data.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.name; }));
How can i center a figure like the square in a certain position? In this case, on a specific area of the x-axis?
EDIT
Added SVG code for square:
var square = svgbody
.selectAll("nodes.rect")
.data(["B", "L"]);
square.exit()
.style("opacity", 1)
.transition()
.duration(500)
.style("opacity", 0)
.remove();
square.enter()
.append("rect")
.attr("class", "squareNodes");
square
.attr("x", function(d){ return x(d);})
.attr("y", function(d){return y(statusText);} )
.attr("width",19)
.attr("height",19)
.attr("rx", "3")
.attr("ry", "3" )
.style('opacity', NODE_OPACITY)
.style('fill', "green");
squareSize = 19;
square
.attr("x", function(d){ return x(d) - squareSize/2;})
.attr("y", function(d){return y(statusText) - squareSize/2;} )
.attr("width",squareSize)
.attr("height",squareSize)
...
If the square is 50px wide, and you have a class called .square:
.square{
position: relative;
right: 25px;
}
Just move it over half. See if that works.
Here is a fiddle to show you what I mean.
http://jsfiddle.net/plushyObject/hkwwxq9j/
i was trying to draw simple d3js graph.I got through drawing the axis and even plotted the data but the data isn't appearing where it is expected to be.
As per my json data below
var d1 = [{
value1: "30",
value2: "10"
}];
i'm trying to plot a circle at coordinates x axis 30 and y axis 10but the circle on the graph appears some where else.
Here is the jsfiddle demo
Here is my code
var d1 = [{
value1: "30",
value2: "10"
}];
function Update(){
var circles = vis.selectAll("circle").data(d1)
circles
.enter()
.insert("svg:circle")
.attr("cx", function (d) { return d.value1; })
.attr("cy", function (d) { return d.value2; })
.style("fill", "red")
circles
.transition().duration(1000)
.attr("cx", function (d) { return d.value1; })
.attr("cy", function (d) { return d.value2; })
.attr("r", function (d) { return 5; })
circles.exit ()
.transition().duration(1000)
.attr("r", 0)
.remove ();
}
/*************************************************/
/*******************Real Stuff starts here*******************/
var vis = d3.select("#visualisation"),
WIDTH = 600,
HEIGHT = 400,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 50
},
xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([0,100]),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0,300]),
xAxis = d3.svg.axis() // generate an axis
.scale(xRange) // set the range of the axis
.tickSize(5) // height of the ticks
.tickSubdivide(true), // display ticks between text labels
yAxis = d3.svg.axis() // generate an axis
.scale(yRange) // set the range of the axis
.tickSize(5) // width of the ticks
.orient("left") // have the text labels on the left hand side
.tickSubdivide(true); // display ticks between text labels
function init() {
vis.append("svg:g") // add a container for the axis
.attr("class", "x axis") // add some classes so we can style it
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") // move it into position
.call(xAxis); // finally, add the axis to the visualisation
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
}
init();
$('#btn').click(function(){
Update();
});
It works if you
define the numbers as numbers and not as strings (i.e. value1: 30 instead of value1: "30") and
use the scales you define (i.e. return xRange(d.value1) instead of return d.value1).
Working jsfiddle here.
Your circle is appearing at pixel (30,10), but that doesn't correspond to the place 30,10 as labeled by your axes. Use your scales to set the point's location.
.attr("cx", function (d) { return xRange(d.value1); })
.attr("cy", function (d) { return yRange(d.value2); })
You will need to apply xScale and yScale to your coordinates to transform them into the plotting space.
See this jsFiddle
.attr("cx", function (d) { return xRange(d.value1); })
.attr("cy", function (d) { return yRange(d.value2); })
Actually it is working fine. It is just that top left corner is (0,0) and not bottom left (as I suspect, you must be assuming).
Set both x,y to 0. Circle will appear at top left corner.
I'm playing around with D3 and want tick lines to cut through a linear time graph across the vertical axis. The tick line elements are there, with the correct vectors, but they do not appear. What appears instead is the path element that runs horizontally with the tick labels.
JSFiddle Link
var width = 960;
var height = 200;
var container = d3.select(".timeTable");
var svg = container.append("svg")
.attr("width", width)
.attr("height", height);
var roomID = container.attr("data-room");
var times = [
{"from":"2012-12-27 00:00:00","until":"2012-12-27 12:00:00"},
{"from":"2012-12-27 00:00:00","until":"2012-12-27 23:59:00"},
{"from":"2012-12-27 02:00:00","until":"2012-12-27 04:00:00"},
{"from":"2012-12-27 03:00:00","until":"2012-12-27 21:00:00"},
{"from":"2012-12-27 03:30:00","until":"2012-12-27 04:50:00"},
{"from":"2012-12-27 05:00:00","until":"2012-12-27 12:00:00"},
{"from":"2012-12-27 09:00:00","until":"2012-12-27 15:00:00"},
{"from":"2012-12-27 13:00:00","until":"2012-12-27 23:00:00"},
{"from":"2012-12-27 13:00:00","until":"2012-12-27 23:30:00"},
{"from":"2012-12-27 20:00:00","until":"2012-12-27 23:59:00"},
{"from":"2012-12-27 20:00:00","until":"2012-12-27 22:00:00"},
{"from":"2012-12-27 23:00:00","until":"2012-12-27 23:30:00"},
{"from":"2012-12-28 01:00:00","until":"2012-12-28 13:00:00"}
];
function draw(times) {
// domain
var floor = d3.time.day.floor(d3.min(times, function (d) { return new Date(d.from); }));
var ceil = d3.time.day.ceil(d3.max(times, function (d) { return new Date(d.until); }));
// define linear time scale
var x = d3.time.scale()
.domain([floor, ceil])
.rangeRound([0, width]);
// define x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(d3.time.hours, 6)
.tickSize(4);
// draw time bars
svg.selectAll("rect")
.data(times)
.enter().append("rect")
.attr("class", "timeRange")
.attr("width", function (d, i) { return x(new Date(d.until)) - x(new Date(d.from)); })
.attr("height", "10px")
.attr("x", function (d, i) { return x(new Date(d.from)); })
.attr("y", function (d, i) { return i * 11; });
// draw x axis
svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(0, " + (height - 23) + ")")
.call(xAxis);
}
draw(times);
The path element generated simply overlaps the ticks, but the ticks are not visible even with path removed.
The desired tick effect is shown here Population Pyramid - ticks on the vertical axis have a line that cuts through the rest of the graph.
Is there different behavior I need to be aware of for time scales?
Much appreciated.
Chrome 23, D3 v3
The trick to getting the tick lines into the plot area is to actually make a second axis and hide the labels. So your code plus the grid lines looks something like (fiddle):
// draw x axis
var xAxisLine = svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(0, " + (height - 23) + ")")
.call(xAxis);
xAxisLine.selectAll("path.domain").attr("stroke","black").style('fill','none');
xAxisLine.selectAll("line.tick").style('stroke','black');
xAxis.tickSize(height-23);
var xAxisLineOver = svg.append("g")
.attr("class", "xAxis-overlay")
.attr("transform", "translate(0,0)")
.call(xAxis);
xAxisLineOver.selectAll("path.domain").attr("stroke-width",0).style('fill','none');
xAxisLineOver.selectAll("line.tick").style('stroke','red');
xAxisLineOver.selectAll("text").text("");
I'm not sure this is the exact same problem I had. What worked for me was:
.tick line{
stroke: black
}
Chrome is very strict regarding rendering SVG. So keep this in mind:
For axes specify their full RGB value (so #ff0000 instead of just #f00).
Path stroke widths are tricky. If they are less than 1 (px) and you have included
shape-rendering: crispEdges;
in CSS styles than any web browser might not display such path (or when the chart is resized to different size).
In that case comment or delete the "crispEdges" rendering and you should be fine (though in this case you leave the browser the smoothing decision).