I would like to create a rounded edge for a corner where the user can specify the corner's radius in D3js.
I found a post that has potential solutions, but the examples are in Observable notebook.
I tried converting to plain Javascript. But it didn't work for me.
https://observablehq.com/#carpiediem/svg-paths-with-circular-corners
Any help is much appreciated, thanks.
I think the post you shared may be overly complicated. Assuming you are using d3.line() or d3.area(), I would suggest looking into the different curve interpolators available in D3. Many of them allow an extra parameter to specify, for example, a tension that can be manipulated.
Here it is:
const drag = () => {
function dragstarted(d) {
d3.select(this).raise().attr("stroke", "black");
}
function dragged(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
d3.select('path.angled')
.attr('d', 'M' + points.map(d => `${d.x} ${d.y}`).join(','));
const angle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
- Math.atan2(points[1].y-points[2].y, points[1].x-points[2].x);
const acuteAngle = Math.min(Math.abs(angle), 2*Math.PI-Math.abs(angle));
const shortestRay = Math.min(
Math.sqrt(Math.pow(points[1].x-points[0].x, 2) + Math.pow(points[1].y-points[0].y, 2)),
Math.sqrt(Math.pow(points[1].x-points[2].x, 2) + Math.pow(points[1].y-points[2].y, 2))
);
const radiusToUse = Math.min( cornerRadius, shortestRay * Math.tan(acuteAngle/2) );
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(acuteAngle/2));
const determinant = (points[1].x-points[0].x)*(points[1].y-points[2].y) - (points[1].x-points[2].x)*(points[1].y-points[0].y);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);
const manualPathDesc = `
M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
d3.select('path.arced').attr('d', manualPathDesc);
d3.select('rect.anchor.in')
.attr("x", anchorIn.x - 3)
.attr("y", anchorIn.y - 3);
d3.select('rect.anchor.out')
.attr("x", anchorOut.x - 3)
.attr("y", anchorOut.y - 3);
const circleCenter = alongSegment(
points[1],
{ x: (anchorIn.x + anchorOut.x)/2, y: (anchorIn.y + anchorOut.y)/2 },
Math.sqrt(Math.pow(radiusToUse, 2) + Math.pow(distanceToTangentPoint, 2))
);
d3.select('path.triangles')
.attr("d", `M${points[1].x} ${points[1].y} L${circleCenter.x} ${circleCenter.y} L${anchorIn.x} ${anchorIn.y} L${circleCenter.x} ${circleCenter.y} L${anchorOut.x} ${anchorOut.y}`);
d3.select('text.angle').text(`${Math.round(acuteAngle * 180 / Math.PI)}°`);
d3.select('text.shortest').text(Math.round(shortestRay));
d3.select('text.maxradius').text(Math.round(shortestRay * Math.tan(acuteAngle/2)));
d3.select('text.toAnchor').text(Math.round(distanceToTangentPoint));
d3.select('text.determinate').text(determinant < 0 ? 'neg.' : 'pos.');
}
function dragended(d) {
d3.select(this).attr("stroke", null);
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
function alongSegment(from, toward, distanceAlong) {
const bearing = Math.atan2(from.y-toward.y, from.x-toward.x);
return {
bearing,
x: from.x - distanceAlong * Math.cos(bearing),
y: from.y - distanceAlong * Math.sin(bearing)
};
}
const chart = () => {
var color = d3.scaleOrdinal().range(d3.schemeCategory20);
const svg = d3.select("svg")
.attr("viewBox", [0, 0, width, height]);
const angle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
- Math.atan2(points[1].y-points[2].y, points[1].x-points[2].x);
const acuteAngle = Math.min(Math.abs(angle), 2*Math.PI-Math.abs(angle));
const shortestRay = Math.min(
Math.sqrt(Math.pow(points[1].x-points[0].x, 2) + Math.pow(points[1].y-points[0].y, 2)),
Math.sqrt(Math.pow(points[1].x-points[2].x, 2) + Math.pow(points[1].y-points[2].y, 2))
);
const radiusToUse = Math.min( cornerRadius, shortestRay * Math.tan(acuteAngle/2) );
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(acuteAngle/2));
const determinant = (points[1].x-points[0].x)*(points[1].y-points[2].y) - (points[1].x-points[2].x)*(points[1].y-points[0].y);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);
const circleCenter = alongSegment(
points[1],
{ x: (anchorIn.x + anchorOut.x)/2, y: (anchorIn.y + anchorOut.y)/2 },
Math.sqrt(Math.pow(radiusToUse, 2) + Math.pow(distanceToTangentPoint, 2))
);
const manualPathDesc = `M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
svg.append('rect')
.attr("x", 8)
.attr("y", 10)
.attr("width", 160)
.attr("height", 105)
.attr("fill", '#eee');
svg.append('text')
.attr("class", 'angle')
.attr("x", 35)
.attr("y", 25)
.attr("text-anchor", "end")
.text(`${Math.round(acuteAngle * 180 / Math.PI)}°`);
svg.append('text')
.attr("class", 'shortest')
.attr("x", 35)
.attr("y", 45)
.attr("text-anchor", "end")
.text(Math.round(shortestRay));
svg.append('text')
.attr("class", 'maxradius')
.attr("x", 35)
.attr("y", 65)
.attr("text-anchor", "end")
.text(Math.round(shortestRay * Math.tan(acuteAngle/2)));
svg.append('text')
.attr("class", 'toAnchor')
.attr("x", 35)
.attr("y", 85)
.attr("text-anchor", "end")
.text(Math.round(distanceToTangentPoint));
svg.append('text')
.attr("class", 'determinate')
.attr("x", 35)
.attr("y", 105)
.attr("text-anchor", "end")
.text(determinant < 0 ? 'neg.' : 'pos.');
svg.append('text')
.attr("x", 40)
.attr("y", 25)
.attr("text-anchor", "start")
.text('angle between rays');
svg.append('text')
.attr("x", 40)
.attr("y", 45)
.attr("text-anchor", "start")
.text('length of shortest ray');
svg.append('text')
.attr("x", 40)
.attr("y", 65)
.attr("text-anchor", "start")
.text('max radius, to fit');
svg.append('text')
.attr("x", 40)
.attr("y", 85)
.attr("text-anchor", "start")
.text('from vertex to anchors');
svg.append('text')
.attr("x", 40)
.attr("y", 105)
.attr("text-anchor", "start")
.text('determinant value');
svg.append('path')
.attr("class", 'arced')
.datum(points)
.attr("d", manualPathDesc)
.attr("stroke", 'orange')
.attr("stroke-width", 5)
.attr("fill", 'none');
svg.append('path')
.attr("class", 'angled')
.attr("d", 'M' + points.map(d => `${d.x} ${d.y}`).join(', '))
.attr("stroke", '#888')
.attr("fill", 'none');
svg.append('rect')
.attr("class", 'anchor in')
.attr("x", anchorIn.x - 3)
.attr("y", anchorIn.y - 3)
.attr("width", 6)
.attr("height", 6)
.attr("fill", '#888');
svg.append('rect')
.attr("class", 'anchor out')
.attr("x", anchorOut.x - 3)
.attr("y", anchorOut.y - 3)
.attr("width", 6)
.attr("height", 6)
.attr("fill", '#ccc');
svg.append('path')
.attr("class", 'triangles')
.attr("d", `M${points[1].x} ${points[1].y} L${circleCenter.x} ${circleCenter.y} L${anchorIn.x} ${anchorIn.y} L${circleCenter.x} ${circleCenter.y} L${anchorOut.x} ${anchorOut.y}`)
.attr("stroke", '#ccc')
.attr("fill", 'none');
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 6)
.attr("fill", (d, i) => color(i))
.on("mouseover", function (d) {d3.select(this).style("cursor", "move");})
.on("mouseout", function (d) {})
.call(drag());
return svg.node();
}
const width = 1000;
const height = 600;
const cornerRadius = 50;
const points = d3.range(3).map(i => ({
x: Math.random() * (width - 10 * 2) + 10,
y: Math.random() * (300 - 10 * 2) + 10,
}));
chart();
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="1000" height="600"></svg>
Related
simple d3 line chart... need the line to span the whole svg and the labels to be anchored to the path line itself. bonus points if there's optional code to add markers as well. thanks in advance.
<html>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function createLineChart(data,id) {
var svg = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 600);
var line = d3.line()
.x(function(d, i) { return i * 50 + 50; })
.y(function(d) { return 300 - d; });
svg.append("path")
.datum(data)
.attr("d", line)
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "none");
data.forEach(function(d, i) {
if (i > 0) {
var percentChange = (d - data[i - 1]) / data[i - 1] * 100;
var color = percentChange >= 0 ? "green" : "red";
var y = percentChange >= 0 ? d - 15 : d + 15;
svg.append("text")
.text(percentChange.toFixed(1) + "%")
.attr("x", i * 50 + 50)
.attr("y", y)
.attr("text-anchor", "middle")
.attr("fill", color);
}
});
}
</script>
</head>
<body>
</body>
<script>
var data = [10, 20, 15, 40, 50, 60];
createLineChart(data);
</script>
</html>
I guess the main things you need to read up on are d3 scales and selections (particularly joins). In particular:
We'll define the width w and height h of the SVG and then defining scales to fit the path into the SVG.
We'll use the data to define each of the path, markers, and text in terms of the data.
createLineChart([10, 20, 15, 40, 50, 60], { mark_points: true })
function createLineChart(data, opts = {}) {
let { w = 800, h = 500, mark_points = false } = opts;
let pad = 50;
let x_scale = d3
.scaleLinear()
.domain([0, data.length - 1])
.range([pad, w - pad]);
let [ymin, ymax] = d3.extent(data);
let y_scale = d3
.scaleLinear()
.domain([ymin, ymax])
.range([h - pad, pad]);
let svg = d3.select('#container')
.append("svg")
.attr("width", w)
.attr("height", h)
.style("border", "solid 1px black");
let line = d3
.line()
.x(function (d, i) {
return x_scale(i);
})
.y(function (d) {
return y_scale(d);
});
svg
.append("path")
.datum(data)
.attr("d", line)
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "none");
svg
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", (_, i) => x_scale(i))
.attr("cy", (d) => y_scale(d))
.attr("r", 5)
.attr("fill", "black");
if (mark_points) {
svg
.selectAll("text")
.data(data)
.join("text")
.attr("x", (_, i) => x_scale(i))
.attr("y", (d) => y_scale(d))
.attr("dx", -3)
.attr("dy", 12)
.attr("font-size", 16)
.attr("text-anchor", "end")
.attr("fill", (_, i) =>
data[i] < data[i - 1]
? "red"
: data[i] == data[i - 1]
? "blue"
: "green"
)
.text(function (_, i) {
if (i > 0) {
let change = (data[i] - data[i - 1]) / data[i - 1];
return d3.format("0.2%")(change);
} else {
return "";
}
});
}
svg
.append("g")
.attr("transform", `translate(0, ${h - pad})`)
.call(d3.axisBottom(x_scale).ticks(data.length));
svg
.append("g")
.attr("transform", `translate(${pad})`)
.call(d3.axisLeft(y_scale).ticks(5));
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="container"></div>
we are needing help coloring the specific links of this sankey chart. We want the links that connect with "Raleigh" to be green, all the others staying grey.
Here is a picture of what it looks like, followed by a picture of what where the green links will go:
We are new to d3 and just can't figure it out, any help would be great!!
var svg = d3.select("svg").attr("style", "outline: thin solid grey;"),
width = +svg.attr("width"),
height = +svg.attr("height");
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var school = {"nodes": [
{"name":"Happiness Index"}, // 0
{"name":"Business Cost Index"}, // 1
{"name":"Home Price to Income"}, // 2
{"name":"Population Growth"}, // 3
{"name":"3 Year GDP Growth"}, // 4
{"name":"Percent with Degree"}, // 5
{"name":"Austin"}, // 6
{"name":"Nashville"}, // 7
{"name":"Atlanta"}, // 8
{"name":"Raleigh"}, // 9
{"name":"Washington DC"}, // 10
],
"links":[
// From Happiness
{"source":0,"target":6,"value":97},
{"source":0,"target":9,"value":100},
{"source":0,"target":10,"value":96},
// From Business Cost
{"source":1,"target":9,"value":87},
{"source":1,"target":8,"value":88},
{"source":1,"target":7,"value":99},
// From PTI
{"source":2,"target":8,"value":86},
// From Pop Growth
{"source":3,"target":9,"value":87},
{"source":3,"target":6,"value":94},
// From 3yrgdp
{"source":4,"target":9,"value":100},
{"source":4,"target":6,"value":88},
{"source":4,"target":7,"value":96},
// From percent undergrad
{"source":5,"target":9,"value":85},
{"source":5,"target":10,"value":100},
]};
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]]);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 15)
.selectAll("g");
sankey(school);
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); })
// link hover values
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(school.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return color(d.name.replace(/ .*/, "")); })
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
svg.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("class", "graphTitle")
.text(" ");
svg.append("text")
.attr("x", width - 80)
.attr("y", height - 10)
Modify the links to:
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); })
.style("stroke", function(d){
return d.target.name == "Raleigh" ? "green" : "gray";
});
Running code:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://unpkg.com/d3-sankey#0.7.1/build/d3-sankey.js"></script>
</head>
<body>
<svg width="500" height="500"></svg>
<script>
var svg = d3.select("svg").attr("style", "outline: thin solid grey;"),
width = +svg.attr("width"),
height = +svg.attr("height");
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var school = {"nodes": [
{"name":"Happiness Index"}, // 0
{"name":"Business Cost Index"}, // 1
{"name":"Home Price to Income"}, // 2
{"name":"Population Growth"}, // 3
{"name":"3 Year GDP Growth"}, // 4
{"name":"Percent with Degree"}, // 5
{"name":"Austin"}, // 6
{"name":"Nashville"}, // 7
{"name":"Atlanta"}, // 8
{"name":"Raleigh"}, // 9
{"name":"Washington DC"}, // 10
],
"links":[
// From Happiness
{"source":0,"target":6,"value":97},
{"source":0,"target":9,"value":100},
{"source":0,"target":10,"value":96},
// From Business Cost
{"source":1,"target":9,"value":87},
{"source":1,"target":8,"value":88},
{"source":1,"target":7,"value":99},
// From PTI
{"source":2,"target":8,"value":86},
// From Pop Growth
{"source":3,"target":9,"value":87},
{"source":3,"target":6,"value":94},
// From 3yrgdp
{"source":4,"target":9,"value":100},
{"source":4,"target":6,"value":88},
{"source":4,"target":7,"value":96},
// From percent undergrad
{"source":5,"target":9,"value":85},
{"source":5,"target":10,"value":100},
]};
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]]);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 15)
.selectAll("g");
sankey(school);
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); })
.style("stroke", function(d){
return d.target.name == "Raleigh" ? "green" : "gray";
})
// link hover values
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(school.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return color(d.name.replace(/ .*/, "")); })
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
svg.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("class", "graphTitle")
.text(" ");
svg.append("text")
.attr("x", width - 80)
.attr("y", height - 10)
</script>
</body>
</html>
I am trying to plot an heatmap of my CSV file.
In each box of the heatmap I would like also to write the numerical value associated to that box.
This code does the job but the numbers are hidden behind the boxes. How can I change it so that the numbers appear above the boxes?
const margin = { top: 50, right: 0, bottom: 100, left: 130 },
xLocLabel = 10
width = 700 - margin.left - margin.right,
height = 3000 - margin.top - margin.bottom,
gridSize = Math.floor(width / 24),
legendElementWidth = gridSize*2,
buckets = 9,
colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"], // alternatively colorbrewer.YlGnBu[9]
datasets = ["num_machines_full.csv"];
const svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right + 20)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const type = (d) => {
return {
username: d.UserName,
x: +d.Hour,
y: +d.Y,
value: +d.value
};
};
const heatmapChart = function(csvFile) {
d3.csv(csvFile, type, (error, data) => {
const colorScale = d3.scaleQuantile()
.domain([0, buckets - 1, d3.max(data, (d) => d.value)])
.range(colors);
console.log(data.length)
const cards = svg.selectAll(".boxColors")
.data(data, (d) => d.y+':'+d.x);
var valuesText = svg.selectAll(".valuesText")
.data(data)
.enter()
.append("text");
valuesText.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("x", function(d){ return 35 + d.x*gridSize})
.attr("y", function(d){ return d.y*gridSize})
.text(function(d){ return d.value})
.attr("class", "parentText");
var users = [...new Set(data.map(function(d) {return d.username}))];
var firstOccurrence = users.map(function(d) {return data.find(function(e) {
return e.username === d})});
const yLabels = svg.selectAll(".yLabel")
.data(firstOccurrence)
.enter().append("text")
.text(function (d) { return d.username; })
.attr("x", xLocLabel)
.attr("y", (d, i) => (i+1) * gridSize)
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize / 1.5 + ")")
.attr("class", "dayLabel mono axis");
var times = [...new Set(data.map(function(d) {return d.x}))];
var firstOccurrence = times.map(function(d) {return data.find(function(e) {
return e.x === d})});
const xLabels = svg.selectAll(".xLabel")
.data(times)
.enter().append("text")
.text((d) => d)
.attr("x", (d, i) => (i+1) * gridSize)
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + gridSize / 2 + ", -6)")
.attr("class", "timeLabel mono axis");
cards.append("title");
cards.enter().append("rect")
.attr("x", (d) => (d.x + 1) * gridSize)
.attr("y", (d) => (d.y + 1) * gridSize)
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0])
.merge(cards)
.transition()
.duration(1000)
.style("fill", (d) => colorScale(d.value));
cards.select("title").text((d) => d.value);
cards.exit().remove();
const legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), (d) => d);
const legend_g = legend.enter().append("g")
.attr("class", "legend");
legend_g.append("rect")
.attr("x", (d, i) => legendElementWidth * i)
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridSize / 2)
.style("fill", (d, i) => colors[i]);
legend_g.append("text")
.attr("class", "mono")
.text((d) => "≥ " + Math.round(d))
.attr("x", (d, i) => legendElementWidth * i)
.attr("y", height + gridSize);
legend.exit().remove();
});
};
heatmapChart(datasets[0]);
const datasetpicker = d3.select("#dataset-picker")
.selectAll(".dataset-button")
.data(datasets);
datasetpicker.enter()
.append("input")
.attr("value", (d) => "Dataset " + d)
.attr("type", "button")
.attr("class", "dataset-button")
.on("click", (d) => heatmapChart(d));
The final result should be similar to : https://i.stack.imgur.com/fUfuu.png
In an SVG whatever is painted last stays on top, just like a real painter using ink in a canvas.
That being said, this...
var valuesText = svg.selectAll(".valuesText")
.data(data)
.enter()
.append("text");
.... has to come after this:
cards.enter().append("rect")
I am able to append a rectangle to group on drag drop but it is not working properly. If I drag and drop multiple rectangles on to group, the rectangles are dislocated at different location. I am sure there must be some thing wrong.
Demo: http://jsfiddle.net/wqvLLbLa/
code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
var rect = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 50)
.attr("width", 51)
.attr("height", 41)
.attr("rx", 10)
.attr("stroke-width", 2)
.attr("stroke", "#7E7E7E")
.style('cursor', 'move')
.style("fill", "white");
function moveRect() {
d3.select(this)
.attr('x', d3.event.x)
.attr('y', d3.event.y);
}
var dragGroup = d3.behavior.drag()
.origin(function () {
var g = this;
return {x: d3.transform(g.getAttribute("transform")).translate[0],
y: d3.transform(g.getAttribute("transform")).translate[1]};
})
.on("drag", function (d, i) {
g = this;
translate = d3.transform(g.getAttribute("transform")).translate;
console.log(translate);
x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
d3.select(g).attr("transform", "translate(" + x + "," + y + ")");
d3.event.sourceEvent.stopPropagation();
});
var group = svgContainer.append("g")
.attr("id", "mygroup")
.call(dragGroup)
.style('cursor', 'move')
.attr("transform", "translate(20, 20)");
group.append("rect")
.attr("x", 250)
.attr("y", 250)
.attr("width", 151)
.attr("height", 141)
.attr("rx", 10)
.attr("stroke-width", 2)
.attr("stroke", "#7E7E7E")
.style("fill", "white");
var circleDrag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("cx"), y: t.attr("cy")};
})
var rectDrag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on('dragend', function (d) {
var mouseCoordinates = d3.mouse(this);
var groupTransform = d3.transform(group.attr("transform"));
var groupX = groupTransform.translate[0];
var groupY = groupTransform.translate[1];
var rect = group.select("rect");
var rectX = +rect.attr("x");
var rectY = +rect.attr("y");
var rectWidth = +rect.attr("width");
var rectHeight = +rect.attr("height");
if (mouseCoordinates[0] > groupX + rectX
&& mouseCoordinates[0] < groupX + rectX + rectWidth
&& mouseCoordinates[1] > groupY + rectY
&& mouseCoordinates[1] < groupY + rectY + rectHeight) {
//Append new element
var newRect = d3.select("g").append("rect")
.classed("drg", true)
.attr("x", 100)
.attr("y", 100)
.attr("rx", 10)
.attr("width", 51)
.attr("height", 41)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483");
}
else
{
var newRect = d3.select("svg").append("rect")
.classed("drg", true)
.attr("x", 100)
.attr("y", 100)
.attr("rx", 10)
.attr("width", 51)
.attr("height", 41)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483")
.call(
d3.behavior.drag()
.on('drag', moveRect).origin(function () {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
}));
}
});
rect.call(rectDrag);
Responding to the update question - as in the comments.
The reason for the duplicates is because you are appending a new element to the targetG after your bounds check, instead of targetCircle.
The easiest way to fix this would be to simply remove targetCircle after appending the new circle like so
targetCircle.remove();
Fiddle - http://jsfiddle.net/afmLhofL/
I've followed the tutorial to make a bar chart from Scott Murray from alignedleft. I'm having problem with my dataset and adding the dataset to a bar as text.
The image below: 1 bar chart: from the tutorial , 2nd bar chart: how I want to display the text.
Here's my code so far:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tutorial d3 bar chart!</title>
<script type="text/javascript" src="d3/d3.v3.js"></script>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 100;
var i = 0;
var barPadding = 1;
var dataset = [
{key:"Department1", value:6234490},
{key:"Department 2", value:9700},
{key:"Department 3", value:2954},
];
//Width and height
var w = 500;
var h = 100;
var barPadding = 1;
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
for(int i = 0; i < dataset.length; i++){
return d[i].key;
}
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
})
.attr("y", function(d) {
return h - (d * 4) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
</script>
</body>
</html>
I've tried to add the text in this part:
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
for(int i = 0; i < dataset.length; i++){
return d[i].key;
}
})
But that just gives me this error:
I hope you guys can help me out.
Try changing int to var, int doesn't exist in javascript.
Every function in d3js provides access to data and the index.
Just use this
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d){return d.key;}
})
EDIT
svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.append("text")
.text(function(d) {
return d.key;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
})
.attr("y", function(d) {
return h - (d * 4) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");