Zooming using d3.scalePoint() - d3.js

So I modified this examples so that I can use scalePoint on the xAxis (instead of scaleLinear, I also changed the code so that you can only zoom on the xAxis, I don't care about yAxis zooming):
https://bl.ocks.org/mbostock/db6b4335bf1662b413e7968910104f0f
Everything seems to be working except the zoom, I get a undefined is not a function error on this line: gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
Any idea on how to make zoom work using scalePoint?
Here's the code:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<style>
text {
fill: black;
}
rect {
fill: steelblue;
}
path.chart__line {
fill: green;
opacity: .3;
stroke: green;
stroke-width: 1.5px;
}
</style>
<svg id="my-svg" width="960" height="500">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0.0%" stop-color="#2c7bb6"></stop>
<stop offset="12.5%" stop-color="#00a6ca"></stop>
<stop offset="25.0%" stop-color="#00ccbc"></stop>
<stop offset="37.5%" stop-color="#90eb9d"></stop>
<stop offset="50.0%" stop-color="#ffff8c"></stop>
<stop offset="62.5%" stop-color="#f9d057"></stop>
<stop offset="75.0%" stop-color="#f29e2e"></stop>
<stop offset="87.5%" stop-color="#e76818"></stop>
<stop offset="100.0%" stop-color="#d7191c"></stop>
</linearGradient>
</defs>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
let xDomain = ['A', 'B', 'C'];
let zoomed = () => {
view.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
// gY.call(yAxis.scale(d3.event.transform.rescaleY(y))); // I don't care about yAxis
};
let resetted = () => {
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
};
let svg = d3.select("#my-svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
let zoom = d3.zoom()
.scaleExtent([1, 40])
.translateExtent([[0, 0], [width, height]])
.on("zoom", zoomed);
let x = d3.scalePoint()
.domain(xDomain)
.range([0, width]);
let y = d3.scaleLinear()
.domain([0, height])
.range([0, height]);
let xAxis = d3.axisBottom(x)
.ticks(xDomain.length)
.tickSize(height)
.tickPadding(8 - height);
let yAxis = d3.axisRight(y)
.ticks(10)
.tickSize(width)
.tickPadding(8 - width);
let view = svg.append("rect")
.attr("class", "view")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
let gX = svg.append("g")
.attr("class", "axis axis--x")
.call(xAxis);
let gY = svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
d3.select("button")
.on("click", resetted);
svg.call(zoom);
</script>
</body>
</html>
Thank you for your time.

You need to use a continuous scale if you want to use the transformX function. It uses the scale's invert function which only continuous scales provide.
Although you mentioned you didn't want to use scaleLinear I tested it and it seems to work OK. (I had to enable y zooming to test this)
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<style>
text {
fill: black;
}
rect {
fill: steelblue;
}
path.chart__line {
fill: green;
opacity: .3;
stroke: green;
stroke-width: 1.5px;
}
</style>
<svg id="my-svg" width="960" height="500">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0.0%" stop-color="#2c7bb6"></stop>
<stop offset="12.5%" stop-color="#00a6ca"></stop>
<stop offset="25.0%" stop-color="#00ccbc"></stop>
<stop offset="37.5%" stop-color="#90eb9d"></stop>
<stop offset="50.0%" stop-color="#ffff8c"></stop>
<stop offset="62.5%" stop-color="#f9d057"></stop>
<stop offset="75.0%" stop-color="#f29e2e"></stop>
<stop offset="87.5%" stop-color="#e76818"></stop>
<stop offset="100.0%" stop-color="#d7191c"></stop>
</linearGradient>
</defs>
</svg>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
let xDomain = ['A', 'B', 'C'];
let zoomed = () => {
view.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
gY.call(yAxis.scale(d3.event.transform.rescaleY(y))); // I don't care about yAxis
};
let resetted = () => {
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
};
let svg = d3.select("#my-svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
let zoom = d3.zoom()
.scaleExtent([1, 40])
.translateExtent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
let x = d3.scaleLinear()
.domain(xDomain)
.range([0, width]);
let y = d3.scaleLinear()
.domain([0, height])
.range([0, height]);
let xAxis = d3.axisBottom(x)
.ticks(xDomain.length)
.tickSize(height)
.tickPadding(8 - height);
let yAxis = d3.axisRight(y)
.ticks(10)
.tickSize(width)
.tickPadding(8 - width);
let view = svg.append("rect")
.attr("class", "view")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
let gX = svg.append("g")
.attr("class", "axis axis--x")
.call(xAxis);
let gY = svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
d3.select("button")
.on("click", resetted);
svg.call(zoom);
</script>
</body>
</html>

Related

Why are my axes not positioning over my bar chart in this d3.js graph?

I am working on a project to teach myself d3.js where I'm building a bar graph using an API of US GDP. However, now that I've added my axes, the axes place where I expected them to. However, they have pushed my bars down below them. Can anyone explain to me why my axes and bars are behaving this way, and what I can do to fix this?
let apiUrl = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json"
GDPGraph ()
function GDPGraph () {
fetch(apiUrl)
.then(response => response.json())
.then(data => {
// document.getElementById('GDP').innerHTML = JSON.stringify(data.data);
var width = document.documentElement.clientWidth,
height = document.documentElement.clientHeight * .8,
margin = 10;
var GDPbyQuarter = [],
yearQuarter =[]
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
for(i = 0; i < data.data.length; i++){
GDPbyQuarter.push(data.data[i][1])
}
for(i = 0; i < data.data.length; i++){
yearQuarter.push(data.data[i][0])
}
console.log(yearQuarter)
// console.log('calculated width:',((1/GDPbyQuarter.length)*width),'calculated legnth:', GDPbyQuarter.length, 'total width:', width)
const maxNumber = d3.max(GDPbyQuarter)
console.log(maxNumber);
const xScale = d3.scaleLinear()
.domain([0, GDPGraph.length])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([0, maxNumber])
.range([0, height]);
d3.select('body')
.selectAll('div')
.data(GDPbyQuarter)
.enter()
.append('svg')
.attr('class', 'bar')
.style('height', (d) => yScale(d)+'px')
.style('width', ((1/(GDPbyQuarter.length+margin)*width)+'px'))
.append('title')
.attr('class', 'title')
.text((d, i) => yearQuarter[i]+': ' + d)
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.call(xAxis)
.attr("transform", "translate(0," + (height -100) + ")");
svg.append('g')
.attr('transform', 'translate(10,0)')
.call(yAxis)
})
}
.bar {
width: 25px;
height: 100px;
display: inline-block;
background-color: red;
}
.bar:hover {
background-color: blue;
}
.title {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div>
<h1>US GDP By Quarter</h1>
<h3>FreeCodeCamp</h3>
</div>
<div id="GDP">
</div>
<script src='js/app.js'></script>
</body>
</html>
You are creating an svg node for each bar. Try inspecting the DOM using the Chrome Dev tools
The answer is that I had to fix my positioning. I ended up making the containing div position:relative; and then my elements within the div position:absolute;
#GDP {
position: relative;
}
#GDP > * {
position: absolute;
}
g {
position: absolute;
}

How to drag element after cloning

How to drag a cloned element after appending to parent node. I have tried with the drag method but it isn't working. I am new to d3 js and trying to go basics and examples on fiddle.
Demo: https://jsfiddle.net/pmczny6k/
code:
<html>
<head>
<title>Editor</title>
<meta http-equiv="x-ua-compatible" content="ie=9"/>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
window.onload = function ()
{
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
var circle = svgContainer.append("circle")
.attr("cx", 30)
.attr("cy", 30)
.attr("r", 20);
var rectangle = svgContainer.append("rect")
.attr("x", 100)
.attr("y", 100)
.attr("width", 50)
.attr("height", 50);
//clonning
function move() {
d3.select(rectangle)
.attr('x', d3.event.x - parseInt(d3.select(this).attr("width")) / 2)
.attr('y', d3.event.y - parseInt(d3.select(this).attr("height")) / 2);
this.parentNode.appendChild(this);
}
;
d3.selectAll(".drg").style("fill", "red")
.call(
d3.behavior.drag()
.on('drag', move).origin(function () {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on('dragend', function (d) {
var elem = d3.select(this);
elem.attr("x", elem.attr("initial-x"));
elem.attr("y", elem.attr("initial-y"));
console.log(elem.attr("x"));
var mouseCoordinates = d3.mouse(this);
if (mouseCoordinates[0] > 70) {
//Append new element
d3.select("svg").append("rect")
.classed("drg", true)
.attr("width", 50)
.attr("height", 50)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "green");
}
})
)
};
</script>
</head>
<body>
<svg width="1024" height="768" style="background-color: #204d74">
<!--<g>-->
<rect x="10" y="20" height="250" width="300" style="fill: #080808"></rect>
<rect class="drg" x="12" y="22" initial-x="12" initial-y="22" height="50" width="50" style="fill: #f0ad4e"></rect>
<!--</g>-->
<rect x="10" y="280" height="250" width="300" style="fill: #080808"></rect>
<rect class="drg" x="12" y="282" initial-x="12" initial-y="282" height="50" width="50" style="fill: #f0ad4e"></rect>
<rect x="320" y="20" height="510" width="690" style="fill: #080808"></rect>
</svg>
</body>
</html>
Just added another call to the new created rect object:
.call(
d3.behavior.drag()
.on('drag', move).origin(function() {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
}));
Updated fiddle.
You also have some syntax errors. Please check them on a better syntax highlighting tool as fiddle isn't a good way to do this. Hope it works now as expected.

D3.JS Problems with domain and range of y axis

I'm sure it's a newbie question, but I'm having problems in scaling my area chart correctly to the data. More precisely, the upper line of my area chart never reaches its maximum and I haven't figured out why, so far. For example, the maximum of the y value (FREQ) is 8, but the upper line limit of the line does not fit to that value (it is somewhere between 7 and 8).
Maybe it's due to my padding variable? Maybe it's shifting my axis in the wrong way?
Does anyone can tell me what my mistake is? I would really appreciate it :-)
Here's my code so far:
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<head>
<title>Data Growth</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css">
body {
font: 12px sans-serif;
background: #DFE3DB;
}
.line {
fill: #7c9393;
stroke: #7c9393;
}
.axis path,
.axis line {
fill: none;
stroke: #34363c;
stroke-width: 3px;
shape-rendering: crispEdges;
}
.title {
font-size: 18px;
}
</style>
</head>
<body>
<div id="dataDevelopment"></div>
<script type="text/javascript">
//Data
var data = [{"YEAR":"2000","CL_ID":1,"FREQ":0,"SUMS":0},{"YEAR":"2001","CL_ID":1,"FREQ":1,"SUMS":1},{"YEAR":"2002","CL_ID":1,"FREQ":0,"SUMS":0},{"YEAR":"2003","CL_ID":1,"FREQ":2,"SUMS":3},{"YEAR":"2004","CL_ID":1,"FREQ":1,"SUMS":4},{"YEAR":"2005","CL_ID":1,"FREQ":3,"SUMS":7},{"YEAR":"2006","CL_ID":1,"FREQ":3,"SUMS":10},{"YEAR":"2007","CL_ID":1,"FREQ":1,"SUMS":11},{"YEAR":"2008","CL_ID":1,"FREQ":3,"SUMS":14},{"YEAR":"2009","CL_ID":1,"FREQ":3,"SUMS":17},{"YEAR":"2010","CL_ID":1,"FREQ":2,"SUMS":19},{"YEAR":"2011","CL_ID":1,"FREQ":7,"SUMS":26},{"YEAR":"2012","CL_ID":1,"FREQ":8,"SUMS":34},{"YEAR":"2013","CL_ID":1,"FREQ":3,"SUMS":37},{"YEAR":"2014","CL_ID":1,"FREQ":7,"SUMS":44},{"YEAR":"2015","CL_ID":1,"FREQ":1,"SUMS":45}];
//The graph shown instantly
var width = 800,
height = 500,
padding = 50;
var svg = d3.select("#dataDevelopment")
.append("svg")
.attr("width", width)
.attr("height", height);
var timeFormat = d3.time.format("%Y");
var xMin = d3.min(data, function(d) {return d.YEAR;});
var xMax = d3.max(data, function(d) {return d.YEAR;});
var yMin = d3.min(data, function(d) {return d.FREQ;});
var yMax = d3.max(data, function(d) {return d.FREQ;});
var x = d3.time.scale().domain([timeFormat.parse(xMin) , timeFormat.parse(xMax)])
.range([padding, width - padding]);
var y = d3.scale.linear().domain([yMin, yMax])
.range([height - padding, padding]);
//Axes
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues([timeFormat.parse(xMin) , timeFormat.parse(xMax)]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var lineGraph = d3.svg.area()
.interpolate("basis")
.x(function(d) { return x(timeFormat.parse(d.YEAR)); })
.y0(height - padding)
.y1(function(d) { return y(d.FREQ); });
svg.append("path")
.attr("class", "line")
.attr("d", lineGraph(data));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + (height - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ", 0)")
.call(yAxis);
</script>
</body>
</html>

How to insert images into d3 hexbin

I created a mesh of hexagons using D3's hexbin. I want to fill the hexagons with images. I took a look at this question where a circle is filled with an image. I tried to do exactly the same and fill every bin with the image but it didn't work.
This is what i tried:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.hexagon {
fill: none;
stroke: #000;
stroke-width: 2px;
}
</style>
<body>
<svg id="mySvg" width="80" height="80">
<defs id="mdef">
<pattern id="image" x="0" y="0" height="40" width="40">
<image x="0" y="0" width="40" height="40" xlink:href="http://www.e-pint.com/epint.jpg"></image>
</pattern>
</defs>
</svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var color = d3.scale.linear()
.domain([0, 20])
.range(["orange", "orange"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.size([width, height])
.radius(100);
var points = hexbin.centers();
var x = d3.scale.identity()
.domain([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("class", "mesh")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".hexagon")
.data(hexbin(points))
.enter().append("path")
.attr("class", "hexagon")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style("fill", 'url(#image)');
</script>
So how can i fill the hexagons with an image?
Looks like your pattern just wasn't defined in a way that would show up in the path. This should move the image to the right part of the pattern to show up in your hex grid.
<pattern id="image" x="-100" y="-100" height="200" width="200">
<image x="50" y="50" width="100" height="100" xlink:href="http://www.e-pint.com/epint.jpg"></image>
</pattern>

group bar, from 3rd onwards coming separately

Here is my code.
http://jsfiddle.net/x8rax/9/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title> - jsFiddle demo</title>
<script type='text/javascript' src='http://d3js.org/d3.v3.min.js'></script>
<link rel="stylesheet" type="text/css" href="/css/result-light.css">
<style type='text/css'>
.chart rect {
fill:#98abc5 ;
}
.chart2 rect {
fill:#8a89a6 ;
}
.chart3 rect {
fill:#7b6888 ;
}
.chart4 rect {
fill:#6b486b ;
}
.chart5 rect {
fill:#a05d56 ;
}
.chart6 rect {
fill:#d0743c ;
}
.chart7 rect {
fill:#ff8c00 ;
}
.chart {
position: absolute;
}
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
</style>
<script type='text/javascript'>//<![CDATA[
window.onload=function(){
var data = [4, 8, 15, 78, 100, 90];
var width = 420,
barHeight = 80;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight +")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 70);
var data = [10, 30, 20, 1000, 9, 500];
var width = 420,
barHeight = 80;
var chart = d3.select(".chart2")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + (i * barHeight + barHeight - 70 ) +")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 70);
var data = [500, 100, 60, 20, 1000, 9];
var width = 420,
barHeight = 80;
var chart = d3.select(".chart3")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + (i * barHeight + barHeight - 60 ) +")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 70);
}//]]>
</script>
</head>
<body>
<svg class="chart"></svg>
<svg class = "chart2"></svg>
<svg class = "chart3"></svg>
<svg class = "chart4"></svg>
<svg class = "chart5"></svg>
<svg class = "chart6"></svg>
<svg class = "chart7"></svg>
</body>
</html>
As you can see, 3rd onwards it's coming separately. I want to create the group bar chart with 7 different bars. I am right now stuck in 3rd.
It is coming separately because the svg tags with classes chart2 onwards are not positioned absolutely.
It works if you add a css rule as follows:
.chart2, .chart3, .chart4, .chart5, .chart6, .chart7{
position: absolute;
}
Please note however, that your current approach is not generic and you end up needing to create specific svg tags and repeating the bar chart creation code.
Please refer the grouped bar example at: http://bl.ocks.org/mbostock/3887051

Resources