How to add non linear curve between any two points of area chart in d3.js - svg

I recently started with d3.js.I am working on a stacked area chart in d3 which looks similar to the below chart,
const stack = d3.stack().keys(["aData", "bData"]);
const stackedValues = stack(data);
const stackedData = [];
stackedValues.forEach((layer, index) => {
const currentStack = [];
layer.forEach((d, i) => {
currentStack.push({
values: d,
year: data[i].year
});
});
stackedData.push(currentStack);
});
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(stackedValues[stackedValues.length - 1], dp => dp[1])]);
const xScale = d3
.scaleLinear()
.range([0, width])
.domain(d3.extent(data, dataPoint => dataPoint.year));
const area = d3
.area()
.x(dataPoint => xScale(dataPoint.year))
.y0(dataPoint => yScale(dataPoint.values[0]))
.y1(dataPoint => yScale(dataPoint.values[1]));
const series = grp
.selectAll(".series")
.data(stackedData)
.enter()
.append("g")
.attr("class", "series");
series
.append("path")
.attr("transform", `translate(${margin.left},0)`)
.style("fill", (d, i) => color[i])
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", strokeWidth)
.attr("d", d => area(d));
I have a requirement to be able to add non linear curve between any two points. I have made a very basic outline chart just to explain my point.
I tried using the curve function but it changes the whole line to the provided curve (here is the example code https://codepen.io/saif_shaik/pen/VwmqxMR), I just need to add a non linear curve between two points. is there any way to achieve this?

I simplied your path by removing precision in: https://yqnn.github.io/svg-path-editor/
You can use that editor to play with d-path, and learn where/how you want to change that d-path String.
Copy the d-path below and paste it in: https://yqnn.github.io/svg-path-editor/
<svg height="300" width="600"><g transform="translate(30,0)">
<g transform="translate(-28.5,-90)">
<g class="series">
<path stroke="steelblue" stroke-linejoin="round"
stroke-linecap="round" stroke-width="1.5"
d="M0 257 15 250C30 242 61 227 91 216C122 205 152 197 182 199C213 200 243 211 274 208
C304 205 334 188 365 169C395 151 425 129 456 116C486 102 517 96 532 93
L547 90 547 280 532 280C517 280 486 280 456 280C425 280 395 280 365 280
C334 280 304 280 273 280C243 280 213 280 182 280C152 280 122 280 91 280
C61 280 30 280 15 280L0 280Z"
style="fill: lightgreen;">
</path></g></g></g></svg>

You could create a custom curve generator. This could take a number of different forms. I'll recycle a previous example by tweaking one of the existing d3 curves and using its point method to create a custom curve.
Normally a custom curve applies the same curve between all points, to allow different types of lines to connect points, I'll keep track of the current point's index in the snippet below.
The custom curve in the snippet below is returned by a parent function that takes an index value. This index value indicates which data point should use a different curve between it and the next data point. The two types of curves are hand crafted - some types curves will present more challenges than others.
This produces a result such as:
function generator(i,context) {
var index = -1;
return function(context) {
var custom = d3.curveLinear(context);
custom._context = context;
custom.point = function(x,y) {
x = +x, y = +y;
index++;
switch (this._point) {
case 0: this._point = 1;
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
this.x0 = x; this.y0 = y;
break;
case 1: this._point = 2;
default:
// curvy mountains between values if index isn't specified:
if(index != i+1) {
var x1 = this.x0 * 0.5 + x * 0.5;
var y1 = this.y0 * 0.5 + y * 0.5;
var m = 1/(y1 - y)/(x1 - x);
var r = -100; // offset of mid point.
var k = r / Math.sqrt(1 + (m*m) );
if (m == Infinity) {
y1 += r;
}
else {
y1 += k;
x1 += m*k;
}
this._context.quadraticCurveTo(x1,y1,x,y);
// always update x and y values for next segment:
this.x0 = x; this.y0 = y;
break;
}
// straight lines if index matches:
else {
// the simplest line possible:
this._context.lineTo(x,y);
this.x0 = x; this.y0 = y;
break;
}
}
}
return custom;
}
}
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);
var data = d3.range(10).map(function(d) {
var x = d*40+40;
var y = Math.random() * 200 + 50;
return { x:x, y:y }
})
var line = d3.line()
.curve(generator(3)) // striaght line between index 3 and 4.
.x(d=>d.x)
.y(d=>d.y)
svg.append("path")
.datum(data)
.attr("d",line)
.style("fill","none")
.style("stroke-width",3)
.style("stroke","#aaa")
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx",d=>d.x)
.attr("cy",d=>d.y)
.attr("r", 2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
This line will work with canvas as well if you specify a context as the second argument of generator(). There are all sorts of refinements that could be made here - the basic principle should be fairly adaptable however.

Just change the curve type. The curveBasis approximates a curve between points but it doesn't cross them. So use either the usually used "curveCardinal" type or maybe even "curveCatmullRom" that are curves passing the data points.
// Fake data
const data = [
{
year: 2000,
aData: 50,
bData: 300
},
{
year: 2001,
aData: 150,
bData: 50
},
{
year: 2002,
aData: 200,
bData: 100
},
{
year: 2003,
aData: 130,
bData: 50
},
{
year: 2004,
aData: 240,
bData: 80
},
{
year: 2005,
aData: 380,
bData: 10
},
{
year: 2006,
aData: 420,
bData: 200
}
];
const color = ["lightgreen", "lightblue"];
// Create SVG and padding for the chart
const svg = d3
.select("#chart")
.append("svg")
.attr("height", 300)
.attr("width", 600);
const strokeWidth = 1.5;
const margin = { top: 0, bottom: 20, left: 30, right: 20 };
const chart = svg.append("g").attr("transform", `translate(${margin.left},0)`);
const width = +svg.attr("width") - margin.left - margin.right - strokeWidth * 2;
const height = +svg.attr("height") - margin.top - margin.bottom;
const grp = chart
.append("g")
.attr("transform", `translate(-${margin.left - strokeWidth},-${margin.top})`);
// Create stack
const stack = d3.stack().keys(["aData", "bData"]);
const stackedValues = stack(data);
const stackedData = [];
// Copy the stack offsets back into the data.
stackedValues.forEach((layer, index) => {
const currentStack = [];
layer.forEach((d, i) => {
currentStack.push({
values: d,
year: data[i].year
});
});
stackedData.push(currentStack);
});
// Create scales
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(stackedValues[stackedValues.length - 1], dp => dp[1])]);
const xScale = d3
.scaleLinear()
.range([0, width])
.domain(d3.extent(data, dataPoint => dataPoint.year));
const area = d3
.area()
.x(dataPoint => xScale(dataPoint.year))
.y0(dataPoint => yScale(dataPoint.values[0]))
.y1(dataPoint => yScale(dataPoint.values[1]))
//.curve(d3.curveBasis)
.curve(d3.curveCardinal)
//.curve(d3.curveCatmullRom.alpha(0.5))
;
const series = grp
.selectAll(".series")
.data(stackedData)
.enter()
.append("g")
.attr("class", "series");
series
.append("path")
.attr("transform", `translate(${margin.left},0)`)
.style("fill", (d, i) => color[i])
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", strokeWidth)
.attr("d", d => area(d));
const dotsGreen = chart
.selectAll(".gdot")
.data(data)
.enter()
.append("circle")
.attr("class", "gdot")
.attr("cx", function(d) {
return xScale(d.year)
})
.attr("cy", d => yScale(d.aData))
.attr("r", 4)
.attr("fill", "green");
const dotsBlue = chart
.selectAll(".bdot")
.data(data)
.enter()
.append("circle")
.attr("class", "bdot")
.attr("cx", function(d) {
return xScale(d.year)
})
.attr("cy", d => yScale(d.aData+d.bData))
.attr("r", 4)
.attr("fill", "blue");
// Add the X Axis
chart
.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).ticks(data.length));
// Add the Y Axis
chart
.append("g")
.attr("transform", `translate(0, 0)`)
.call(d3.axisLeft(yScale));
#chart {
text-align: center;
margin-top: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"></div>
The working codePen forked from your code is also here:
changed CodePen pen
Note: I've added the code for two circle groups (not SVG G element) that you can simply remove. They just serve for a proof where curves are drawn near data points based on curve type scripted.
Your sketched chart looks like you want to have a curve only between two given points. For that you would have to change the curve call to use the running index (e.g. (d,i)) in a function that would return different curve type based on chosen index (or indeces).
ADDED: you can play with different D3.js curve types here:
D3 curve explorer

Related

SVG : How to convert meters to points ratio

I have a building floor map in SVG whith these size attributes:
width="594.75pt" height="841.5pt"
The size of the map, is in meters : 40x52.
What is the correct way to convert meters to points ?
Here is what I've tried so far :
XmlTextReader reader = new XmlTextReader(pathToSvg);
while (reader.Read() && string.IsNullOrEmpty(width) && string.IsNullOrEmpty(height))
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name == #"svg")
{
width = reader.GetAttribute(#"width");
height = reader.GetAttribute(#"height");
}
break;
}
}
// Remove pt from width and height strings
width = width.Replace("pt", string.Empty);
height = height.Replace("pt", string.Empty);
// Convert to double values
double widthInPoint = double.Parse(width, CultureInfo.InvariantCulture);
double heightInPoint = double.Parse(height, CultureInfo.InvariantCulture);
// compute the ratio meters/points in both dimensions <=== Is this section right ??
// 594.75pt => 40 meters
// 1pt => X meters
double ratioX = mapHorizontalMeterSize / widthInPoint;
double ratioY = mapVerticalMeterSize / heightInPoint;
// Compute the Beacon position in points
double radiusInPoint = Math.Round(radius / ratioX, 2);
double beaconXPositionOnMapInPt = Math.Round((customMapX / ratioX) - (radius / ratioX), 2);
double yPos = Math.Round((customMapY / ratioY) - (radius / ratioY), 2);
// SVG positioning is top left corner by default, we are bottom left (originCornerId == 0)
double beaconYPositionOnMapInPt = originCornerId == 0 ? heightInPoint - yPos : yPos;
string tmpPath;
var reaaderSettings = new XmlReaderSettings();
reaaderSettings.DtdProcessing = DtdProcessing.Parse;
using (var svgReader = XmlReader.Create(path, reaaderSettings))
{
XDocument doc = XDocument.Load(svgReader);
var xmlns = doc.Root.GetDefaultNamespace();
var xlinkns = doc.Root.GetNamespaceOfPrefix("xlink");
// Add the circle
doc.Root.Add(new XElement(xmlns + "circle",
new XAttribute("stroke", "blue"),
new XAttribute("stroke-width", "3"),
new XAttribute("cx", $"{beaconXPositionOnMapInPt.ToString(CultureInfo.InvariantCulture)}pt"),
new XAttribute("cy", $"{beaconYPositionOnMapInPt.ToString(CultureInfo.InvariantCulture)}pt"),
new XAttribute("r", $"{radiusInPoint.ToString(CultureInfo.InvariantCulture)}"),
new XAttribute("fill-opacity", "0.1")
));
// Add the beacon image
//XNamespace xlinkns = "https://www.w3.org/1999/xlink";
doc.Root.Add(new XElement(xmlns + "image",
new XAttribute("x", $"{beaconXPositionOnMapInPt.ToString(CultureInfo.InvariantCulture)}pt"),
new XAttribute("y", $"{yPos.ToString(CultureInfo.InvariantCulture)}pt"),
new XAttribute(xlinkns + "href", $"data:image/{iconFormat};base64,{icon}")
));
tmpPath = FileHelpers.NextAvailableFilename(path);
doc.Save(tmpPath);
}
The result is absolutely not what I'm expecting.
The svg file is almost 3Mb in size and I can't show it here.
If you need to add new elements with dimensions specified in meters you could use a conversion helper function to find the right scaling multiplier/divisor
If your svg's dimensions are 594.8 × 841.5 user units
594.8 × 841.5 pt (real life print format - A4)
containing a map that is 40×52m in real life:
Meter to point multiplier: 2834.64388 (for converting meters to points/user units)
Scaling divisor: 40*2834.64388 / 594.8
Js example
I'm adding a rectangle at x/y=20m; width/height=10m; using my helper function
m2UserUnits('20m', scaleDivisor) that will convert meter to user units.
let svg = document.querySelector('svg');
let realWidth = '40m';
let userWidth = '594.8pt';
let m2PtMultiplier = 2834.64388;
let scaleDivisor = parseFloat(realWidth)*m2PtMultiplier / parseFloat(userWidth);
//append to svg
let ns ='http://www.w3.org/2000/svg';
let rect = document.createElementNS(ns, 'rect');
rect.setAttribute('x', m2UserUnits('20m', scaleDivisor) );
rect.setAttribute('y', m2UserUnits('20m', scaleDivisor) );
rect.setAttribute('width', m2UserUnits('10m', scaleDivisor) );
rect.setAttribute('height', m2UserUnits('10m', scaleDivisor) );
svg.appendChild(rect)
let circle = document.createElementNS(ns, 'circle');
circle.setAttribute('cx', m2UserUnits('5m', scaleDivisor) );
circle.setAttribute('cy', m2UserUnits('5m', scaleDivisor) );
circle.setAttribute('r', m2UserUnits('5m', scaleDivisor) );
svg.appendChild(circle)
//unit conversion
function m2UserUnits(val, scaleDivisor){
let valNum = parseFloat(val);
if(val.indexOf('m')!==-1){
valNum *= 2834.64388;
}
return valNum/scaleDivisor;
}
svg{
border: 1px solid red;
width:40%;
}
text{
font-size:32px
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 594.8 841.5" >
<rect id="bg-A4" fill="#FFFFFF" width="594.8" height="841.5"/>
<rect id="map-40x52m" fill="#EEEEEE" width="594.8" height="773.2"/>
<text x="50%" y="90%" text-anchor="middle">Map area: 40×52m (real life)</text>
<text x="50%" y="98%" text-anchor="middle">svg viewport: 594.8 × 841.5 user units</text>
</svg>

Add constant horizontal line

I drew a multi-line chart that changes based on some user input. I want to add a reference line to always appear at the value y=100. I was able to manually place a line, but it is not always exactly at y=100.
Formatting issues aside, this is what I have for one possible user input. As you can see, the reference line is slightly below 100:
And my code:
const svg = d3.select("svg");
const width = +svg2.attr("width");
const height = +svg2.attr("height");
const render = data =>{
const xValue = d => +d.week;
const yValue = d => +d.power_score;
const margin = {top:50, right:70, bottom:60, left:20};
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const colorValue = d => d.team;
// define scales
const xScale = d3.scaleLinear()
.domain([1, d3.max(data, xValue)])
.range([0, innerWidth-250])
.nice();
const yScale = d3.scaleLinear()
.domain([d3.min(data, yValue)-10, d3.max(data, yValue)+10])
.range([innerHeight, 0])
.nice();
const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
const g = svg2.append("g")
.attr('transform', 'translate(75, 50)');
// create axes
const xAxis = d3.axisBottom(xScale)
.tickSize(-innerHeight-10);
const yAxis = d3.axisLeft(yScale)
.tickSize(-innerWidth+240);
const xAxisG = g.append("g").call(xAxis)
.attr("transform", "translate(0, 400)");
xAxisG.select(".domain")
.remove();
xAxisG.append("text")
.attr("class", "axis-label")
.attr("y", 40)
.attr("x", (innerWidth-250)/2)
.attr("fill", "black")
.text("Week");
const yAxisG = g.append("g").call(yAxis)
.attr("transform", "translate(-10, 0)")
.select(".domain")
.remove();
yAxisG.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -35)
.attr("x", -innerHeight/4)
.attr("fill", "black")
.text("Power Score");
// generate line
const lineGenerator = d3.line()
.x(d => xScale(xValue(d)))
.y(d => yScale(yValue(d)));
// sort data for legend
const lastYValue = d =>
yValue(d.values[d.values.length - 1]);
// group data
const nested = d3.nest()
.key(colorValue)
.entries(data)
.sort((a, b) =>
d3.descending(lastYValue(a), lastYValue(b)));
colorScale.domain(nested.map(d => d.key));
// manually add horizonal line here
svg2.append("g")
.attr("transform", "translate(75, 267)")
.append("line")
.attr("x2", innerWidth-250)
.style("stroke", "black")
.style("stroke-width", "2px")
.style("stroke-dasharray", "3, 3");
// add lines with mouseover effect
g.selectAll(".line-path").data(nested)
.enter().append("path")
.attr("class", "line-path")
.attr("d", d => lineGenerator(d.values))
.attr("stroke", d => colorScale(d.key))
.attr("stroke-width", "3")
.attr("opacity", "0.5")
.on("mouseover", function(d, i) {
d3.select(this).transition()
.duration("50")
.attr("stroke-width", "5")
.attr("opacity", "1")})
.on("mouseout", function(d, i) {
d3.select(this).transition()
.duration("50")
.attr("stroke-width", "3")
.attr("opacity", "0.5")});
d3.line()
.x(d => xScale(xValue(d)))
.y(d => yScale(yValue(d)));
// draw legend
const colorLegend = (selection, props) => {
const {
colorScale,
circleRadius,
spacing,
textOffset
} = props;
const groups = selection.selectAll('g')
.data(colorScale.domain());
const groupsEnter = groups
.enter().append('g')
.attr('class', 'tick');
groupsEnter
.merge(groups)
.attr('transform', (d, i) =>
`translate(0, ${i * spacing})`
);
groups.exit().remove();
groupsEnter.append('circle')
.merge(groups.select('circle'))
.attr('r', circleRadius)
.attr('fill', colorScale);
groupsEnter.append('text')
.merge(groups.select('text'))
.text(d => d)
.attr('dy', '0.32em')
.attr('x', textOffset);
}
svg2.append("g")
.attr("transform", "translate(710, 60)")
.call(colorLegend, {
colorScale,
circleRadius: 4,
spacing: 15,
textOffset: 8
});
// Title
g.append("text")
.attr("class", "title")
.attr("x", (innerWidth-250)/4)
.attr("y", -10)
.text("Weekly Power Ranks");
};
d3.csv('data.csv').then(data => {
data.forEach(d => {
d.week = +d.week;
d.power_score = +d.power_score;
});
render(data);
});
Instead of using magic numbers...
svg2.append("g")
.attr("transform", "translate(75, 267)")
//magic numbers---------------^----^
...use the same y scale you're using to paint the paths:
g.append("g")
.attr("transform", `translate(75, ${yScale(100)})`)
Also, append it to the already translated <g>, or apply the same translation (again, more magic numbers in your code...).

Problems loading CSV data in D3. svg.selectAll(...).data(...).enter is not a function

I am trying to load a CSV data set into d3 by assigning it to a variable, but it seems that I keep receiving an error saying that enter() is not a function. I think the issue lies in the way I'm loading the CSV data.
For reference, I'm following this tutorial: http://duspviz.mit.edu/d3-workshop/scatterplots-and-more/
Here is my code for reference.
var ratData = [];
d3.csv("rat-data.csv", function(d) {
return {
city : d.city, // city name
rats : +d.rats // force value of rats to be number (+)
};
}, function(error, rows) { // catch error if error, read rows
ratData = rows; // set ratData equal to rows
console.log(ratData);
createVisualization(); // call function to create chart
});
function createVisualization(){
// Width and height of SVG
var w = 150;
var h = 175;
// Get length of dataset
var arrayLength = ratData.length; // length of dataset
var maxValue = d3.max(ratData, function(d) { return +d.rats;} ); // get maximum
var x_axisLength = 100; // length of x-axis in our layout
var y_axisLength = 100; // length of y-axis in our layout
// Use a scale for the height of the visualization
var yScale = d3.scaleLinear()
.domain([0, maxValue])
.range([0, y_axisLength]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Select and generate rectangle elements
svg.selectAll( "rect" )
.data( ratData )
.enter()
.append("rect")
.attr( "x", function(d,i){
return i * (x_axisLength/arrayLength) + 30; // Set x coordinate of rectangle to index of data value (i) *25
})
.attr( "y", function(d){
return h - yScale(d.rats); // Set y coordinate of rect using the y scale
})
.attr( "width", (x_axisLength/arrayLength) - 1)
.attr( "height", function(d){
return yScale(d.rats); // Set height of using the scale
})
.attr( "fill", "steelblue");
// Create y-axis
svg.append("line")
.attr("x1", 30)
.attr("y1", 75)
.attr("x2", 30)
.attr("y2", 175)
.attr("stroke-width", 2)
.attr("stroke", "black");
// Create x-axis
svg.append("line")
.attr("x1", 30)
.attr("y1", 175)
.attr("x2", 130)
.attr("y2", 175)
.attr("stroke-width", 2)
.attr("stroke", "black");
// y-axis label
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.text("No. of Rats")
.attr("transform", "translate(20, 20) rotate(-90)")
.attr("font-size", "14")
.attr("font-family", "'Open Sans', sans-serif");
}; // end of function

Transform <g> attribute on map of d3.js

I have prepared one example of map in d3.js. I wanted to implement zoom on map with and node(contains circle, smiley and text. as of now i putted circle and smiley) on map shows the city of different countries. When i zoom over map i could not able to transform the tag so smiley got misplace as per my logic. so how to transform only g tags on map. I don't want to transform shape(circle, images) inside tag.
My Jsfiddle link
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll(".node")
.attr("width", function(){
var self = d3.select(this);
var r = 28 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
g.selectAll(".circle")
.attr("r", function(){
var self = d3.select(this);
var r = 8 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
});
Please anybody help me to solve my issue.
To do semantic zooming, one would need to adjust the width and height of the smiley faces as well as adjust the x and y locations since the adjustments will change relative to the width/height:
g.selectAll(".node")
.attr("x", function(d) { return (projection([d.lon, d.lat])[0]) - (8 / d3.event.scale); })
.attr('y', function(d) { return (projection([d.lon, d.lat])[1]) - (8 / d3.event.scale); })
.attr('width', function () { return 20 / d3.event.scale; })
.attr('height', function () { return 20 / d3.event.scale; })
Demo: http://jsfiddle.net/ktee4dLp/

Problems when interactively resizing and moving svg rectangles with D3

I am trying to create panel components that will hold some visualizations.
I am making the panel component with svgs. They look ok, but I am getting some weird behavior when resizing and moving the panels.
var groups = ["uno", "dos", "tres", "cuatro"];
var w = 350;
var x = 0;
var y = 0;
var width = 800;
var h = 200;
var height = 800;
var val = [];
var drag = d3.behavior.drag()
.origin(Object)
.on("drag", move);
var resize = d3.behavior.drag()
.origin(Object)
.on("drag", dragResize);
svg = d3.select("body").append("div").append("svg");
charts = svg.selectAll("g.chart")
.data(groups); //(dims);
box = charts.enter()
.append("g").classed("chart", true)
.attr("id", function(d,i) { return "box"+i})
//.data([{x: 95, y: 0}]);
box.append("rect").classed("box", true)
var t = box.append("rect").classed("titleBox", true)
t.call(drag);
box.append("text").classed("title", true).data(groups)
box.append("text").classed("legend", true).data(groups)
box.append("rect").classed("icon", true)
.call(resize);
box.selectAll("rect.box")
.data([{x: 95, y: 0}])
.attr({
x: function(d) { return d.x; },
y: function(d) { return d.y; },
width: w,
height: function(d) { return 200}//d.length*30 + 60}
})
box.selectAll("rect.titleBox")
.classed("drag", true)
.data([{x: 95, y: 0}])
.attr({
x: function(d) { return d.x; },
y: function(d) { return d.y; },
width: w,
height: 25,
fill: "#000000"
})
box.selectAll("text.title")
.attr({
x: 105,
y: 20,
width: 350,
height: 25,
fill: "#ffffff"
})
.text(function(d) {
console.log("i from title "+ d);
return d;
})
box.selectAll("text.legend")
.attr({
x: 105,
y: 45,
width: 200,
height: 25,
fill: "#999999"
})
.text(function(d) {
return d;
})
box.selectAll("rect.icon")
.data([{x: 429, y: 184}])
.attr({
x: function(d) { return d.x; },
y: function(d) { return d.y; },
width: 16,
height: 16,
fill: "#999999"
})
var dx = 429;
var dy = 184;
function move(){
var dragTarget = d3.select(this);
var dragObject = d3.select(this.parentNode);
console.log("move x:"+x+" y:"+y);
//console.log("d3.event.x:"+d3.event.x+" d3.event.y:"+d3.event.y);
x += d3.event.x - parseInt(dragTarget.attr('x'));
y += d3.event.y - parseInt(dragTarget.attr("y"));
console.log("x:"+x+" y:"+y);
dragObject
.attr("transform", "translate(" + x + "," + y + ")")
};
function dragResize(){
var dragx = Math.max(dx + (16/2), Math.min(w, dx + width + d3.event.dx));
var dragy = Math.max(dy + (16/2), Math.min(h, dy + height + d3.event.dy));
//console.log("resize x:"+x+" y:"+y);
console.log("d3.event.x:"+d3.event.dx+" d3.event.y:"+d3.event.dy);
var dragTarget = d3.select(this);
var dragObject = d3.select(this.parentNode);
var o = dragObject.select("rect.box");
var o1 = dragObject.select("rect.titleBox");
var oldx = dx;
var oldy = dy;
dx = Math.max(0, Math.min(dx + width - (16 / 2), d3.event.x));
dy = Math.max(0, Math.min(dy + height - (16 ), d3.event.y));
w = w - (oldx - dx);
h = h - (oldy - dy);
dragTarget
.attr("x", function(d) { return dragx - (16/2) })
.attr("y", function(d) { return dragy - (16) })
o.attr("width", w)
.attr("height", h);
o1.attr("width", w);
};
I have posted the code at http://jsfiddle.net/dtqY5/
The problem is the following: I can move each panel, by dragging the title area, with no problem. Howvwer, after I resize any of the panels, I cannot move them anymore. They jump to their original position. The x and y becones NaN, but I cannot understand why.
ANy ideas and suggestions will be welcomed.
D3 uses the drag.origin accessor you provide to calculate an offset. Since the access you provide is just an empty object, this offset is NaN which results in x and y on the event also being NaN.
If you remove drag.origin altogether it uses the current mouse position as the origin which makes the panels jump when you start dragging. If you specify the origin to be the position of the shape being dragged it looks better:
.origin(function() {
var current = d3.select(this);
return {x: current.attr("x"), y: current.attr("y") };
})
Here's an updated fiddle: http://jsfiddle.net/4nvhc/

Resources