I am trying to use log scale for y-axis on my line chart.
Here is my code:
var yScale_for_axis = d3.scaleLog().domain([1,d3.max(vals)]).range ([height,0]);
g.append("g")
.call(d3.axisLeft(yScale_for_axis).tickFormat( d3.format(".1e"));
The ticks are getting overlapped with each other. Heres how it looks:
What should I do to make it look like this?
Look at the snippet - it seems to be working. Maybe it's tick formatting which makes ticks overlap:
const width = 400, height = 500;
// Append SVG
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create scale
const scale = d3.scaleLog()
.domain([1, 5000])
.range([20, height - 20]);
// Add scales to axis
const yAxis = d3.axisLeft()
.scale(scale);
//Append group and insert axis
svg.append("g")
.attr('transform', 'translate(150, 0)')
.call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Related
I have 2 buttons that i want to use to control what data set I am using for my bar chart. Right now I can click on one and it shows my d3 graph without problems. But when I want to switch to the other graph, I click on the button and it shows me that graph on top of my previous graph. How do I make it so that when I switch between graphs, it only shows me one graph.
var djockey = 'top5jockey.csv'
var dtrainer = 'top5trainer.csv'
// Define SVG area dimensions
var svgWidth = 1500;
var svgHeight = 1000;
// Define the chart's margins as an object
var chartMargin = {
top: 30,
right: 30,
bottom: 130,
left: 30
};
// Define dimensions of the chart area
var chartWidth = svgWidth - chartMargin.left - chartMargin.right;
var chartHeight = svgHeight - chartMargin.top - chartMargin.bottom;
// Select body, append SVG area to it, and set the dimensions
var svg = d3.select("body")
.append("svg")
.attr("height", svgHeight)
.attr("width", svgWidth);
// Append a group to the SVG area and shift ('translate') it to the right and to the bottom
var chartGroup = svg.append("g")
.attr("transform", `translate(${chartMargin.left}, ${chartMargin.top})`);
var btnj = document.getElementById("Jockey")
btnj.addEventListener('click', function(e){
change(e.target.id)
})
var btnt = document.getElementById("Trainer")
btnt.addEventListener('click', function(e){
change(e.target.id)
})
function change(value){
if(value === 'Jockey'){
update(djockey);
}else if(value === 'Trainer'){
update(dtrainer);
}
}
function update(data){
d3.csv(data).then(function(data) {
console.log(data);
// Cast the hours value to a number for each piece of tvData
data.forEach(function(d) {
d.Count = +d.Count;
});
// Configure a band scale for the horizontal axis with a padding of 0.1 (10%)
var xScale = d3.scaleBand()
.domain(data.map(d => d.Name))
.range([0, chartWidth])
.padding(0.1);
// Create a linear scale for the vertical axis.
var yLinearScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.Count)])
.range([chartHeight, 0]);
// Create two new functions passing our scales in as arguments
// These will be used to create the chart's axes
var bottomAxis = d3.axisBottom(xScale);
var leftAxis = d3.axisLeft(yLinearScale).ticks(10);
// Append two SVG group elements to the chartGroup area,
// and create the bottom and left axes inside of them
chartGroup.append("g")
.call(leftAxis);
chartGroup.append("g")
.attr("class", "x_axis")
.attr("transform", `translate(0, ${chartHeight})`)
.call(bottomAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Create one SVG rectangle per piece of tvData
// Use the linear and band scales to position each rectangle within the chart
chartGroup.selectAll("#bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => xScale(d.Name))
.attr("y", d => yLinearScale(d.Count))
.attr("width", xScale.bandwidth())
.attr("height", d => chartHeight - yLinearScale(d.Count));
}).catch(function(error) {
console.log(error);
})
};
D3 has a function allowing you to remove all svg elements. Basically, you select the svg, then run .remove() at the top of your event listener. It will clear out all svg elements.
I am trying to draw an svg map from a topojson file located here. When I run the code below, I see a small red collection of g elements that is that map, but I'm not sure how to make it larger. I've tried doing projection.scale(100) but that does not work.
Here is a fiddle.
<svg width=500 height=500></svg>
async function run() {
const res = await fetch(
"https://rawcdn.githack.com/jasonicarter/toronto-geojson/0fb40bd54333bc3d397a26cf4f68abb1b6d94188/toronto_topo.json"
);
const jsondata = await res.json();
const width = 500;
const height = 500;
const neighbourhoods = topojson.feature(jsondata, jsondata.objects.toronto);
const projection = d3.geoAlbers().translate([width / 2, height / 2])
const svg = d3.select("svg")
svg
.append("g")
.selectAll("path")
.data(neighbourhoods.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection))
.attr("fill", "red")
.attr("stroke", "white");
console.log("done")
}
run();
Indeed, you have to use the scale and the translate properties to scale / center your map.
But d3.geoProjection also provides some convenience functions such as fitExtent and fitSize in order to fit the projection on one specific GeoJSON feature object.
As your dataset is containing many features, I propose to use topojson.mesh to obtain a unique object representing your whole dataset (as a mesh) to use its extent with the fitSize method of the projection to scale your map :
const neighbourhoods = topojson.feature(jsondata, jsondata.objects.toronto);
const mesh = topojson.mesh(jsondata, jsondata.objects.toronto);
const projection = d3.geoAlbers()
.fitSize([width, height], mesh);
const svg = d3.select("svg")
svg
.append('g')
.selectAll("path")
.data(neighbourhoods.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection))
.attr("fill", "red")
.attr("stroke", "white");
Which (after adding a border on the svg element) gives the following :
If you wanted to fit the extent using a some padding (lets say 20px) you could have use the following :
const projection = d3.geoAlbers()
.fitExtent([[20, 20], [width - 20, height - 20]], mesh);
I would like to create a vertical grid using D3.
I know I could use d3 axis methods (like axisBottom, axisLeft, ...) and set tickSize to manage the grid lines size but the axis generators create not only the lines but also the axis and the labels and I don't need them.
For example, this is what I could draw using axisBottom:
const container = d3.select('svg')
container.append('g').attr('class', 'vertical-grid')
const height = 100
const numberOfTicks = 5
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 200])
const xGridGenerator = d3.axisBottom(xScale)
.tickSize(height)
.ticks(numberOfTicks)
container
.select('.vertical-grid')
.attr('transform', `translate(${0}, ${0})`)
.call(xGridGenerator)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="app">
<svg></svg>
</div>
This is what I would like to draw:
I'm interested only in the red and blue lines, no labels, no axes.
I have the g container, the two scales and the number of ticks.
How can I do that?
Honestly I don't know how to start
You have a good start. A simple way to do the customization is to modify the axes' elements (labels, line colors, etc.) after you've added them. For example:
xAxis.selectAll('text').remove()
xAxis.selectAll('line').attr('stroke', 'blue')
Here's a complete example that renders a version of what you want (though the width/height are a little off because you'd want to have some margin):
const height = 200
const width = 200
const margin = { top: 10, bottom: 10, left: 10, right: 10 }
const container = d3.select('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
container.append('g').attr('class', 'vertical-grid')
container.append('g').attr('class', 'horizontal-grid')
const numberOfTicks = { x: 8, y: 4 }
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, width - margin.left - margin.right ])
const xGridGenerator = d3.axisBottom(xScale)
.tickSize(height - margin.top - margin.bottom)
.ticks(numberOfTicks.x)
const xAxis = container
.select('.vertical-grid')
.attr('transform', `translate(${0}, ${0})`)
.call(xGridGenerator)
const yScale = d3.scaleLinear()
.domain([0, 100])
.range([0, height - margin.top - margin.bottom ])
const yGridGenerator = d3.axisRight(yScale)
.tickSize(width - margin.left - margin.right)
.ticks(numberOfTicks.y)
const yAxis = container
.select('.horizontal-grid')
.attr('transform', `translate(${0}, ${0})`)
.call(yGridGenerator)
// Customize
xAxis.selectAll('text').remove()
xAxis.selectAll('line').attr('stroke', 'blue')
yAxis.selectAll('text').remove()
yAxis.selectAll('line').attr('stroke', 'red')
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="app">
<svg></svg>
</div>
Is there an efficient way to get the path string of an axis generated in D3 from an axis generator function e.g. below as I need to get the intersection of a path added to the chart later on that bisects the X axis,
var xScale = d3.scaleLinear().domain([0,10]).range([0,width]);
var xAxis = g.append("g")
.attr("class","x-axis-right")
.attr("transform", `translate(0, ${height})`)
.style("fill","blue")
.style("stroke","none")
var xAxisCall = d3.axisBottom(xScale);
xAxis.transition(t).call(xAxisCall);
It makes little sense to me why would you want the d attribute, but anyway, this is how to do it:
In D3, getters are pretty much methods without the second argument. So, if this sets the d attribute...
selection.attr("d", foo);
... this gets it:
selection.attr("d");
Therefore, all you need is to select the desired path, which in your case would be:
xAxis.select("path").attr("d");
Here is the demo, look at the console:
var width = 500,
height = 100;
var svg = d3.select("svg");
var xScale = d3.scaleLinear().domain([0, 10]).range([10, width - 10]);
var xAxis = svg.append("g")
.attr("class", "x-axis-right")
.attr("transform", `translate(0, ${height - 30})`)
.style("fill", "blue")
.style("stroke", "none")
var xAxisCall = d3.axisBottom(xScale);
xAxis.call(xAxisCall);
var pathString = xAxis.select("path").attr("d");
console.log(pathString)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="100"></svg>
I encountered a problem when using d3.brush.the problem
The graph is outside the limit.
related code(var xScale is for the bigger graph and xScale_ is for the graph which has a brush):
var template = {
width: '1200',
height: '520',
padding: 20,
xScaleTick: 50,
yScaleTick: 20
};
function initScale(dataset) {
xScale = d3
.scaleLinear()
.domain([1, dataset[dataset.length - 1]['chapter']])
.range([template.padding, template.width - 2*template.padding]);
xAxis = d3
.axisBottom()
.scale(xScale)
.ticks(template.xScaleTick);
}
xScale_ = d3
.scaleLinear()
.domain([1, dataset[dataset.length - 1]['chapter']])
.range([template.padding, template.width - 2 * template.padding]);
xAxis_ = d3
.axisBottom()
.scale(xScale_)
.ticks(template.xScaleTick);
var brush = d3
.brushX()
.extent([[xScale_(1), 0], [timeline.width, timeline.height]])
.on('brush', brushed);
$timeline
.append('g')
.attr('class', 'brush')
.call(brush)
.call(brush.move, xScale_.range().map(value => value / 2));
function brushed() {
var s = d3.event.selection || xScale_.range();
var smap = s.map(xScale_.invert,xScale_);
xScale.domain(smap).nice();
xAxis = d3
.axisBottom()
.scale(xScale)
.ticks(template.xScaleTick);
$chart
.selectAll('g.area')
.select('path')
.attr('d', area)
.attr('transform', 'translate(' + template.padding + ',0)');
$chart.select('g.x').call(xAxis);
}
I think maybe I have made the graph's padding in a wrong way,but I don't know how to fix it.
Thanks for any help.Or any related examples will be helpful.
See this example: Zoomable Area
You can define a clipPath to crop the graph to the area you wish. Code from the example:
var areaPath = g.append("path")
.attr("clip-path", "url(#clip)")
.attr("fill", "steelblue");
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
I don't know if there is a better solution. I just appended a "rect" to overlap the part out of the limit.
Sorry for my poor English.