I need to design a d3 component like the one shown in the figure below.
I referred to an existing code sample from this link, and modified it to create something like this.
Left was changing the width of the axis, which I tried by changing the stroke-width property of the domain class. However, I ended with something like this.
Problems:
The slider handle isn't aligning with the axis.
The axis color imprints on the slider.
The ends of the axis are not perfectly round.
Questions:
I can't figure out what do I translate/transform to align the sliders and the axis.
I tried fiddling around with the opacity values, but didn't help.
I set stroke-linecap to round, but it's still not completely round.
I am using d3 v4 for this. And the jsfiddle for my final code is here.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick{
visibility:hidden;
}
.domain {
stroke: grey;
stroke-width:10px;
stroke-linecap: round;
}
.selection {
fill:red
}
</style>
</head>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 15;
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0,100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([[0,0], [width,height]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
.call(d3.axisBottom()
.scale(x)
.tickSize(0));
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
console.log('range->'+range)
d3.selectAll("span")
.text(function(d, i) {
console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
</body>
</html>
The axis and the brush are actually perfectly aligned!
You can see this if you set the stroke-width to 1px:
.as-console-wrapper { max-height: 30% !important;}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick{
visibility:hidden;
}
.domain {
stroke: grey;
stroke-width:1px;
stroke-linecap: round;
}
.selection {
fill:red
}
</style>
</head>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 15;
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0,100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([[0,0], [width,height]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
.call(d3.axisBottom()
.scale(x)
.tickSize(0));
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
console.log('range->'+range)
d3.selectAll("span")
.text(function(d, i) {
console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
</body>
</html>
So, what's happening here? The issue is that when you tell the browser to take a line (in this case it's a path, but it doesn't matter) and increase its stroke to, let's say, 100 pixels, it will increase 50 pixels to one side and 50 pixels to the other side. So, the middle of that thick axis is right on the top of the brush's rectangle.
There are several solutions here, like drawing an rectangle. If, however, you want to keep your approach of increasing the .domain stroke-width, let's break the selections and move the axis half its stroke-width down (here I'm increasing the width to 20 pixels, so it's easier to see the alignment):
.as-console-wrapper { max-height: 30% !important;}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<!--
axes and brushes are styled out of the box,
so this is no longer needed
<style>
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
-->
<style>
.tick {
visibility: hidden;
}
.domain {
stroke: grey;
stroke-width: 20px;
stroke-linecap: round;
}
.selection {
fill: red
}
</style>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
</body>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 20;
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0, 100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([
[0, 0],
[width, height]
])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100);
svg.append("g")
.attr("transform", "translate(" + margin + "," + (margin + 10) + ")")
.call(d3.axisBottom()
.scale(x)
.tickSize(0));
var brushg = svg.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
console.log('range->' + range)
d3.selectAll("span")
.text(function(d, i) {
console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
The path in the axis is a closed shape and stroking that gives problems. Also you don't want ticks so why not draw the "axis" yourself. Then the round edge will be drawn correct.
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
// .call(d3.axisBottom()
// .scale(x)
// .tickSize(0))
;
svg.append("path")
.attr("class", "domain")
.attr("d", `M${x(0)},0 ${x(100)},0`);
You have to match the brush extent to the stroked path surface
var margin = 20,
width = 400 - margin * 2,
height = 10; // same as stroke width
var brush = d3.brushX()
.extent([[0,-height*0.5], [width,height*0.5]])
.on("brush", brushed);
The dy attribute has no purpose
//d3.selectAll("rect").attr("dy", "-5em")
Set the fill-opacity of the selection
.selection {
fill:red;
fill-opacity: 1;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick{
visibility:hidden;
}
.domain {
stroke: grey;
stroke-width:10;
stroke-linecap: round;
}
.selection {
fill:red;
fill-opacity: 1;
}
</style>
</head>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 10; // same as stroke width
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0,100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([[0,-height*0.5], [width,height*0.5]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
// .call(d3.axisBottom()
// .scale(x)
// .tickSize(0))
;
svg.append("path")
.attr("class", "domain")
.attr("d", `M${x(0)},0 ${x(100)},0`);
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
//console.log('range->'+range)
d3.selectAll("span")
.text(function(d, i) {
//console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
//d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
</body>
</html>
Related
When I zoom in on my d3 line chart, I can't get daily ticks to display. The lowest tick size that will show up are weekly ticks. Maybe my domain is incorrect? Or I need to use transform.rescaleX() like shown here https://bl.ocks.org/stepheneb/29134b7a51a1ede49f6fc57c5a2a5f38
https://jsfiddle.net/urb1qvch/1/
Javascript
// Prep data
var x = d3.timeDays(new Date(2010, 06, 01), new Date(2020, 10, 30));
var y = Array.from({length: x.length}, Math.random).map(n => Math.floor(n * 10) + 5);
var data = x.map((v, i) => {
return {
"x": v,
"y": y[i]
}
});
// Use the margin convention practice
var margin = {top: 50, right: 50, bottom: 50, left: 50}
var width = 600 - margin.left - margin.right // Use the window's width
var height = 400 - margin.top - margin.bottom; // Use the window's height
// zoom function for d3v6
// adapt to v6 from this v5 block https://bl.ocks.org/LemoNode/7ac1d41fe75fe7d2d9cb85e78aad6303
var zoom = d3.zoom()
.on('zoom', (event) => {
xScale
.domain(event.transform.rescaleX(xScale2).domain())
.range([0, width].map(d => event.transform.applyX(d))); //
svg.select(".line")
.attr("d", line); // zooms line
svg.select(".x-axis")
.call(d3.axisBottom(xScale)
.tickSizeOuter(0)
.ticks(20)); // zooms axis
})
.scaleExtent([1, 32]); // adjust 32 to less zoom less
// X scale - use min and max of scale
var xScale = d3.scaleUtc()
.domain([d3.min(x), d3.max(x)]) // input
.range([0, width]); // output
// constant reference point whilst zooming
var xScale2 = d3.scaleUtc()
.domain([d3.min(x), d3.max(x)]) // input
.range([0, width]); // output
// Y scale - add 5 for bit of whitespace at top of graph
var yScale = d3.scaleLinear()
.domain([0, d3.max(y) + 5]) // input
.range([height, 0]); // output
// d3's line generator
var line = d3.line()
.x(function(d) { return xScale(d.x); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
// Add the SVG to the page and employ #2
var svg = d3.select("#my_chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom) // call the zoom
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// clippath to stop line and x-axis 'spilling over'
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", 0)
.attr("width", width)
.attr("height", height);
// call x-axis and apply the clip from the defs
svg.append("g")
.attr("class", "x-axis")
.attr("clip-path", "url(#clip)") // add the clip path!
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// Call the y-axis
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// Append the path, bind the data, and call the line generator
svg.append("path")
.datum(data) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("clip-path", "url(#clip)") // add the clip path!
.attr("d", line); // 11. Calls the line generator
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
<h1>My Chart</h1>
<div id="my_chart"></div>
</body>
</html>
CSS
.line {
fill: none;
stroke: #ffab00;
stroke-width: 1.5;
}
.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;
}
Recently, maybe after updating Java, I'm unable to load a scatter plot on Chrome, Firefox or Internet Explorer.
One month ago, everything loaded fine. I don't know what happened. I've updated Java to the last version and enabled ActiveX on Internet options, but nothing works for me.
I'll paste the html code. It loads the point info using a csv file saved in the same folder:
<!DOCTYPE html> <meta charset="utf-8"> <style>
body { font: 12px Arial;}
.axis path, .axis line { fill: none; stroke: grey; stroke-width: 1;
shape-rendering: crispEdges; }
</style> <body>
<script src="http://d3js.org/d3.v4.js"></script> <script>
// Set the dimensions of the canvas / graph var margin = {top: 30, right: 20, bottom: 50, left: 60}, width = 900 - margin.left - margin.right, height = 360 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.timeParse("%a %b %d %H:%M:%S %Z %Y "); // Set the ranges
var y = d3.scaleTime().range([height, 0]); var x = d3.scaleLinear().range([0, width]);
// Define the axes
var xAxis = d3.axisBottom().scale(x) .ticks(5);
var yAxis = d3.axisLeft().scale(y)
.ticks(5);
// Adds the svg canvas 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 + ")"); // Get the data
d3.csv("data.csv", function(error, data) { var max = data.length; console.log(max)
data.forEach(function(d, i) { d.dateParsed = parseDate(d.date); d.close = max - i;
}); // Scale the range of the data
y.domain(d3.extent(data, function(d) { return d.dateParsed; })); x.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the scatterplot svg.selectAll("dot") .data(data)
.enter().append("circle") .attr("r", 0.5)
.attr("fill","#2980B9")
.attr("cy", function(d) { return y(d.dateParsed); }) .attr("cx", function(d) { return x(d.close); });
svg.append("g") .attr("class", "x axis")
.attr("transform", "translate(0," + height + ")") .call(xAxis);
svg.append("text") .attr("class", "x label") .attr("text-anchor", "middle") .attr("x", width / 2) .attr("y", height + 40) .text("followers");
svg.append("g")
.attr("class", "y axis") .call(yAxis);
svg.append("text") .attr("class", "y label") .attr("text-anchor", "middle") .attr("x", -height / 2 ) .attr("y", -50)
.attr("transform", "rotate(-90)") .text("account creation date");
});
</script> </body>
It looks like main issue is code is not formatted properly.
Many comments get mixed with the code caused the syntax errors.
I try to format the code and try to fix those errors.
As we don't have your data.csv file so we are not able to load that data but code creates the chart now.
You can try to use this code on your side with your data.csv file. It will help you to load the chart properly.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font: 12px Arial;}
.axis path, .axis line { fill: none; stroke: grey; stroke-width: 1;
shape-rendering: crispEdges; }
</style>
</head>
<body>
<script src="http://d3js.org/d3.v4.js"></script> <script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 50, left: 60}, width = 900 - margin.left - margin.right, height = 360 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.timeParse("%a %b %d %H:%M:%S %Z %Y "); // Set the ranges
var y = d3.scaleTime().range([height, 0]); var x = d3.scaleLinear().range([0, width]);
// Define the axes
var xAxis = d3.axisBottom().scale(x) .ticks(5);
var yAxis = d3.axisLeft().scale(y)
.ticks(5);
// Adds the svg canvas
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 + ")"); // Get the data
d3.csv("data.csv", function(error, data) { var max = data.length;
console.log(max);
data.forEach(function(d, i) { d.dateParsed = parseDate(d.date); d.close = max - i;
});
// Scale the range of the data
y.domain(d3.extent(data, function(d) { return d.dateParsed; })); x.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the scatterplot
svg.selectAll("dot") .data(data)
.enter().append("circle") .attr("r", 0.5)
.attr("fill","#2980B9")
.attr("cy", function(d) { return y(d.dateParsed); }) .attr("cx", function(d) { return x(d.close); });
svg.append("g") .attr("class", "x axis")
.attr("transform", "translate(0," + height + ")") .call(xAxis);
svg.append("text") .attr("class", "x label") .attr("text-anchor", "middle") .attr("x", width / 2) .attr("y", height + 40) .text("followers");
svg.append("g")
.attr("class", "y axis") .call(yAxis);
svg.append("text") .attr("class", "y label") .attr("text-anchor", "middle") .attr("x", -height / 2 ) .attr("y", -50)
.attr("transform", "rotate(-90)") .text("account creation date");
});
</script>
</body>
</html>
I am creating left axis and the current output is like this.
.
The problem is there is a gap between tick values but i want uniform gap between two tick values as here.
Here is the Code example.
svg.append("g")
.attr("class", "axisLeft")
.call(d3.axisLeft(y1).tickValues(y1TickValues).tickSizeOuter(0).tickFormat(d3.format("d")))
.selectAll('text')
.style('text-anchor', 'end');
What you're asking for is impossible. The reason is simple: a linear scale is a continuous scale. That is, it deals with a continuous (non-discrete) quantitative variable.
The only way for you to guarantee that the distance between the ticks is rigorously the same is using an ordinal scale, but those scales deal with qualitative (categorical) variables. Not what you want.
However, there is a hack: using a log scale. In this case, since your domain crosses zero, well use a symlog scale (avoiding the log of zero, which in math is not a real number), available on D3 v5 (not v4, the version you're using). By using a symlog scale with constant(100)...
var y1 = d3.scaleSymlog()
.constant(100)
.domain([0,2000]).range([height,0]);
... we get something similar (but not exactly like) to what you asked:
Here is the updated code:
(function(window){
var graphData = [1699, 725, 1149, 868, 304, 1844, 745, 1846, 1423, 1739, 823, 1404, 226, 1603, 389, 517, 1452, 1842, 930, 547, 1072, 828, 733, 632];
var timeArr = [];
for (var i=0;i<24;i++) {
timeArr.push(i);
}
function trans(key){
return key;
}
drawEditGraph();
function drawEditGraph() {
var dataGraph = { timeArr:timeArr, graphData:graphData};
function make_x_gridlines() {
return d3.axisBottom(x).tickSize(height).tickValues(xTicks)
.ticks(10)
}
var margin = {top: 35, right: 50, bottom: 30, left: 50},
width = $(window).width() - margin.left - margin.right,
height = $(window).height() - margin.top - margin.bottom;
var svgHeight = height + 40;
var x = d3.scaleLinear().range([0, width]);
var tickValues= [0,4,8,12,16,20,24];
var y1TickValues = [20,50,75,100,150,200,300,400,500,750,1000,1500,2000]
x.domain([0,23]);
var y1 = d3.scaleSymlog()
.constant(100)
.domain([0,2000]).range([height,0]);
var xTicks = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]
var valueline2 = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y1(d.open); });
var svg = d3.select("#graphDiv").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", svgHeight + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var data = [];
for (var i=0;i<dataGraph.timeArr.length;i++){
var obj = {};
obj.date = dataGraph.timeArr[i];
obj.open = dataGraph.graphData[i];
data.push(obj)
}
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0,"+(height)+")")
.call(make_x_gridlines()
.tickSize(-width)
.tickSizeOuter(0)
.tickFormat("")
)
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline2);
// Add the X Axis
svg.append("g")
.attr("class", "axisBottom")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickValues(xTicks).tickFormat(function(d,i){
if (d<10)
return "0"+d;
return d;
}));
// Add the Y Axis
svg.append("g")
.attr("class", "axisLeft")
.call(d3.axisLeft(y1).tickValues(y1TickValues).tickSizeOuter(0).tickFormat(d3.format("d")))
.selectAll('text')
.style('text-anchor', 'end');
//Add title
svg.append("text")
.attr("text-anchor", "center")
.attr("x", (width/2) - 25)
.attr("y", height + 35 )
.attr("fill", "#8E8E8E")
.attr("font-size", "12")
.text(trans("Time"));
// Y0 axis label:
svg.append("text")
.attr("text-anchor", "end")
.attr("transform", "rotate(0)")
.attr("y", -23)
.attr("x", 5)
.attr("font-size", "12")
.attr("fill", "#725100")
.text(trans("Colour"));
svg.append("text")
.attr("text-anchor", "end")
.attr("transform", "rotate(0)")
.attr("y", -8)
.attr("x", 5)
.attr("font-size", "12")
.attr("fill", "#725100")
.text("("+trans("K") + ")");
}
}(window));
.line {
fill: none;
stroke: #FFC841 ;
stroke-width: 2px;
}
.axisSteelBlue text{
fill: #FFC841;
}
.axisRed text{
fill: #5BCBD4;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Graph Demo">
<meta name="viewport" content="width=device-width">
<title>Graph Demo</title>
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.2/d3.min.js"></script>
</head>
<body>
<div id="graphDiv">
</div>
</body>
</html>
Hi everyone i have been following some youtube tutorials on making bar charts in D3.js and i seemed to have goofed up a few things as my output went haywire
Here is my html file
<!DOCTYPE html>
<html>
<head>
<!-- meta -->
<meta charset="utf-8">
<title>My Data Record</title>
<!-- CSS stylesheet -->
<link rel="stylesheet" type="text/css" href="stylesheet.css">
<!-- D3.js CDN source -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
</head>
<body>
<!-- Title -->
<h1 style="text-align:center;">Monthly Dispensed Amount</h1>
<!-- Your D3 code for bar graph -->
<script type="text/javascript" src="gdpBarGraph.js"></script>
</body>
</html>
and here is my javascript file
var margin = {top: 20, right: 10, bottom: 100, left:50},
width = 700 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr ({
"width": width + margin.right + margin.left,
"height": height + margin.top + margin.bottom
})
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.right + ")");
// defining x and y scales
var xScale = d3.scale.ordinal()
.rangeRoundBands([0,width], 0.2, 0.2);
var yScale = d3.scale.linear()
.range([height, 0]);
// defining x axis and y axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
d3.csv("newcashdata.csv", function(error,data) {
if(error) console.log("Error: data could not be loaded!");
data.forEach(function(d) {
d.date = d.date;
d.amount = +d.amount;
console.log(d.amount);
});
// sort the values to show at which date the cash collection was the highest
data.sort(function(a,b) {
return b.amount - a.amount;
});
// Specify the domains of the x and y scales
xScale.domain(data.map(function(d) { return d.date; }) );
yScale.domain([0, d3.max(data, function(d) { return d.amount; } ) ]);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr("height", 0)
.attr("y", height)
.transition().duration(3000)
.delay( function(d,i) { return i * 200; })
.attr({
"x": function(d) { return xScale(d.date); },
"y": function(d) { return yScale(d.amount); },
"width": xScale.rangeBand(),
"height": function(d) { return height - yScale(d.amount); }
})
.style("fill", function(d,i) { return 'rgb(20, 20, ' + ((i * 30) + 100) + ')'});
svg.selectAll('text')
.data(data)
.enter()
.append('text')
.text(function(d){
return d.amount;
})
.attr({
"x": function(d){ return xScale(d.date) + xScale.rangeBand()/2; },
"y": function(d){ return yScale(d.amount)+ 12; },
"font-family": 'sans-serif',
"font-size": '13px',
"font-weight": 'bold',
"fill": 'white',
"text-anchor": 'middle'
});
// Drawing x axis and positioning the label
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".25em")
.attr("transform", "rotate(-60)" )
.style("text-anchor", "end")
.attr("font-size", "10px");
// Drawing y Axis and positioning the label
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height/2)
.attr("dy", "-2em")
.style("text-anchor", "middle")
.text("Amount Dispensed");
});
and lastly my stylesheet:
svg {
margin-left: auto;
margin-right: auto;
display: block;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text{
font: Times;
font-size: 20px;
font-weight: bold;
}
here's what i am getting as output:
as its evident i made a mess out of my Y axis label "Amount Dispensed" and i can't think of a way to change that is it because of my font size in stylesheet or some other mistake in my code, any help will be highly appreciated.
edit: here is my csv file
here is the output after right axis changes
The y axis label did came back however the numbers on individual bars seem to be too big to depict is there a way to shorten them for isntance say 950000 to 950K and likewise
Here is a full fiddle
I increased your margin.left to 75 from 50. I also modified your yAxis creation to fix scale number formatting the s will change numbers into their corresponding prefix (7000 to 7k etc.)
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left").tickFormat(d3.format("s"));
The data I used was just randomly created as the issues were just in axis formatting.
I also moved over the label you added to the yAxis from y: -2em to y: -3em
New to D3. I am trying to modify the simple bar chart example shown here. I'm trying to update the data but am missing something fundamental. I'm trying to follow along here, where Mike talks about object constancy. Specifically, I am trying to achieve the following in my code:
Key functions can be useful for improving performance independent of transitions. For example, if you filter a large table, you can use a key function to reduce the number of DOM modifications: reorder DOM elements in the update selection rather than regenerating them. We used this technique at Square to improve the performance of merchant analytics, and it’s one of the reasons that D3 is faster than most template frameworks.
(In my case, my key function is simply ".data(data)" (which is ok, according to this post)
My code below works, but I suspect isn't the most performance friendly. For instance, frequency "70" is in both sets of data, but by "removing" the data, I am effectively redrawing it. (If I don't "remove" the data first, then another chart is drawn, rather than the old chart just getting updated data). How do I modify the code below to adhere to the key function and so that data that exists in both datasets doesn't get redrawn?
My code for the bar chart:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<button id="change" name="change">Update</button>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js" type="text/javascript"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
$(document).ready(function () {
var old_data = [{"letter": "A","frequency": "50"},
{"letter": "B","frequency": "60"},
{"letter": "C","frequency": "70"}, // this also appears in new_data
{"letter": "D","frequency": "80"},
];
draw_chart(old_data);
$("#change").click(function(){
var new_data = [{"letter": "A","frequency": "10"},
{"letter": "B","frequency": "20"},
{"letter": "C","frequency": "70"}, // this appears in old_data
{"letter": "D","frequency": "30"},
];
var bar = d3.select('body').selectAll('svg').remove();// delete this line and you'll get multiple charts rather than just updating the data in the original chart
draw_chart(new_data);
});
});
</script>
<script>
function draw_chart(data){
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
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("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 + ")");
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", height)
.attr("height","0")
.transition()
.delay(function(d, i) { return i*300 })
.duration(1000)
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
}
</script>
First, the reason why you have the line,
var bar d3.select('body')...remove() // delete this line and you'll get double...
Is beacuse in your draw_chart your always appending to the page when it's called. You need to change this line,
var svg = d3.select("body").append("svg")
to something that doesn't continually append a new svg
If I have more time i'll take a look at the main question.