Published
Edited
Feb 12, 2021
1 fork
Insert cell
md`# D3 Autoscaling Axis
Port of http://bl.ocks.org/WilliamQLiu/59c87d2bcc00800ec3f9 to D3 v6`
Insert cell
chart = {
var w = width, //window.innerWidth,
h = height, // window.innerHeight,
//margin = { top: 0, right: 0, bottom: 0, left: 0 },
margin = { top: 40, right: 20, bottom: 20, left: 40 },
radius = 6;
/*var svg = d3.select("body").append("svg").attr({
width: w,
height: h
});*/

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

var dataset = [
{ x: 100, y: 110 },
{ x: 83, y: 43 },
{ x: 92, y: 28 },
{ x: 49, y: 74 },
{ x: 51, y: 10 },
{ x: 25, y: 98 },
{ x: 77, y: 30 },
{ x: 20, y: 83 },
{ x: 11, y: 63 },
{ x: 4, y: 55 },
{ x: 0, y: 0 },
{ x: 85, y: 100 },
{ x: 60, y: 40 },
{ x: 70, y: 80 },
{ x: 10, y: 20 },
{ x: 40, y: 50 },
{ x: 25, y: 31 }
];

// We're passing in a function in d3.max to tell it what we're maxing (x value)
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d.x + 10; })])
.range([margin.left, w - margin.right]); // Set margins for x specific

// We're passing in a function in d3.max to tell it what we're maxing (y value)
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) { return d.y + 10; })])
.range([margin.top, h - margin.bottom]); // Set margins for y specific

// Add a X and Y Axis (Note: orient means the direction that ticks go, not position)
var xAxis = d3.axisTop(xScale);
var yAxis = d3.axisLeft(yScale);
//var xAxis = d3.svg.axis().scale(xScale).orient("top");
//var yAxis = d3.svg.axis().scale(yScale).orient("left");

// New circles will start at 0,0
var circleInitialAttrs = {
cx: xScale(0),
cy: yScale(0),
r: 1
};

// Sets circles attributes
var circleAttrs = {
cx: function(d) { return xScale(d.x); },
cy: function(d) { return yScale(d.y); },
r: radius
};

// Adds X-Axis as a 'g' element
/*
var xAxisGroup = svg.append("g").attr({
"class": "axis", // Give class so we can style it
transform: "translate(" + [0, margin.top] + ")" // Translate just moves it down into position (or will be on top)
}).call(xAxis); // Call the xAxis function on the group

// Adds Y-Axis as a 'g' element
var yAxisGroup = svg.append("g").attr({
"class": "axis",
transform: "translate(" + [margin.left, 0] + ")"
}).call(yAxis); // Call the yAxis function on the group
*/
// Adds X-Axis as a 'g' element
var xAxisGroup = svg.append("g")
.attr("class", "axis") // Give class so we can style it
.attr("transform", "translate(" + [0, margin.top] + ")") // Translate just moves it down into position (or will be on top)
.call(xAxis); // Call the xAxis function on the group

// Adds Y-Axis as a 'g' element
var yAxisGroup = svg.append("g")
.attr("class", "axis") // Give class so we can style it
.attr("transform", "translate(" + [margin.left, 0] + ")") // Translate just moves it down into position (or will be on top)
.call(yAxis); // Call the yAxis function on the group

var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
//.attr(circleInitialAttrs) // Get attributes from circleInitialAttrs var
.attr("cx", xScale(0))
.attr("cy", yScale(0))
.attr("r", 1)
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);

circles.transition() // Gives the fly out from the center effect
.delay(function (d, i){
return i * 100; // Gives a slight delay with 100 ms spacing
})
.duration(1000)
.ease(d3.easeElastic.period(0.4))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("r", radius);

// On Click, we want to add data to the array and chart
svg.on("click", function(event) {
let coords = d3.pointer(event);
// Normally we go from data to pixels, but here we're doing pixels to data
var newData= {
x: Math.round( xScale.invert(coords[0])), // Takes the pixel number to convert to number
y: Math.round( yScale.invert(coords[1]))
};

dataset.push(newData); // Push data to our array

xScale.domain([0, d3.max(dataset, function (d) { return d.x + 10; })])
yScale.domain([0, d3.max(dataset, function (d) { return d.y + 10; })])

// Update Axis (e.g. might increase if new higher value added)
xAxisGroup.transition().call(xAxis); // Update X-Axis
yAxisGroup.transition().call(yAxis); // Update Y-Axis

// When adding new items, goes from 0,0 and transition to place
let c = svg.selectAll("circle"); // For new circle, go through the update process

// Updates existing circles to new positions by just adding attr
c.transition()
.ease(d3.easeElastic.period(0.4))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("r", radius);
//.attr(circleAttrs);

/*c.enter()
.append("circle")
//.attr(circleInitialAttrs)
.attr("cx", xScale(0))
.attr("cy", yScale(0))
.attr("r", 1)
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut); */
c = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", xScale(0))
.attr("cy", yScale(0))
.attr("r", 1)
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);

c.transition()
.duration(1000) // Set how long it takes
.ease(d3.easeElastic.period(0.4))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("r", radius);
})

// Create Event Handlers for mouse
function handleMouseOver(event, d, i, index) { // Add interactivity
// Use D3 to select element, change color and size
d3.select(this)
.attr("fill", "orange")
.attr("r", radius * 2);

// Specify where to put label of text
svg.append("text")
// Create an id for text so we can select it later for removing on mouseout
.attr("id", function() { return "t" + d.x + "-" + d.y + "-" + index})
.attr("x", function() { return xScale(d.x) - 30; })
.attr("y", function() { return yScale(d.y) - 15; })
.text(function() {
return `${d.x}, ${d.y}`; // Value of the text
});
}

function handleMouseOut(event, d, i, index) {
// Use D3 to select element, change color back to normal
d3.select(this)
.attr("fill", "black")
.attr("r", radius);

// Select text by id and then remove
d3.select("#t" + d.x + "-" + d.y + "-" + index).remove(); // Remove text location
}
return svg.node();
}
Insert cell
d3 = require('d3@6')
Insert cell
width = 800
Insert cell
height = 600
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more