How to drag element after cloning - d3.js

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.

Related

Zooming using d3.scalePoint()

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>

Appending D3.js Elements to Shadow DOM of Polymer Element

I'm working on a Polymer (v1) project and one of my custom polymer elements needs to contain a D3 (v4) chart. D3 seems to operate pretty heavily on appending to the DOM. Unfortunately, Polymer seems pretty strict about how DOM manipulation is performed.
I have created a very simple version of the D3 chart I want to implement:
index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.bar {
fill: #0198E1;
}
</style>
</head>
<body>
<svg></svg>
<script>
var data = [100, 120, 130, 110, 150, 90];
const CHART_WIDTH = 126;
const CHART_HEIGHT = 160;
svg = d3.select('svg')
.attr("width", CHART_WIDTH)
.attr("height", CHART_HEIGHT);
svg.selectAll('rect')
.data(data).enter().append('rect')
.attr("x", function(d, i) {return i * 21})
.attr("y", function(d) {return CHART_HEIGHT - d})
.attr("height", function(d) {return d})
.attr("width", 20)
.attr("class", "bar");
</script>
</body>
</html>
I have attempted two solutions with the following files.
index.html
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./d3-chart.html">
</head>
<body>
<d3-chart></d3-chart>
</body>
</html>
d3-lib.html
<script src="https://d3js.org/d3.v4.min.js"></script>
Attempted Solution 1
Use a combination of D3 and Polymer to select the target svg element and then perform d3 appends.
d3-chart.html
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./d3-lib.html">
<dom-module id="d3-chart">
<template>
<style>
.bar {
fill: #0198E1;
}
</style>
<svg id="svg"></svg>
</template>
<script>
Polymer({
is: 'd3-chart',
properties: {
data: {
Type: Array,
value: [100, 120, 130, 110, 150, 90]
}
},
ready: function() {
const CHART_WIDTH = 126;
const CHART_HEIGHT = 160;
var svg = d3.select(this.$.svg)
.attr("width", CHART_WIDTH)
.attr("height", CHART_HEIGHT);
svg.selectAll('rect')
.data(this.data).enter().append('rect')
.attr("x", function(d, i) {return i * 21})
.attr("y", function(d) {return CHART_HEIGHT - d})
.attr("height", function(d) {return d})
.attr("width", 20)
.attr("class", "bar");
}
});
</script>
</dom-module>
This successfully displays the chart but the css style doesn't apply. I assume this is because Polymer doesn't 'know' about the new elements that have been appended.
Attempted Solution 2
Use D3 to select a new svg element (not in the DOM), perform D3 appends on the element, and use Polymer to append it to the DOM.
d3-chart.html
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./d3-lib.html">
<dom-module id="d3-chart">
<template>
<style>
.bar {
fill: #0198E1;
}
</style>
</template>
<script>
Polymer({
is: 'd3-chart',
properties: {
data: {
Type: Array,
value: [100, 120, 130, 110, 150, 90]
}
},
ready: function() {
const CHART_WIDTH = 126;
const CHART_HEIGHT = 160;
var newSvgElement = document.createElement("svg");
var svg = d3.select(newSvgElement)
.attr("width", CHART_WIDTH)
.attr("height", CHART_HEIGHT);
svg.selectAll('rect')
.data(this.data).enter().append('rect')
.attr("x", function(d, i) {return i * 21})
.attr("y", function(d) {return CHART_HEIGHT - d})
.attr("height", function(d) {return d})
.attr("width", 20)
.attr("class", "bar");
Polymer.dom(this.root).appendChild(newSvgElement);
}
});
</script>
</dom-module>
This code successfully appends all elements to the DOM but nothing is displayed.
What is the proper way to integrate Polymer with D3?
I found a solution. I just had to add the following line to the beginning of the ready function in 'Attempted Solution 1'.
this.scopeSubtree(this.$.svg, true);
For more information:
Why is my SVG rendered by D3 inside a Polymer component unstyled?

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

How to apply a pattern for a D3 bar chart?

I am trying to apply a pattern to a D3 bar chart, but what I get is this:
the chart should stop exactly at 100,000
the pattern should be "fluid"
I am using a green and red pattern defined as follows:
var defs = this.svg.append("g:defs");
defs.append("g:pattern")
.attr("id", "red-fill")
.attr("patternUnits", "userSpaceOnUse")
.attr("width", "85")
.attr("height", "10")
.append("g:image")
.attr("xlink:href", "../10px-barchart-red.png")
.attr("x", 0)
.attr("y", 0)
.attr("width", 85)
.attr("height", 10);
var defs = this.svg.append("g:defs");
defs.append("g:pattern")
.attr("id", "green-fill")
.attr("patternUnits", "userSpaceOnUse")
.attr("width", "85")
.attr("height", "10")
.append("g:image")
.attr("xlink:href", "../10px-barchart-green.png")
.attr("x", 0)
.attr("y", 0)
.attr("width", 85)
.attr("height", 10);
And the plot is made with:
this.svg.selectAll("rect")
.data(dataset, getKeys)
.enter()
.append("rect")
.attr('class', 'bar')
.attr("x", function(d, i) {
return x(i) + 44;
})
.attr("y", function(d, i) {
return y(d.value);
})
.attr("width", x.rangeBand())
.attr("height", function(d, i) {
return height + padding - y(d.value);
})
.attr("fill", function(d) {
if (d.key == 0) {
return "url(#green-fill)";
} else {
return "url(#red-fill)";
}
})
This block by John Schulz successfully makes patterned bar charts that look fantastic (https://bl.ocks.org/jfsiii/7772281).
Copied the code in the below snippet for convenience and perpetuity (also added a little animation to show that it transitions well also).
var first = true;
setInterval( function(){
if(first){
d3.select('.thing-2').transition()
.delay(500)
.duration(1000)
.attr('height',20)
.attr('y',80)
}else{
d3.select('.thing-2').transition()
.delay(500)
.duration(1000)
.attr('height',100)
.attr('y',0)
}
first = !first;
},2500)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>SVG colored patterns via mask</title>
<style>
/* FF seems to need explicit dimensions */
svg {
width: 500px;
height: 500px;
}
rect.hbar {
mask: url(#mask-stripe)
}
.thing-1 {
fill: blue;
}
.thing-2 {
fill: green;
}
</style>
</head>
<body>
<svg>
<defs>
<pattern id="pattern-stripe"
width="4" height="4"
patternUnits="userSpaceOnUse"
patternTransform="rotate(45)">
<rect width="2" height="4" transform="translate(0,0)" fill="white"></rect>
</pattern>
<mask id="mask-stripe">
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern-stripe)" />
</mask>
</defs>
<!-- bar chart -->
<rect class="hbar thing-2" x="0" y="0" width="50" height="100"></rect>
<rect class="hbar thing-2" x="51" y="50" width="50" height="50"></rect>
<rect class="hbar thing-2" x="102" y="25" width="50" height="75"></rect>
<!-- horizontal bar chart -->
<rect class="hbar thing-1" x="0" y="200" width="10" height="50"></rect>
<rect class="hbar thing-1" x="0" y="251" width="123" height="50"></rect>
<rect class="hbar thing-1" x="0" y="302" width="41" height="50"></rect>
</svg>
</body>
</html>

Resources