Recently I have written a project with D3, so I need a dynamic rectangle. I used Angular to create a dynamic visualization.I have two input type rang, the first one will change the 'width' of rectangle and the second will change the 'height'.However I don't know how to use angular to draw a dynamic rectangle.
This is my code:
<div ng-app="myApp">
<rect-designer/>
<div>
<input type="range" ng-model="rectWidth" min="0" max="400" value="0"/>
<input type="range" ng-model="rectHeight" min="0" max="700" value="0"/>
</div>
</div>
Here is my JavaScript code:
var App = angular.module('myApp', []);
App.directive('rectDesigner', function() {
function link(scope, el, attr) {
var svgwidth=1000, svgheight=600;
var svgContainer = d3.select(el[0]).append('svg').attr('id','svgcontainer')
.attr({ width: svgwidth, height: svgheight });
scope.$watchGroup(['rectWidth','rectHeight'], function () {
svgContainer.append("rect").attr("id", "Rect")
.attr({ width: rectWidth, height: rectHeigh })
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
},true);
}return {
link: link,
scope: {
rectHeigh: '=',
rectWidth: '=',
},
restrict: 'E'
};
});
I don't know if there is any way to make svgWidth and svgheight dynamic, I used this code but the result was undefined.
scope.$watch(function(){
svgWidth = el.clientWidth;
svgHeight = el.clientHeight;
});
You are missing some basics here:
You don't have a controller.
The variables you are watching are not part of your directive but they should be part of that missing controller.
Since these variables aren't part of the directive, there's no need to return them into it's scope (again, they will be in the controller).
$scope.watchGroup has a callback with a function of newValues. This is where the changed variables will be.
You want to append the rect to the svg and then manipulate it's width/height. You don't want to re-append it each time the width/height changes.
So putting all this together:
var App = angular.module('myApp', []);
var Ctrl = App.controller('myCtrl', ['$scope', function($scope) {
// I always like to give them defaults in code
$scope.rectWidth = 50;
$scope.rectHeight = 50;
}]);
Ctrl.directive('rectDesigner', function() {
function link(scope, el, attr) {
var svgwidth = 500,
svgheight = 600;
var svgContainer = d3.select(el[0])
.append('svg')
.attr('id', 'svgcontainer')
.attr({
width: svgwidth,
height: svgheight
});
// only append one rect
var rect = svgContainer
.append("rect")
.attr("id", "Rect")
.attr('transform', 'translate(' + svgwidth / 2 + ',' + svgheight / 2 + ')');
scope.$watchGroup(['rectWidth', 'rectHeight'], function(newValues) {
var width = newValues[0];
var height = newValues[1];
// now change it's width and height
rect.attr({
width: width,
height: height
});
}, true);
}
return {
link: link,
};
});
Example here.
Related
Currently I am working on a web application using Angularjs. I need to show heat-map on a svg in order to show some analytics. Can anyone suggest me a JavaScript library?
You can create a custom directive for the same. I have used an angularjs minified file...
(URL: https://www.patrick-wied.at/static/heatmapjs/assets/js/heatmap.min.js)
I could not find anything more relevant on github or anywhere else! If you find something, please update the same here.
Anyway, I tried to create a small heatmap using this external file, hope this helps!
var myApp = angular.module('myapp', []);
myApp
.controller('HeatMapCtrl', function($scope) {
//random data
var points = [];
var max = 0;
var width = 840;
var height = 400;
var len = 200;
while (len--) {
var val = Math.floor(Math.random() * 100);
max = Math.max(max, val);
var point = {
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
value: val
};
points.push(point);
}
// heatmap data format
$scope.heat_data = {
max: max,
data: points
};
})
.directive('heatMap', function() {
return {
restrict: 'E',
scope: {
data: '='
},
template: '<div container></div>',
link: function(scope, ele, attr) {
scope.heatmapInstance = h337.create({
container: ele.find('div')[0]
});
scope.heatmapInstance.setData(scope.data);
}
};
});
heat-map {
width: 600px;
height: 300px;
display: block;
}
heat-map div {
height: 100%;
}
<script src="https://www.patrick-wied.at/static/heatmapjs/assets/js/heatmap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="HeatMapCtrl">
<heat-map data="heat_data"></heat-map>
</div>
</div>
so I have this Angular application I'm working on.
I have a controller called visualization and a directive called force-layout.
In the HTML template of the directive I'm creating three buttons and I attach to them three corresponding functions that I define in the directive code:
<div class="panel smallest controls">
<span ng-click="centerNetwork()"><i class="fa fa-arrows-alt" aria-hidden="true"></i></span>
<span ng-click="zoomIn()"><i class="fa fa-search-plus" aria-hidden="true"></i></span>
<span ng-click="zoomOut()"><i class="fa fa-search-minus" aria-hidden="true"></i></span>
</div>
<force-layout ng-if=" config.viewMode == 'individual-force' || config.viewMode == 'individual-concentric' "></force-layout>
The function are defined in the directive like this:
scope.centerNetwork = function() {
console.log("Recenter");
var sourceNode = nodes.filter(function(d) { return (d.id == sourceId)})[0];
svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity.translate(width/2-sourceNode.x, height/2-sourceNode.y));
}
var zoomfactor = 1;
scope.zoomIn = function() {
console.log("Zoom In")
svg.transition().duration(500).call(zoom.scaleBy, zoomfactor + .5);
}
scope.zoomOut = function() {
console.log("Zoom Out")
svg.transition().duration(500).call(zoom.scaleBy, zoomfactor - .25);
}
It does not trigger any error.
It was working before, not it is not and I cannot understand what is causing the problem, any help?
UPDATE: full directive code.
'use strict';
/**
* #ngdoc directive
* #name redesign2017App.directive:forceLayout
* #description
* # forceLayout
*/
angular.module('redesign2017App')
.directive('forceLayout', function() {
return {
template: '<svg width="100%" height="100%"></svg>',
restrict: 'E',
link: function postLink(scope, element, attrs) {
console.log('drawing network the first time');
// console.log(scope.data);
var svg = d3.select(element[0]).select('svg'),
width = +svg.node().getBoundingClientRect().width,
height = +svg.node().getBoundingClientRect().height,
nodes,
links,
degreeSize,
sourceId,
confidenceMin = scope.config.confidenceMin,
confidenceMax = scope.config.confidenceMax,
dateMin = scope.config.dateMin,
dateMax = scope.config.dateMax,
complexity = scope.config.networkComplexity;
var durationTransition = 500;
// A function to handle click toggling based on neighboring nodes.
function toggleClick(d, newLinks, selectedElement) {
// Some code for handling selections cutted out from here
}
svg.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'transparent')
.on('click', function() {
// Clear selections on nodes and labels
d3.selectAll('.node, g.label').classed('selected', false);
// Restore nodes and links to normal opacity. (see toggleClick() below)
d3.selectAll('.link')
.classed('faded', false)
d3.selectAll('.node')
.classed('faded', false)
// Must select g.labels since it selects elements in other part of the interface
d3.selectAll('g.label')
.classed('hidden', function(d) {
return (d.distance < 2) ? false : true;
});
// reset group bar
d3.selectAll('.group').classed('active', false);
d3.selectAll('.group').classed('unactive', false);
// update selction and trigger event for other directives
scope.currentSelection = {};
scope.$apply(); // no need to trigger events, just apply
});
// HERE ARE THE FUNCTIONS I ASKED ABOUT
// Zooming function translates the size of the svg container.
function zoomed() {
container.attr("transform", "translate(" + d3.event.transform.x + ", " + d3.event.transform.y + ") scale(" + d3.event.transform.k + ")");
}
var zoom = d3.zoom();
// Call zoom for svg container.
svg.call(zoom.on('zoom', zoomed)); //.on("dblclick.zoom", null);
//Functions for zoom and recenter buttons
scope.centerNetwork = function() {
console.log("Recenter");
var sourceNode = nodes.filter(function(d) {
return (d.id == sourceId) })[0];
svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity.translate(width / 2 - sourceNode.x, height / 2 - sourceNode.y));
// svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
}
var zoomfactor = 1;
scope.zoomIn = function() {
console.log("Zoom In")
svg.transition().duration(500).call(zoom.scaleBy, zoomfactor + .5);
}
scope.zoomOut = function() {
console.log("Zoom Out")
svg.transition().duration(500).call(zoom.scaleBy, zoomfactor - .25);
}
// TILL HERE
var container = svg.append('g');
// Toggle for ego networks on click (below).
var toggle = 0;
var link = container.append("g")
.attr("class", "links")
.selectAll(".link");
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node");
var label = container.append("g")
.attr("class", "labels")
.selectAll(".label");
var loading = svg.append("text")
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.attr('x', width / 2)
.attr('y', height / 2)
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.text("Simulating. One moment please…");
var t0 = performance.now();
var json = scope.data;
// graph = json.data.attributes;
nodes = json.included;
links = [];
json.data.attributes.connections.forEach(function(c) { links.push(c.attributes) });
sourceId = json.data.attributes.primary_people;
// d3.select('.legend .size.min').text('j')
var simulation = d3.forceSimulation(nodes)
// .velocityDecay(.5)
.force("link", d3.forceLink(links).id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody().strength(-75)) //.distanceMax([500]))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide().radius(function(d) {
if (d.id == sourceId) {
return 26;
} else {
return 13;
}
}))
// .force("x", d3.forceX())
// .force("y", d3.forceY())
.stop();
for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
simulation.tick();
}
loading.remove();
var t1 = performance.now();
console.log("Graph took " + (t1 - t0) + " milliseconds to load.")
function positionCircle(nodelist, r) {
var angle = 360 / nodelist.length;
nodelist.forEach(function(n, i) {
n.fx = (Math.cos(angle * (i + 1)) * r) + (width / 2);
n.fy = (Math.sin(angle * (i + 1)) * r) + (height / 2);
});
}
function update(confidenceMin, confidenceMax, dateMin, dateMax, complexity, layout) {
// some code for visualizing a force layout cutted out from here
}
// Trigger update automatically when the directive code is executed entirely (e.g. at loading)
update(confidenceMin, confidenceMax, dateMin, dateMax, complexity, 'individual-force');
// update triggered from the controller
scope.$on('Update the force layout', function(event, args) {
console.log('ON: Update the force layout')
confidenceMin = scope.config.confidenceMin;
confidenceMax = scope.config.confidenceMax;
dateMin = scope.config.dateMin;
dateMax = scope.config.dateMax;
complexity = scope.config.networkComplexity;
update(confidenceMin, confidenceMax, dateMin, dateMax, complexity, args.layout);
});
}
};
});
So I think I found the problem.
The directive was invoked by the <force-layout> tag, which presented the ng-if='some-condition' directive.
For some reason, while the controller code is evaluated, that condition doesn't prove to be true, and the directive as a whole, meaning its HTML and its JS, is simply skipped: the ng-click is then obviously not able to attach the click event to a function that does not exists yet.
Replacing ng-if with ng-show solved the issue: apparently in this case the code is executed immediately and the directive exists hidden, with all its declared properties.
I have the following data in a csv file called BarData.csv:
Fruit,dt,amount
Apple,12/28/2016,-1256
Apple,12/29/2016,-500
Apple,12/30/2016,3694
Apple,12/31/2016,5586
Apple,1/1/2017,4558
Apple,1/2/2017,6696
Apple,1/3/2017,7757
Apple,1/4/2017,8528
Apple,1/5/2017,5543
Apple,1/6/2017,3363
Apple,1/7/2017,5464
Pear,12/25/2017,250
Pear,12/26/2017,669
Pear,12/27/2017,441
Pear,12/28/2017,159
Pear,12/29/2017,357
Pear,12/30/2017,775
Pear,12/31/2017,669
The following html, css, and javascript is in one .html file:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>BAR SINGLE FUNCTION</title>
<script src="http://d3js.org/d3.v3.js"></script>
<style type="text/css">
#radioDiv {
top: 45px;
font-family: verdana;
font-size: 8px;
width: 455px;
}
#TOPbarChart {
position: absolute;
top: 50px;
left: 30px;
width: 750px;
height: 195px;
}
.axis--y path,
.axis--x path {
display: none;
}
.axis--x line,
.axis--y line {
stroke: black;
fill: none;
stroke-width: 2px
}
.yAxis text,
.xAxis text {
font: 7pt Verdana;
stroke: none;
fill: black;
}
.title,
.titleX {
font-family: Verdana;
font-size: 10px;
}
</style>
</head>
<body>
<div id="radioDiv">
<label>
<input id="radioFrt" type="radio" name="frt" value="Apple" class="radioB" checked> APPLE
</label>
<label>
<input type="radio" name="frt" value="Pear" class="radioB"> PEAR
</label>
</div>
<div id="TOPbarChart"></div>
<script type="text/javascript">
var currentFruit = "Apple";
var currentColr = "#00a5b6";
var barDataCSV_Dly = "BarData.csv";
//
//
// radio button
document.getElementById("radioFrt").checked = true;
d3.selectAll('input[name="frt"]').on("change", function change() {
currentFruit = this.value;
TOPbarChart(currentFruit, currentColr);
});
//FORMATS
var parseDate = d3.time.format("%m/%d/%Y").parse;
//
// BASIC SIZING
//
function barChartBasics() {
var margin = {
top: 25,
right: 35,
bottom: 25,
left: 70
},
width = 550 - margin.left - margin.right,
height = 155 - margin.top - margin.bottom,
colorBar = d3.scale.category20(),
barPaddingFine = 1,
barPaddingThick = 2;
return {
margin: margin,
width: width,
height: height,
colorBar: colorBar,
barPaddingFine: barPaddingFine,
barPaddingThick: barPaddingThick
};
}
// create svg element
var basics = barChartBasics();
var svg = d3.select("#TOPbarChart")
.append("svg")
.attr({
"width": basics.width + basics.margin.left + basics.margin.right,
"height": basics.height + basics.margin.top + basics.margin.bottom,
id: "svgTOPbarChart"
});
// create svg group
var plot = svg
.append("g")
.attr({
"transform": "translate(" + basics.margin.left + "," + basics.margin.top + ")",
id: "svgPlotTOPbarChart"
});
var axisPadding = 2;
var leftAxisGroup = svg
.append('g')
.attr({
transform: 'translate(' + (basics.margin.left - axisPadding) + ',' + (basics.margin.top) + ')',
'class': "yAxis axis--y",
id: "yAxisGTOPbarChart"
});
var bottomAxisGroup = svg
.append('g')
.attr({
'class': "xAxis axis--x",
id: "xAxisGTOPbarChart"
});
var titleTxt = svg.append("text")
.attr({
x: basics.margin.left + 12,
y: 20,
'class': "title",
'text-anchor': "start"
})
// create scales with ranges
var xScale = d3.time.scale().range([0, basics.width]);
var yScale = d3.scale.linear().range([basics.height, 0]);
function TOPbarChart(
frt, colorChosen) {
// get the data
d3.csv(barDataCSV_Dly, function(rows) {
TOPbarData = rows.map(function(d) {
return {
"Fruit": d.Fruit,
"dt": parseDate(d.dt),
"amount": +d.amount
};
}).filter(function(row) {
if (row['Fruit'] == frt) {
return true;
}
});
// create domains for the scales
xScale.domain(d3.extent(TOPbarData, function(d) {
return d.dt;
}));
var amounts = TOPbarData.map(function(d) {
return d.amount;
});
var yMax = d3.max(amounts);
var yMin = d3.min(amounts);
var yMinFinal = 0;
if (yMin < 0) {
yMinFinal = yMin;
}
yScale.domain([yMinFinal, yMax]);
// introduce the bars
// var plot = d3.select("#svgPlotTOPbarChart")
var sel = plot.selectAll("rect")
.data(TOPbarData);
sel.enter()
.append("rect")
.attr({
x: function(d, i) {
return xScale(d.dt);
},
y: function(d) {
return yScale(d.amount);
},
width: (basics.width / TOPbarData.length - basics.barPaddingFine),
height: function(d) {
return basics.height - yScale(d.amount);
},
fill: colorChosen,
'class': "bar"
});
// this little function will create a small ripple affect during transition
var dlyRipple = function(d, i) {
return i * 100;
};
sel
.transition()
.duration(dlyRipple) //1000
.attr({
x: function(d, i) {
return xScale(d.dt);
},
y: function(d) {
return yScale(d.amount);
},
width: (basics.width / TOPbarData.length - basics.barPaddingFine),
height: function(d) {
return basics.height - yScale(d.amount);
},
fill: colorChosen
});
sel.exit().remove();
// add/transition y axis - with ticks and tick markers
var axisY = d3.svg.axis()
.orient('left')
.scale(yScale)
.tickFormat(d3.format("s")) // use abbreviations, e.g. 5M for 5 Million
.outerTickSize(0);
leftAxisGroup.transition().duration(1000).call(axisY);
// add/transition x axis - with ticks and tick markers
var axisX = d3.svg.axis()
.orient('bottom')
.scale(xScale);
bottomAxisGroup
.attr({
transform: 'translate(' + (basics.margin.left + ((basics.width / TOPbarData.length) / 2)) + ',' + (basics.margin.top + basics.height) + ')',
})
.transition().duration(1000).call(axisX.ticks(5));
titleTxt.text("Daily: last " + TOPbarData.length + " days");
// console.log(TOPbarData.length)
});
}
//
//
//
//
TOPbarChart(currentFruit, currentColr);
//
//
//
//
</script>
</body>
</html>
When all the data is positive everything is pretty much ok - but when some of the data is negative we can see the result in this plunker demo:
http://plnkr.co/edit/1hudJYkRq2MnuIlwxXZi?p=preview
How do I amend the code so that:
- the negative bars are shown?
- the base of the positive bars moves vertically up when negative numbers are included?
- the vertical movement is also included in the transition?
Above is more than 1 question but help on any would be appreciated.
The key is to play with the y and height attributes of the bars to position them correctly.
For y, change it to:
y: function(d) {
return yScale(Math.max(0, d.amount));
},
And for the height, change it to:
height: function(d) {
return Math.abs(yScale(d.amount) - yScale(0));
},
You can then style the negative bars to make them a different color.
Check the updated Plunkr - http://plnkr.co/edit/q7dQsPW0PiPuwFTy8gLN?p=preview
Edit:
For the coloring part, you can achieve it with a 1 liner if you want to reduce lines and want more simplicity.
Instead of:
fill: function(d) {
var col = colorChosen
if (d.amount < 0) {
col = "#FF0000";
}
return col;
},
});
You can do:
fill: function(d) {
return d.amount < 0 ? "#FF0000" : colorChosen;
},
I'm stuck with a global/local variable issue.
Here, my code based on http://www.d3noob.org/2014/04/using-html-inputs-with-d3js.html
index.html
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<title>Input test (circle)</title>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<p>
<label for="nRadius"
style="display: inline-block; width: 240px; text-align: right">
radius = <span id="nRadius-value">…</span>
</label>
<input type="range" min="1" max="150" id="nRadius">
</p>
<script src="./slider.js"></script>
</body>
</html>
slider.js
// update the circle radius
function update(nRadius) {
// adjust the text on the range slider
d3.select("#nRadius-value").text(nRadius);
d3.select("#nRadius").property("value", nRadius);
// update the circle radius
circle.selectAll("circle").attr("r", nRadius);
return nRadius;
}
var width = 600;
var height = 300;
var circle = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// Circle with no radius
circle.append("circle")
.attr("cx", 300)
.attr("cy", 150)
.style("fill", "none")
.style("stroke", "blue");
// Initial radius
update(50);
// when the input range changes update the circle
d3.select("#nRadius").on("input", function() {
update(+this.value);
});
This code works just fine, but what I'd like to do is to be able to export 'this.value' outside the d3.select, in order to use it in some other function.
The d3.select showed below already includes some suggestions found by googling my question:
var currentRadius = 0;
d3.select("#nRadius").on("input", function() {
currentRadius = +this.value
console.log("Inside: " + currentRadius);
currentRadius = update(currentRadius);
});
console.log("Outside: " + currentRadius);
But it doesn't work.
The problem is here
//this is your global variable
var currentRadius = 0;
d3.select("#nRadius").on("input", function() {
//you are changing the global value here on change event.
currentRadius = +this.value
console.log("Inside: " + currentRadius);
currentRadius = update(currentRadius);
//call your function
outside();
});
//some function somewhere
function outside(){
console.log(currentRadius)
}
//value of global variable when this executed is 0
//because no change event has been called
console.log("Outside: " + currentRadius);
So in short the global variable change will happen in the change function.
And the console.log("Outside: " + currentRadius); is done much before that event got fired.
I hope this helps
I use the D3 visualization library for a lot of projects and find myself copying and pasting a lot of boilerplate code for each one. Most projects, for example, start out like this:
var margin = {top: 20, right: 10, bottom: 30, left: 60},
width = 960,
height = 500;
var svg = d3.select(container_id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
After this sort of code, every project diverges. Part of the joy of D3 is that you do some specialized, creative coding for each new project.
I want to write a lightweight wrapper for the boilerplate code so that I can skip to the fun part each time, and in so doing I realized I don't quite understand how to properly make a complex, reusable Javascript object. Here's what I started with:
var d3mill = function() {
var margin = {top: 20, right: 10, bottom: 30, left: 60},
width = 960,
height = 500;
var svg = d3.select(container_id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
return {
svg: function() { return svg; },
call: function(f) { f(); }
};
};
I think I want to be able to do this:
var d3m = d3mill();
var test = function() {
console.log(svg);
};
d3.call(test);
I thought (wishfully) that passing the function through call() would cause the function to fire inside the closure of the d3mill instance, thus making svg be defined.
It would be a huge waste of time to expose every variable in the closure to the outside world in the manner of the svg() function above. What's the right way to allow outside functions to operate here?
If you change you code to this:
return {
svg: function() { return svg; },
call: function(f) { f.call(this); }
};
then it should correctly set the context within test to be d3m.
Within that function you should then be able to access this.svg() to get the SVG object, but you will not be able to access the "private" lexically scoped variable svg directly, i.e.:
var d3m = d3mill();
var test = function() {
console.log(this.svg()); // OK
console.log(svg); // not OK - undefined variable
};
d3m.call(test);
You could also just pass the svg parameter to f when it's called:
return {
svg: function() { return svg; },
call: function(f) { return f.call(this, svg); } // also added "return", just in case
};
with usage:
var d3m = d3mill();
var test = function(svg) {
console.log(svg); // now OK - it's a parameter
};
d3m.call(test);
You could also use as a constructor function.
var D3Mill = (function() {
var defaults = {
margin: { top: 20, right: 10, bottom: 30, left: 60 },
width: 960,
height: 500
};
function num(i, def) {
return ("number" === typeof i) ? i : def;
}
function D3Mill(container_id, opts) {
opts = opts || {};
// Use opts.xxx or default.xxx if no opts provided
// Expose all values as this.xxx
var margin = this.margin = (opts.margin || defaults.margin);
var width = this.width = num(opts.width, defaults.width);
var height = this.height = num(opts.height, defaults.height);
this.svg = d3.select(container_id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
}
D3Mill.prototype.perform = function(f) { return f.call(this); };
return D3Mill;
}());
var d3m = new D3Mill("my_container_id");
// or
var opts = {
width: 1,
height: 1,
margin: { ... }
};
var d3m = new D3Mill("my_container_id", opts);
var test = function() {
console.log(this.svg, this.margin, this.width, this.height);
};
d3m.perform(test);
the following will also give you access to the variables you want inside test.
var d3mill = function() {
this.margin = {top: 20, right: 10, bottom: 30, left: 60},
width = 960,
height = 500;
this.svg = d3.select(container_id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
};
var d3m = new d3mill();
var test = function() {
console.log(this.svg);
};
test.call(d3m);