Related
I started the D3.js challenge on FreeCodeCamp, the problem is that I solved it with the chart but it only gives me a display on the rectum, only one with the width and height that it I put, I'll show the code below.
The entire code on
<script>
//set d3
var w = 1000, h = 500;
var padding = 50;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
//title
svg.append('text')
.attr('x', w / 2)
.attr('y', 50)
.text('United States GDP')
fetch('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((result)=>result.json())
.then((data)=>{
var the_data = data['data']
//get vals
var get_max = d3.max(data['data'])
var get_mix = d3.min(data['data'])
//for x
var max_x = Number(get_max[0].split('-')[0])
var min_x = Number(get_mix[0].split('-')[0])
//for y
var max_y = get_max[1]
var min_y = get_mix[1]
var xScale = d3.scaleLinear()
.domain([min_x, max_x])
.range([padding, w-padding])
var yScale = d3.scaleLinear()
.domain([min_y, max_y])
.range([h-padding, padding])
//the_chars
for(var i in the_data){
var get_year = Number(the_data[i][0].split('-')[0])
the_data[i][0] = get_year
}
svg.selectAll('rect')
.data(the_data)
.enter()
.append('rect')
.attr("x", (d) => { xScale(d[0]) })
.attr('y', (d)=>{ yScale(d[1]) })
.attr("width", 200)
.attr("height", 20)
//axis
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
//display axis
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append('g')
.attr('transform', 'translate(' + padding + ', 0)')
.call(yAxis)
})
Now, what I need to do to display the charts!
I mention that the script tags are embedded in the body
Problem: Arrow functions without a return value. Solution: Instead use an explicit or an implicit return.
.attr("x", (d) => { xScale(d[0]) }) // returns undefined
.attr("x", (d) => xScale(d[0])) // implicit return
.attr("x", (d) => { return xScale(d[0]) }) // explicit return
Problem: Fixed height value. Solution Evaluate the height of each based on the GDP value (d[1]) instead.
.attr('height', 20) // fixed height
.attr('height', d => yScale(min_y) - yScale(d[1]))
// subtract from min range to account for padding and inverted y coordinates in SVG
Full solution in this codepen
I want to make a visual that shows ordinal data (ratings). There are 12 rating dimensions, and each rating will have its own dedicated line appended to a circle. The polar orientation of the line designates a category (i.e. lines pointing to 1 o'clock = category 1, 2 o'clock = category 2, and so forth). The length of the line indicates the ratings value (short = bad, long = good). The result should resemble a snow flake or a sun burst.
The name is stored in a string. The ratings for each company are stored in an array. Here are two slices of my data variable:
{'fmc':'fmc1', 'ratings':[10,10,10,10,10,10,10,10,10,10,10,10]},
{'fmc':'fmc2', 'ratings':[8,10,10,5,10,10,10,10,10,7,10,5]},
I have the grid-system placement for the companies functioning, but there seems to be an issue with the way I'm aligning the lines about the circle. Relevant code:
var rotationDegree = d3.scalePoint().domain([0,12]).range([0, 2*Math.PI - Math.PI/6]);
fmcG.append('line')
.data([10,10,10,10,10,10,10,10,10,10,10,10])
.attr("x1", r)
.attr("y1", r)
.attr("x2", function(d,i) { return length(10) * Math.cos(rotationDegree(i) - Math.PI/2) + (width/2); })
.attr("y2", function(d,i) { return length(10) * Math.sin(rotationDegree(i) - Math.PI/2) + (height/2); })
.style("stroke", function(d) { return "#003366" });
It would seem that I have the trig mapped out correctly, but in implementation I am proven wrong: the lines are not being appended about the circle like a snow flake / sun burst / clock.
Snippet:
var margins = {top:20, bottom:300, left:30, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
//{'fmc':'fmc1', 'ratings':[{'r1':10,'r2':10,'r3':10,'r4':10,'r5':10}]}
{'fmc':'fmc1', 'ratings':[10,10,10,10,10,10,10,10,10,10,10,10]},
{'fmc':'fmc2', 'ratings':[8,10,10,5,10,10,10,10,10,7,10,5]},
{'fmc':'fmc3', 'ratings':[10,10,10,10,10,10,10,10,10,10,10,10]},
];
var r = 30;
var length = d3.scaleLinear().domain([0, 10]).range([0, 50]);
var rotationDegree = d3.scalePoint().domain([0,12]).range([0, 2*Math.PI - Math.PI/6]);
var columns = 5;
var spacing = 220;
var vSpacing = 250;
var fmcG = graphGroup.selectAll('.fmc')
.data(data)
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d,i) => 'fmc' + i)
.attr('transform', (d,k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate("+horSpace+","+vertSpace+")";
});
fmcG.append('circle')
.attr('cx',100)
.attr('cy',100)
.attr('r', r)
.style('fill','none')
.style('stroke','#003366');
fmcG.append('text')
.attr('x',100)
.attr('y',105)
.style('text-anchor','middle')
.text(function(d) {return d.fmc});
fmcG.append('line')
//.data(function(d) {return d.ratings}) why doesnt it workk??????
.data([10,10,10,10,10,10,10,10,10,10,10,10])
.attr("x1", r)
.attr("y1", r)
.attr("x2", function(d,i) { return length(10) * Math.cos(rotationDegree(i) - Math.PI/2) + (width/2); })
.attr("y2", function(d,i) { return length(10) * Math.sin(rotationDegree(i) - Math.PI/2) + (height/2); })
.style("stroke", function(d) { return "#003366" });
<script src="https://d3js.org/d3.v5.min.js"></script>
Question
How can I take an 12-item array and append lines about the circle in 30 degree increments (360 divided by 12) while using the value of each item in the array to determine the line's length?
The main issue is that, right now, you're appending a single line. For appending as many lines as data points you have to set up a proper enter selection:
fmcG.selectAll(null)
.data(function(d) {
return d.ratings
})
.enter()
.append('line')
//etc...
And that, by the way, is the reason your data is not working (as you ask in your comment "why doesnt it workk??????")
Other issues:
A point scale needs to have a discrete domain, for instance d3.range(12)
For whatever reason you're moving the circles 100px right and down. I'm moving the lines by the same amount.
Here is the snippet with those changes:
var margins = {
top: 20,
bottom: 300,
left: 30,
right: 100
};
var height = 600;
var width = 900;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(" + margins.left + "," + margins.top + ")");
var data = [
//{'fmc':'fmc1', 'ratings':[{'r1':10,'r2':10,'r3':10,'r4':10,'r5':10}]}
{
'fmc': 'fmc1',
'ratings': [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
},
{
'fmc': 'fmc2',
'ratings': [8, 10, 10, 5, 10, 10, 10, 10, 10, 7, 10, 5]
},
{
'fmc': 'fmc3',
'ratings': [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
},
];
var r = 30;
var length = d3.scaleLinear().domain([0, 10]).range([0, 50]);
var rotationDegree = d3.scalePoint().domain(d3.range(12)).range([0, 2 * Math.PI]);
var columns = 5;
var spacing = 220;
var vSpacing = 250;
var fmcG = graphGroup.selectAll('.fmc')
.data(data)
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
fmcG.append('circle')
.attr('cx', 100)
.attr('cy', 100)
.attr('r', r)
.style('fill', 'none')
.style('stroke', '#003366');
fmcG.append('text')
.attr('x', 100)
.attr('y', 105)
.style('text-anchor', 'middle')
.text(function(d) {
return d.fmc
});
fmcG.selectAll(null)
.data(function(d) {
return d.ratings
})
.enter()
.append('line')
.attr("x1", 100)
.attr("y1", 100)
.attr("x2", function(d, i) {
return 100 + length(d) * Math.cos(rotationDegree(i));
})
.attr("y2", function(d, i) {
return 100 + length(d) * Math.sin(rotationDegree(i));
})
.style("stroke", function(d) {
return "#003366"
});
<script src="https://d3js.org/d3.v5.min.js"></script>
I'm really having trouble with D3 and need some help changing my existing barchart to be a grouped barchart The barchart is being used within a tooltip and currently looks like:
Each colour represents a sector of industry (pink = retail, teal = groceries...etc).
I need to change the bar chart so that it compares the percentage change in each industry with the world average percentage change in this industry.
At the moment the bar chart is being created from an array of data. I also have an array with the world percentage values.
So imagine:
countryData = [10,-20,-30,-63,-23,20],
worldData = [23,-40,-23,-42,-23,40]
Where index 0 = retail sector, index 1 = grocery sector, etc.
I need to plot a grouped barchart comparing each sector to the world average (show the world average in red). This is a bit tricky to explain so I drew it for you (...excuse the shoddy drawing).
Please can someone help me change my existing tooltip?
Here's the current code. If you want to simulate the data values changing.
If you want to scrap my existing code that's fine.
.on('mouseover', ({ properties }) => {
// get county data
const mobilityData = covid.data[properties[key]] || {};
const {
retailAverage,
groceryAverage,
parksAverage,
transitAverage,
workplaceAverage,
residentialAverage,
} = getAverage(covid1);
let avgArray = [retailAverage, groceryAverage, parksAverage, transitAverage, workplaceAverage, retailAverage];
let categoriesNames = ["Retail", "Grocery", "Parks", "Transit", "Workplaces", "Residential"];
// create tooltip
div = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
div.html(properties[key]);
div.transition()
.duration(200)
.style('opacity', 0.9);
// calculate bar graph data for tooltip
const barData = [];
Object.keys(mobilityData).forEach((industry) => {
const stringMinusPercentage = mobilityData[industry].slice(0, -1);
barData.push(+stringMinusPercentage); // changing it to an integer value, from string
});
//combine the two lists for the combined bar graph
var combinedList = [];
for(var i = 0; i < barData.length; i++) {
const stringMinusPercentage2 = +(avgArray[i].slice(0, -1));
const object = {category: categoriesNames[i], country: barData[i], world: stringMinusPercentage2}
combinedList.push(object); //Push object into list
}
console.log(combinedList);
// barData = barData.sort(function (a, b) { return a - b; });
// sort into ascending ^ keeping this in case we need it later
const height2 = 220;
const width2 = 250;
const margin = {
left: 50, right: 10, top: 20, bottom: 15,
};
// create bar chart svg
const svgA = div.append('svg')
.attr('height', height2)
.attr('width', width2)
.style('border', '1px solid')
.append('g')
// apply the margins:
.attr('transform', `translate(${[`${margin.left},${margin.top}`]})`);
const barWidth = 30; // Width of the bars
// plot area is height - vertical margins.
const chartHeight = height2 - margin.top - margin.left;
// set the scale:
const yScale = d3.scaleLinear()
.domain([-100, 100])
.range([chartHeight, 0]);
// draw some rectangles:
svgA
.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', (d, i) => i * barWidth)
.attr('y', (d) => {
if (d < 0) {
return yScale(0); // if the value is under zero, the top of the bar is at yScale(0);
}
return yScale(d); // otherwise the rectangle top is above yScale(0) at yScale(d);
})
.attr('height', (d) => Math.abs(yScale(0) - yScale(d))) // the height of the rectangle is the difference between the scale value and yScale(0);
.attr('width', barWidth)
.style('fill', (d, i) => colours[i % 6]) // colour the bars depending on index
.style('stroke', 'black')
.style('stroke-width', '1px');
// Labelling the Y axis
const yAxis = d3.axisLeft(yScale);
svgA.append('text')
.attr('class', 'y label')
.attr('text-anchor', 'end')
.attr('x', -15)
.attr('y', -25)
.attr('dy', '-.75em')
.attr('transform', 'rotate(-90)')
.text('Percentage Change (%)');
svgA.append('g')
.call(yAxis);
})
.on('mouseout', () => {
div.style('opacity', 0);
div.remove();
})
.on('mousemove', () => div
.style('top', `${d3.event.pageY - 140}px`)
.style('left', `${d3.event.pageX + 15}px`));
svg.append('g')
.attr('transform', 'translate(25,25)')
.call(colorLegend, {
colorScale,
circleRadius: 10,
spacing: 30,
textOffset: 20,
});
};
drawMap(svg1, geoJson1, geoPath1, covid1, key1, 'impact1');
drawMap(svg2, geoJson2, geoPath2, covid2, key2, 'impact2');
};
In short I would suggest you to use two Band Scales for x axis. I've attached a code snippet showing the solution.
Enjoy ;)
//Assuming the following data final format
var finalData = [
{
"groupKey": "Retail",
"sectorValue": 70,
"worldValue": 60
},
{
"groupKey": "Grocery",
"sectorValue": 90,
"worldValue": 90
},
{
"groupKey": "other",
"sectorValue": -20,
"worldValue": 30
}
];
var colorRange = d3.scaleOrdinal().range(["#00BCD4", "#FFC400", "#ECEFF1"]);
var subGroupKeys = ["sectorValue", "worldValue"];
var svg = d3.select("svg");
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = +svg.attr("width") - margin.left - margin.right;
var height = +svg.attr("height") - margin.top - margin.bottom;
var container = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// The scale spacing the groups, your "sectors":
var x0 = d3.scaleBand()
.domain(finalData.map(d => d.groupKey))
.rangeRound([0, width])
.paddingInner(0.1);
// The scale for spacing each group's bar, your "sector bar":
var x1 = d3.scaleBand()
.domain(subGroupKeys)
.rangeRound([0, x0.bandwidth()])
.padding(0.05);
var yScale = d3.scaleLinear()
.domain([-100, 100])
.rangeRound([height, 0]);
//and then you will need to append both, groups and bars
var groups = container.append('g')
.selectAll('g')
.data(finalData, d => d.groupKey)
.join("g")
.attr("transform", (d) => "translate(" + x0(d.groupKey) + ",0)");
//define groups bars, one per sub group
var bars = groups
.selectAll("rect")
.data(d => subGroupKeys.map(key => ({ key, value: d[key], groupKey: d.groupKey })), (d) => "" + d.groupKey + "_" + d.key)
.join("rect")
.attr("fill", d => colorRange(d.key))
.attr("x", d => x1(d.key))
.attr("width", (d) => x1.bandwidth())
.attr('y', (d) => Math.min(yScale(0), yScale(d.value)))
.attr('height', (d) => Math.abs(yScale(0) - yScale(d.value)));
//append x axis
container.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
//append y axis
container.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale))
.append("text")
.attr("x", 2)
.attr("y", yScale(yScale.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Values");
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg width="600" height="400"></svg>
I created a dynamic d3.js chart that plots time series.
Each second I add a new sample and remove an old sample.
It gives the impression that the sparklines are moving from the right to the left, which is good.
However, I am not very happy with the way the new and old segment of a path are added and removed.
See this picture:
On this image, you can see a little gap between the y-axis on the right and the sparklines. As the sparklines move to the left, this gap gets bigger and once it is large enough the new segments of the sparklines are then added. That doesn't look very smooth.
I would like the new segments to be drawn as the sparklines move to the left (like you would do when drawing it by hand).
I am using a clip-path to hide the part of paths that I don't want (outside of the plot), but that doesn't seem to give me the correct behavior.
Definition of the clip-path:
this.container.append('defs')
.append('clipPath')
.attr('id', 'chart-content')
.append('rect')
.attr('height', this.height)
.attr('width', this.width);
Use of the clip-path:
group.path = this.paths
.append('g')
.attr('clip-path', 'url(#chart-content)')
.append('path')
.data([group.data])
On another note, the clip-path seem to work when I am panning and zooming.. Which confuse me even more! I hope someone can help me out with that!
I found the answer to my question..
Here is a snippet of a dynamic line chart!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
#chart{
width: 100%;
height: 500px;
}
</style>
<div id='chart'></div>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
<script>
var margin = { top: 20, right: 20, bottom: 20, left: 20};
var padding = { top: 20, right: 40, bottom: 20, left: 40};
let width;
let height;
let svg;
let container;
let data;
let xScale;
let yScale;
let now;
let path;
let gX;
let xAxis;
let lineGenerator;
const duration = 500;
render = () => {
this.hostElement = d3.select('#chart');
this.width = this.hostElement.node().getBoundingClientRect().width - this.margin.left - this.margin.right - this.padding.left - this.padding.right;
this.height = this.hostElement.node().getBoundingClientRect().height - this.margin.top - this.margin.bottom - this.padding.bottom - this.padding.top;
this.createSvg();
this.createAxis();
this.createLine();
this.defineBounds();
this.tick();
}
createSvg = () => {
this.svg = this.hostElement
.append('svg')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')')
.attr('width', this.width + this.padding.right + this.padding.left)
.attr('height', this.height + this.padding.top + this.padding.bottom)
this.container = this.svg
.append('g')
.attr('class', 'chart')
.attr('transform', 'translate(' + this.padding.left + ',' + this.padding.top + ')')
.attr('width', this.width)
.attr('height', this.height);
}
createAxis = () => {
// maxY
const maxY = d3.max(this.data, d => d.value);
// minX & maxX
const minX = d3.min(this.data, d => d.date);
const maxX = d3.max(this.data, d => d.date);
this.now = maxX;
const maxXToDisplay = moment(maxX).subtract(1, 's').toDate();
const minXToDisplay = moment(minX).add(2, 's').toDate();
// Update scales
this.xScale = d3.scaleTime()
.domain([minXToDisplay, maxXToDisplay])
.range([0, this.width]);
this.yScale = d3.scaleLinear()
.domain([0, maxY])
.range([this.height, 0]);
// Update axis
this.xAxis = d3.axisBottom(this.xScale);
const yAxis = d3.axisLeft(this.yScale);
// Draws the axis
this.gX = this.container.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + this.height + ')')
.call(this.xAxis);
const gY = this.container.append('g')
.attr('class', 'y axis')
.call(yAxis);
}
createLine = () => {
this.lineGenerator = d3.line()
.x(d => this.xScale(d.date))
.y(d => this.yScale(d.value))
.curve(d3.curveMonotoneX);
this.path = this.container
.append('g')
.attr('class', 'path-container')
.attr('clip-path', 'url(#chart-content)')
.append('path')
.datum(this.data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr('d', this.lineGenerator);
}
tick = () => {
this.now = moment(this.now).add(1, 's').toDate();
// Add new values
this.data.push({
value: Math.floor(10 + Math.random() * 15),
date: this.now
});
// Remove old values
this.data.shift();
this.path.attr('d', this.lineGenerator);
const numberSamplesToDisplay = this.data.length - 2;
const minX = moment(this.now).subtract(numberSamplesToDisplay, 's').toDate();
const maxX = this.now;
const maxXToDisplay = moment(maxX).subtract(1, 's').toDate();
const minXToDisplay = moment(minX).add(1, 's').toDate();
// Shift domain
this.xScale.domain([minXToDisplay, maxXToDisplay]);
// Slide x-axis left
const xTransition = this.gX.transition()
.duration(1000)
.ease(d3.easeLinear)
.call(this.xAxis);
// Slide paths left
this.path.attr('transform', null)
.transition()
.duration(1000)
.ease(d3.easeLinear)
.attr('transform', 'translate(' + this.xScale(minX) + ', 0)')
.on('end', this.tick);
}
defineBounds = () => {
this.container.append('defs')
.append('clipPath')
.attr('id', 'chart-content')
.append('rect')
.attr('height', this.height)
.attr('width', this.width);
}
generateData = () => {
const dataset = [
{
value: Math.floor(10 + Math.random() * 15),
date: moment().subtract(60, 'seconds').toDate()
}
];
for (let i = 0; i < 59; i ++) {
dataset.push({
value: Math.floor(10 + Math.random() * 15),
date: moment(dataset[dataset.length - 1].date).add(1, 'seconds').toDate()
})
}
return dataset;
}
this.data = generateData();
render();
</script>
I have a bar chart see plunker the problem is that I would like to move the y-axis ticks to be at the middle left side of the rects but they appear on the top and end. and I cannot seem to move them without destroying the chart.
my code
var info = [{
name: "Walnuts",
value: 546546
}, {
name: "Almonds",
value: 456455
}
];
/* Set chart dimensions */
var width = 960,
height = 500,
margin = {
top: 10,
right: 10,
bottom: 20,
left: 60
};
//subtract margins
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
//sort data from highest to lowest
info = info.sort(function(a, b) {
return b.value - a.value;
});
//Sets the y scale from 0 to the maximum data element
var max_n = 0;
var category = []
for (var d in info) {
max_n = Math.max(info[d].value, max_n);
category.push(info[d].name)
}
var dx = width / max_n;
var dy = height / info.length;
var y = d3.scale.ordinal()
.domain(category)
.range([0, height]);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
var svg = d3.select("#chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr('preserveAspectRatio', 'xMidYMin')
.attr("viewBox", '0 0 ' + parseInt(width + margin.left + margin.right) + ' ' + parseInt(height + margin.top + margin.bottom))
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".bar")
.data(info)
.enter()
.append("rect")
.attr("class", function(d, i) {
return "bar" + d.name;
})
.attr("x", function(d, i) {
return 0;
})
.attr("y", function(d, i) {
return dy * i;
})
.attr("width", function(d, i) {
return dx * d.value
})
.attr("height", dy)
.attr("fill", function(d, i) {
if (d.name == 'Walnuts') {
return 'red'
} else {
return 'green'
}
});
var y_xis = svg.append('g')
.attr('id', 'yaxis')
.call(yAxis);
You are using range in y axis like this:
var y = d3.scale.ordinal()
.domain(category)
.range([0, height]);
You should be using 'rangeRoundBands' since the y scale is ordinal
var y = d3.scale.ordinal()
.domain(category)
.rangeRoundBands([0, height], .1);
working code here
For d3 versions like v4/v5.
Defining height as the graph/plot height, and max as the maximum value of y.
import { parseSvg } from 'd3-interpolate/src/transform/parse'
const yScale = d3
.scaleLinear()
.domain([0, max])
.rangeRound([height, 0])
const yAxis = d3.axisLeft(yScale)
svg
.append('g')
.call(yAxis)
.selectAll('.tick')
.each(function(data) {
const tick = d3.select(this)
const { translateX, translateY } = parseSvg(tick.attr('transform'))
tick.attr(
'transform',
translate(translateX, translateY + height / (2 * max))
)
})
Recently I needed something very very similar and I solved this with a call with selecting all text elements in the selection and moving their dy upwards. I will give an example with OP's code:
var y_xis = svg.append('g')
.attr('id','yaxis')
.call(yAxis)
.call(selection => selection
.selectAll('text')
.attr('dy', '-110') // this moves the text labels upwards
.attr('x', '110')); // this does the same job but horizontally