I'm building a bar chart that will have a varied number of data points. All the examples I've seen use an ordinal scale for the x axis, then call rangeRoundBands() to get the width for the bars.
I'm getting the error "Uncaught TypeError: Cannot read property '1' of undefined" when I try to called .rangeRoundBands() and as far as I can tell I'm setting the scale up properly.
http://jsfiddle.net/rLzbbec0/1/
var globOb = {
bar: {
height: 390,
width: 675,
innerHeight: 300,
innerWidth: 615,
margin: {
bottom: 70,
left: 40,
right: 20,
top: 20
}
},
barData: [
{ task: "meetingsTask", val: 2.5 },
{ task: "reportsTask", val: 3 },
{ task: "emailsTask", val: 2 },
{ task: "planningTask", val: 1.5 },
{ task: "clientsTask", val: 4 },
{ task: "dataAnalysisTask", val: 3 }
]};
//Set X Scale
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, globOb.bar.innerWidth], .05, .1);
xScale.domain(globOb.barData.map(function(d) {
return d.task
}));
//Set Y Scale
var yScale = d3.scale.linear()
.domain([0, d3.max(globOb.barData, function(d) {
return d.val + 1
})])
.range([globOb.bar.innerHeight, 0]);
//Grab SVG and set dimensions
var svg = d3.select("#viewport").append("svg")
.attr("width", globOb.bar.width)
.attr("height", globOb.bar.height);
//Set the drawing area of the SVG
var inner = svg.append("g")
.attr("transform", "translate(" + globOb.bar.margin.left + "," +
globOb.bar.margin.top + ")")
.attr("width", globOb.bar.innerWidth)
.attr("height", globOb.bar.innerHeight);
//Setup Axes
var xAxis = d3.svg.axis()
.scale(xScale)
.innerTickSize(0)
.outerTickSize(-globOb.bar.innerHeight)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.innerTickSize(-globOb.bar.innerWidth)
.outerTickSize(-globOb.bar.innerWidth)
.orient("left");
//Add X Axis
inner.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (globOb.bar.innerHeight) + ")")
.call(xAxis);
//Add Y axis
inner.append("g")
.attr("class", "axis")
.attr("transform", "translate(0,0)")
.call(yAxis);
//draw data
inner.selectAll("bar")
.data(globOb.barData)
.enter()
.append("rect")
.attr("class", function(d) {
return d.task.substring(0, d.task.length - 4) + "-bar"
})
.attr("x", function(d) {
return xScale(d.task)
})
.attr("width", 50) //xScale.rangeRoundBands())
.attr("y", function(d) {
return yScale(d.val)
})
.attr("height", function(d) {
return globOb.bar.innerHeight - yScale(d.val)
});
console.log(xScale.rangeRoundBands());
In the fiddle I have changed the call of rangeRoundBands() to a set number to show the chart otherwise draws fine. At the end I'm using console.log() to show the error with .rangeRoundBands().
ordinal.rangeRoundBands is a configuration setter, and you cannot call it without arguments, so it generates an error while trying to access the second item x[1] (indexed from zero) in the first argument x. You can take a look at the source.
You probably want to use ordinal.rangeBand() instead, which:
Returns the band width. When the scale’s range is configured with rangeBands or rangeRoundBands, the scale returns the lower value for the given input. The upper value can then be computed by offsetting by the band width. If the scale’s range is set using range or rangePoints, the band width is zero.
Related
I am newish to D3, and I've been trying to troubleshoot this issue for a few days now without luck. I'm not sure what to try next.
I have a JSON dataset with daily data, and I'm attempting to make a bar chart with one bar per day. That's all good. However, I'm having trouble with the x-axis. I'd like the x-axis to have ticks and labels only at the first of each month. It's as if d3.timeMonth thinks every data point is a new month:
I've set up the x-axis as scaleBand, because every time I tried to set it up as scaleTime, the bars displayed as huge overlapping bars. However, just before I set up the x-axis, I've printed my data to the console log, and it looks correctly formatted as Dates.
const data = [
{
date_facet: '2020-08-31',
published: 2,
not_published: 0,
},
{
date_facet: '2020-09-01',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-02',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-03',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-04',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-05',
published: 0,
not_published: 0,
},
];
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 80,
left: 40
},
width = 450 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#graph")
.append("svg")
.attr("viewBox", '0 0 450 350')
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// format the data
data.forEach(function(d) {
d.date_facet = parseTime(d.date_facet);
d.published = +d.published;
});
// order the data
data.sort(function(a, b) {
return a["date_facet"] - b["date_facet"];
})
// X axis
var x = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.date_facet;
}))
.padding(0.2);
// Y axis
var y = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.published);
}) + 4]);
// Add X axis, ticks and labels
svg.append("g")
.attr("class", "axis axis-minor")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
svg.append("g")
.call(d3.axisLeft(y));
// Bars
svg.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.date_facet);
})
.attr("width", x.bandwidth())
.attr("height", function(d) {
return height - y(d.published);
})
.attr("y", function(d) {
return y(d.published);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="graph"></div>
Because you use scaleBand, all values are considered categorical. What I mean by that is that they're like labels, like "ball", "orange", "circle". Just terms, completely unrelated to each other. This contrasts with time or numbers, where you can say one value is bigger than the other, or one is closer to A than to B.
Change the values to scaleTime instead:
const data = [
{
date_facet: '2020-08-31',
published: 2,
not_published: 0,
},
{
date_facet: '2020-09-01',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-02',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-03',
published: 1,
not_published: 0,
},
{
date_facet: '2020-09-04',
published: 0,
not_published: 0,
},
{
date_facet: '2020-09-05',
published: 0,
not_published: 0,
},
];
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 80,
left: 40
},
width = 450 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#graph")
.append("svg")
.attr("viewBox", '0 0 450 350')
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// format the data
data.forEach(function(d) {
d.date_facet = parseTime(d.date_facet);
d.published = +d.published;
});
// order the data
data.sort(function(a, b) {
return a["date_facet"] - b["date_facet"];
})
// Extend the domain by 12 hours on each side to account for the bar widths
var xDomain = d3.extent(data.map(function(d) {
return d.date_facet;
}));
// Deep copy the date objects to make sure you can make safe modifications
xDomain = [new Date(xDomain[0]), new Date(xDomain[1])];
xDomain[0].setHours(xDomain[0].getHours() - 12);
xDomain[1].setHours(xDomain[1].getHours() + 12);
// X axis
var x = d3.scaleTime()
.range([0, width])
.domain(xDomain);
var xDomainInDays = (x.domain()[1] - x.domain()[0]) / (1000 * 60 * 60 * 24);
var xBarWidth = width / xDomainInDays;
var padding = 0.2;
// Y axis
var y = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.published);
}) + 4]);
// Add X axis, ticks and labels
svg.append("g")
.attr("class", "axis axis-minor")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth.every(1))
.tickFormat(d3.timeFormat("%b")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
svg.append("g")
.call(d3.axisLeft(y));
// Bars
svg.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
// Get the x coordinate
// Then shift by half of xBarWidth so the middle of the bar is at the tick
// Then apply half of the padding (other half at the other side)
return x(d.date_facet) - (xBarWidth / 2) + (padding / 2) * xBarWidth;
})
// Make the bar "padding * xBarWidth" thinner so it applies the padding correctly
.attr("width", xBarWidth - padding * xBarWidth)
.attr("height", function(d) {
return height - y(d.published);
})
.attr("y", function(d) {
return y(d.published);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="graph"></div>
This does come with some complications.
You need to calculate the bar width yourself. I did this by checking the size of the domain and the size of the range, so I found the width available to each bar that way;
The tick will be at the left edge of each bar. If you want to centre it (which I did here), you need to play with padding and centre the bar on the tick;
Now, the bar will exceed the axis by a bit. You can make the domain 12 hours larger in both directions, this solves the issue.
My making progress with my graph, it seems to mostly work. However I'm struggling to put my data values on the x axis. In this case, there should be 3 x axis labels, (test1, test2,test3).
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
It seems to just label it by how many entries there are (0,1,2) rather than using the name. What I tried was changing this:
var line = d3.line()
.x(function(d, i) {
return xScale(i);
To this (which I must admit was a bit of a guess).
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
Unfortunately that didn't work and I'm not sure what I can try next. Here is the full code if that helps.
http://jsfiddle.net/spadez/cfz3g4w2/
You are using the wrong scale for your x data. You have discrete data and want an ordinal scale.
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input is an array of names
.range([0, width]); // output
Running code:
// Data
var dataset = [{
name: "test1",
y: 0.1
},
{
name: "test2",
y: 0.6
},
{
name: "test3",
y: 0.9
}
];
// Count number of datapoints
var n = Object.keys(dataset).length;
// Find max of the data points for Y axis
var mymax = Math.max.apply(Math, dataset.map(function(o) {
return o.y;
}));
// 2. Use the margin convention practice
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
},
width = window.innerWidth - margin.left - margin.right;
height = window.innerHeight - margin.top - margin.bottom;
// 5. X scale will use the index of our data
var xScale = d3.scalePoint()
.domain(dataset.map(d => d.name)) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, mymax]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) {
return xScale(d.name);
}) // 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
// 1. Add the SVG to the page and employ #2
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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) {
return xScale(d.name)
})
.attr("cy", function(d) {
return yScale(d.y)
})
.attr("r", 5)
.on("mouseover", function(a, b, c) {
console.log(a)
this.attr('class', 'focus')
})
.on("mouseout", function() {})
.on("mousemove", mousemove);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(d);
}
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
.overlay {
fill: none;
pointer-events: all;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #ffab00;
stroke: #fff;
}
.focus circle {
fill: none;
stroke: steelblue;
}
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
To start, I am fairly new to D3.Js. I have spent the past week or so working on a D3.JS issue-specifically making a graph with a Y-axis label. However, I cannot get the graph exactly how I want. It is almost there but inverted or my data comes out wrong. Now I will briefly show some of my code and images of my main problem before showing all of the code. I have spent time looking at other Stack Overflow posts with a similar issue and I do what is on those posts and still have the same issue.
For example, I thought that this post would have the solution: reversed Y-axis D3
The data is the following:
[0,20,3,8] (It is actually an array of objects but I think this may be all that is needed.
So, to start, when the yScale is like this:
var yScale = d3.scaleLinear()
.domain([0, maxPound]) //Value of maxpound is 20
.range([0, 350]);
The bar chart looks like this:
As one can see the Y chart starts with zero at the top and 20 at the bottom-which at first I thought was an easy fix of flipping the values in the domain around to this:
var yScale = d3.scaleLinear()
.domain([0, maxPound]) //Value of maxpound is 20
.range([0, 350]);
I get this image:
In the second image the y-axis is right-20 is on top-Yay! But the graphs are wrong. 0 now returns a value of 350 pixels-the height of the SVG element. That is the value that 20 should be returning! If I try to switch the image range values, I get the same problem!
Now the code:
var w = 350;
var h = 350;
var barPadding = 1;
var margin = {top: 5, right: 200, bottom: 70, left: 25}
var maxPound = d3.max(poundDataArray,
function(d) {return parseInt(d.Pounds)}
);
//Y-Axis Code
var yScale = d3.scaleLinear()
.domain([maxPound, 0])
.range([0, h]);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Creating SVG element
var svg = d3.select(".pounds")
.append('svg')
.attr("width", w)
.attr('height', h)
.append("g")
.attr("transform", "translate(" + margin.left + "," +
margin.top + ")");
svg.selectAll("rect")
.data(poundDataArray)
.enter()
.append("rect")
.attr('x', function(d, i){
return i * (w / poundDataArray.length);
})
.attr('y', function(d) {
return 350 - yScale(d.Pounds);
})
.attr('width', (w / 4) - 25)
.attr('height', function(d){
return yScale(d.Pounds);
})
.attr('fill', 'steelblue');
//Create Y axis
svg.append("g")
.attr("class", "axis")
.call(yAxis);
Thank you for any help! I believe that the error may be in the y or height values and have spent time messing around there with no results.
That is not a D3 issue, but an SVG feature: in an SVG, the origin (0,0) is at the top left corner, not the bottom left, as in a common Cartesian plane. That's why using [0, h] as the range makes the axis seem to be inverted... actually, it is not inverted: that's the correct orientation in an SVG. By the way, HTML5 Canvas has the same coordinates system, and you would have the same issue using a canvas.
So, you have to flip the range, not the domain:
var yScale = d3.scaleLinear()
.domain([0, maxPound])
.range([h, 0]);//the range goes from the bottom to the top now
Or, in your case, using the margins:
var yScale = d3.scaleLinear()
.domain([0, maxPound])
.range([h - margin.bottom, margin.top]);
Besides that, the math for the y position and height is wrong. It should be:
.attr('y', function(d) {
return yScale(d.Pounds);
})
.attr('height', function(d) {
return h - margin.bottom - yScale(d.Pounds);
})
Also, as a bonus tip, don't hardcode the x position and the width. Use a band scale instead.
Here is your code with those changes:
var poundDataArray = [{
Pounds: 10
}, {
Pounds: 20
}, {
Pounds: 5
}, {
Pounds: 8
}, {
Pounds: 14
}, {
Pounds: 1
}, {
Pounds: 12
}];
var w = 350;
var h = 350;
var barPadding = 1;
var margin = {
top: 5,
right: 20,
bottom: 70,
left: 25
}
var maxPound = d3.max(poundDataArray,
function(d) {
return parseInt(d.Pounds)
}
);
//Y-Axis Code
var yScale = d3.scaleLinear()
.domain([0, maxPound])
.range([h - margin.bottom, margin.top]);
var xScale = d3.scaleBand()
.domain(d3.range(poundDataArray.length))
.range([margin.left, w - margin.right])
.padding(.2);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Creating SVG element
var svg = d3.select("body")
.append('svg')
.attr("width", w)
.attr('height', h)
.append("g")
.attr("transform", "translate(" + margin.left + "," +
margin.top + ")");
svg.selectAll("rect")
.data(poundDataArray)
.enter()
.append("rect")
.attr('x', function(d, i) {
return xScale(i);
})
.attr('y', function(d) {
return yScale(d.Pounds);
})
.attr('width', xScale.bandwidth())
.attr('height', function(d) {
return h - margin.bottom - yScale(d.Pounds);
})
.attr('fill', 'steelblue');
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
The last ticks in x-axis and y-axis don't have values drawn next to them. I cannot figure out why these values are missing.
This is the JS code :
var xAxisScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.x; })])
.range([padding, containerWidth - padding * 2]);
var xAxis = d3.svg.axis().scale(xAxisScale).orient("bottom");
var xGuide = chartContainer.append('g')
.attr("class", "xAxis")
.call(xAxis)
.attr('transform', 'translate(0 ,' + (containerHeight -padding) + ')');
/* Code for adding y axis in chart
*
* */
var yAxisScale = d3.scale.linear()
.domain([0, d3.max(data, function(d){return d.y})])
.range([containerHeight-padding, padding ]);
var yAxis = d3.svg.axis().scale(yAxisScale).orient("left");
var yGuide = chartContainer.append('g')
.attr("class", "yAxis")
.call(yAxis)
.attr('transform', 'translate('+padding + ',0)');
This is my live demo.
This can be done by overriding the default behavior for determining tick values using axis.tickValues():
var yAxis = d3.svg.axis().scale(yAxisScale).orient("left")
.tickValues(yAxisScale.ticks().concat(yAxisScale.domain()));
This still resorts to the automatically generated tick values by calling yAxisScale.ticks() which is the default behavior for D3's axes. These values are then supplemented with the outer bounds of your data values, i.e. the array returned by yAxisScale.ticks(). To set just the upper bound, if would be sufficient to specify yAxisScale.domain()[1], although it won't hurt having duplicate values in the array provided to .tickValues().
Doing it this way frees you from any hardcoding of tick values.
Have a look at this working example:
var padding = 70;
//Width and height
var containerWidth = 1000;
var containerHeight = 500;
var data = [{
"x": 82,
"y": 1730
}, {
"x": 533,
"y": 16385
}, {
"x": 41,
"y": 783
}, {
"x": 20.5,
"y": 5873
}, {
"x": 553.5,
"y": 25200
}, {
"x": 61.5,
"y": 30000
}, {
"x": 184.5,
"y": 2853
}, {
"x": 1476,
"y": 83775
}];
//Create scale functions
var xScale = d3.scale.linear()
var chartContainer = d3.select("body")
.append("svg")
.attr("class", "chartContainer")
.attr("width", containerWidth)
.attr("height", containerHeight);
$(".chartContainer").css({
"background-color": "",
"position": "absolute",
"top": 50,
"left": 15
});
var xAxisScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.x; })])
.range([padding, containerWidth - padding * 2]);
var xAxis = d3.svg.axis().scale(xAxisScale).orient("bottom")
.tickValues(xAxisScale.ticks().concat(xAxisScale.domain()));
var xGuide = chartContainer.append('g')
.attr("class", "xAxis")
.call(xAxis)
.attr('transform', 'translate(0 ,' + (containerHeight -padding) + ')');
xGuide.selectAll('path')
.style({ fill: 'none', stroke: "#000"});
xGuide.selectAll('line')
.style({ stroke: "#000"});
/* Code for adding y axis in chart
*
* */
var yAxisScale = d3.scale.linear()
.domain([0, d3.max(data, function(d){return d.y})])
.range([containerHeight-padding, padding ]);
var yAxis = d3.svg.axis().scale(yAxisScale).orient("left")
.tickValues(yAxisScale.ticks().concat(yAxisScale.domain()[1]));
var yGuide = chartContainer.append('g')
.attr("class", "yAxis")
.call(yAxis)
.attr('transform', 'translate('+padding + ',0)');
chartContainer.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return xAxisScale(d.x);
})
.attr("cy", function(d) {
return yAxisScale(d.y);
})
.attr("r", 4)
.attr("fill", "red")
.attr({
"z-index": "9999"
});
yGuide.selectAll('path')
.style({ fill: 'none', stroke: "#000"});
yGuide.selectAll('line')
.style({ stroke: "#000"});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Add .nice() to line 69 of your live demo. will solve the issue. See graph below for the end result.
For more details and explanation, please refer to this closed issue on the d3-scale library. mbostock commented on it on Dec 6, 2016 and provided the solution.
You are using d3's automatic tick generator which makes a best guess on how best to draw your axes given the data in the chart.
You have two options for overriding this:
Setting a number of tick values to show. For example:
var xAxis = d3.svg.axis()
.scale(xAxisScale)
.orient("bottom")
.ticks(10);
Passing a tick values array to explicitly tell d3 what values you want on your x or y axis; like this:
var xAxis = d3.svg.axis()
.scale(xAxisScale)
.orient("bottom")
.tickValues([0, 100, 200, 300, 400]);
If you want to get rid of the ticks that are missing values (in your case, the outer ticks) just set .outerTickSize(0) on the x or y-axis.
You should also probably read up on the d3's axes API here.
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.