Hello I need help to add gray (Minor and Major) graduation lines inside the gauge to capture more precisely the position of the needle. Also if you have any idea how we could put text to Major graduation lines.
For my sample I would required 7 Major graduation lines divided by 10 minor separations (to look exactly like the Telerik Gauge).
Here is my implementation : http://jsfiddle.net/8svg5/
<html>
<head runat="server">
<title></title>
</head>
<body>
<div>
<a id="myTooltip" title="This is my message"></a>
<div id="svgTarget"></div>
</div>
<script>
$(function () {
var gaugeRanges = [
{
From: 1.5,
To: 2.5,
Color: "#8dcb2a"
}, {
From: 2.5,
To: 3.5,
Color: "#ffc700"
}, {
From: 3.5,
To: 4.5,
Color: "#ff7a00"
},
{
From: 4.5,
To: 6,
Color: "#c20000"
}];
$("#svgTarget").mttD3Gauge({ data: gaugeRanges });
});
</script>
</body>
</html>
(function ($) {
$.fn.mttD3Gauge = function (options) {
var settings = $.extend({
width: 300,
innerRadius: 130,
outterRadius: 145,
data: []
}, options);
this.create = function () {
this.html("<svg class='mtt-svgClock' width='" + settings.width + "' height='" + settings.width + "'></svg>");
var maxLimit = 0;
var minLimit = 9999999;
var d3DataSource = [];
var d3TickSource = [];
//Data Genration
$.each(settings.data, function (index, value) {
d3DataSource.push([value.From, value.To, value.Color]);
if (value.To > maxLimit) maxLimit = value.To;
if (value.From < minLimit) minLimit = value.From;
});
if (minLimit > 0) {
d3DataSource.push([0, minLimit, "#d7d7d7"]);
}
var pi = Math.PI;
//Control Genration
var vis = d3.select(this.selector + " .mtt-svgClock");
var translate = "translate(" + settings.width / 2 + "," + settings.width / 2 + ")";
var cScale = d3.scale.linear().domain([0, maxLimit]).range([-120 * (pi / 180), 120 * (pi / 180)]);
var arc = d3.svg.arc()
.innerRadius(settings.innerRadius)
.outerRadius(settings.outterRadius)
.startAngle(function (d) { return cScale(d[0]); })
.endAngle(function (d) { return cScale(d[1]); });
var tickArc = d3.svg.arc()
.innerRadius(settings.innerRadius - 20)
.outerRadius(settings.innerRadius - 2)
.startAngle(function (d) { return cScale(d[0]); })
.endAngle(function (d) { return cScale(d[1]); });
for (var i = 0; i < 10; i++) {
var point = (i * maxLimit) / 10.0;
d3TickSource.push([point, point +1, "#d7d7d7"]);
}
vis.selectAll("path")
.data(d3DataSource)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function (d) { return d[2]; })
.attr("transform", translate);
return this;
};
return this.create();
};
}(jQuery));
enter code here
Here is the link of the page of what i'm trying to achieve with D3.js
http://demos.telerik.com/aspnet-ajax/gauge/examples/types/radialgauge/defaultcs.aspx?#qsf-demo-source
Any help will be greatly appreciated!
I finally found the way to add graduation, feel free to use this code
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="Scripts/jquery-1.9.1.js"></script>
<script src="Scripts/html5shiv.js"></script>
<script src="Scripts/d3.v3.min.js"></script>
<script src="Scripts/mtt-D3Gauge.js"></script>
</head>
<body>
<div>
<a id="myTooltip" title="This is my message"></a>
<div id="svgTarget"></div>
</div>
<script>
$(function () {
var gaugeRanges = [
{
From: 1.5,
To: 2.5,
Color: "#8dcb2a"
}, {
From: 2.5,
To: 3.5,
Color: "#ffc700"
}, {
From: 3.5,
To: 4.5,
Color: "#ff7a00"
},
{
From: 4.5,
To: 6,
Color: "#c20000"
}];
$("#svgTarget").mttD3Gauge({ data: gaugeRanges });
});
</script>
</body>
</html>
(function ($) {
$.fn.mttD3Gauge = function (options) {
var vis;
var settings = $.extend({
width: 300,
innerRadius: 130,
outterRadius: 145,
majorGraduations: 6,
minorGraduations: 10,
majorGraduationLenght: 16,
minorGraduationLenght: 10,
majorGraduationMarginTop: 7,
majorGraduationColor: "rgb(234,234,234)",
minorGraduationColor:"rgb(234,234,234)",
data: []
}, options);
this.create = function () {
this.html("<svg class='mtt-svgClock' width='" + settings.width + "' height='" + settings.width + "'></svg>");
var maxLimit = 0;
var minLimit = 9999999;
var d3DataSource = [];
//Data Genration
$.each(settings.data, function (index, value) {
d3DataSource.push([value.From, value.To, value.Color]);
if (value.To > maxLimit) maxLimit = value.To;
if (value.From < minLimit) minLimit = value.From;
});
if (minLimit > 0) {
d3DataSource.push([0, minLimit, "#d7d7d7"]);
}
//Render Gauge Color Area
vis = d3.select(this.selector + " .mtt-svgClock");
var translate = "translate(" + settings.width / 2 + "," + settings.width / 2 + ")";
var cScale = d3.scale.linear().domain([0, maxLimit]).range([-120 * (Math.PI / 180), 120 * (Math.PI / 180)]);
var arc = d3.svg.arc()
.innerRadius(settings.innerRadius)
.outerRadius(settings.outterRadius)
.startAngle(function (d) { return cScale(d[0]); })
.endAngle(function (d) { return cScale(d[1]); });
vis.selectAll("path")
.data(d3DataSource)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function (d) { return d[2]; })
.attr("transform", translate);
renderMajorGraduations();
return this;
};
var renderMinorGraduations = function (majorGraduationsAngles, indexMajor) {
var graduationsAngles = [];
if (indexMajor > 0) {
var minScale = majorGraduationsAngles[indexMajor - 1];
var maxScale = majorGraduationsAngles[indexMajor];
var scaleRange = maxScale - minScale;
for (var i = 1; i < settings.minorGraduations; i++) {
var scaleValue = minScale + i * scaleRange / settings.minorGraduations;
graduationsAngles.push(scaleValue);
}
var xCenter = settings.width / 2;
var yCenter = settings.width / 2;
//Render Minor Graduations
$.each(graduationsAngles, function (indexMinor, value) {
var cos1Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.minorGraduationLenght));
var sin1Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.minorGraduationLenght));
var cos2Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var sin2Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var x1 = xCenter + cos1Adj;
var y1 = yCenter + sin1Adj * -1;
var x2 = xCenter + cos2Adj;
var y2 = yCenter + sin2Adj * -1;
vis.append("svg:line")
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.style("stroke", settings.majorGraduationColor);
});
}
};
var renderMajorGraduations = function () {
var scaleRange = 240;
var minScale = -120;
var graduationsAngles = [];
for (var i = 0; i <= settings.majorGraduations; i++) {
var scaleValue = minScale + i * scaleRange / settings.majorGraduations;
graduationsAngles.push(scaleValue);
}
var xCenter = settings.width / 2;
var yCenter = settings.width / 2;
//Render Major Graduations
$.each(graduationsAngles, function (index, value) {
var cos1Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.majorGraduationLenght));
var sin1Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop - settings.majorGraduationLenght));
var cos2Adj = Math.round(Math.cos((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var sin2Adj = Math.round(Math.sin((90 - value) * Math.PI / 180) * (settings.innerRadius - settings.majorGraduationMarginTop));
var x1 = xCenter + cos1Adj;
var y1 = yCenter + sin1Adj * -1;
var x2 = xCenter + cos2Adj;
var y2 = yCenter + sin2Adj * -1;
vis.append("svg:line")
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.style("stroke", settings.majorGraduationColor);
renderMinorGraduations(graduationsAngles, index);
});
};
return this.create();
};
}(jQuery));
Related
I'm currently working on a thing which is supposed to look like a lava lamp later. Unfortunately, I'm already failing at the beginning:
I managed to create randomly generated blobs, but I just can't apply a fill. The stroke works just fine.
here is the relevant code which creates the blobs:
var ctx = this; // context
this.path = d3.areaRadial()
.angle(function(d) {return d.theta})
.radius(function(d) {return d.r})
.curve(d3.curveCatmullRomClosed.alpha(1));
// create the blob
this.create = function() {
var anchors = [];
for (var i = 0; i < ctx.options.anchors; i++) {
var currTheta = i * (360 / ctx.options.anchors) + rand(ctx.options.spread.theta);
var currRadians = Math.radians(currTheta);
var currRadius = ctx.options.radius + rand(ctx.options.spread.r);
anchors.push({theta: currRadians, r: currRadius});
}
var pathData = ctx.path(anchors);
d3.select(ctx.options.target).append('path')
.attr('d', pathData)
.attr('class', 'blob')
.style('opacity', ctx.options.opacity)
.style('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)')
.attr('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)');
console.log(pathData);
}
function rand(x) // creates a random number between -0.5 * x and 0.5 * x
Full code: https://codepen.io/normanwink/pen/BrMVrE
Thanks!
That's the expected behaviour, since you're using radius, which is:
Equivalent to area.y, except the accessor returns the radius: the distance from the origin ⟨0,0⟩.
And for area.y:
If y is specified, sets y0 to y and y1 to null and returns this area generator. If y is not specified, returns the current y0 accessor. (emphasis mine)
Therefore, you probably want outerRadius here.
this.path = d3.areaRadial()
.angle(function(d) {
return d.theta
})
.outerRadius(function(d) {
return d.r
})
.curve(d3.curveCatmullRomClosed.alpha(1));
This is your code with that change only:
(function() {
"use strict";
// window size
var windowX = window.innerWidth;
var windowY = window.innerHeight;
var svgX = windowX;
var svgY = windowY;
var blobCount = 1;
function init() {
// console.log(new Blob());
var svg = d3.select('#svg')
.attr('viewBox', '0 0 ' + svgX + ' ' + svgY)
.attr('preserveAspectRatio', 'xMinYMin meet');
for (var i = 0; i < blobCount; i++) {
var newBlob = new Blob(svgX * 0.5, svgY * 0.5);
}
}
function Blob(x, y) {
var ctx = this; // context
this.options = {
anchors: 8,
breathe: 30,
fill: '#ffffff',
opacity: 0.5,
radius: 150,
spread: {
theta: 10, // angle
r: 300 // radius
},
target: '#svg',
};
this.x = x || 0;
this.y = y || 0;
this.path = d3.areaRadial()
.angle(function(d) {return d.theta})
.outerRadius(function(d) {return d.r})
.curve(d3.curveCatmullRomClosed.alpha(1));
// create the blob
this.create = function() {
var anchors = [];
for (var i = 0; i < ctx.options.anchors; i++) {
var currTheta = i * (360 / ctx.options.anchors) + rand(ctx.options.spread.theta);
var currRadians = Math.radians(currTheta);
var currRadius = ctx.options.radius + rand(ctx.options.spread.r);
anchors.push({theta: currRadians, r: currRadius});
}
var pathData = ctx.path(anchors);
d3.select(ctx.options.target).append('path')
.attr('d', pathData)
.attr('class', 'blob')
.style('opacity', ctx.options.opacity)
.style('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)')
.attr('transform', 'translate(' + ctx.x + 'px, ' + ctx.y + 'px)');
}
// update position and anchor movement
this.update = function() {
}
// apply changes
this.render = function() {
}
this.create();
}
function rand(i) {
return (Math.random()-0.5) * (i || 1);
}
Math.radians = function(degrees) {
return degrees * Math.PI / 180;
};
// init when ready
init();
})();
#svg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.blob {
fill: blue; /* why is this not working? */
stroke: red;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="svg"></svg>
Your problem is your pathData , because if you replace your .attr('d', pathData) with .attr('d', 'M37,17v15H14V17z M50,0H0v50h50z') which is a valid path, your fill is working properly.
I will continue searching the issue, just want to give you a hint, maybe you will find the issue faster then me. :)
I have made a gauge chart using D3.js and it is working perfectly. However, when I am trying to add the same javascript code to render the same chart in a Qlikview extension, it is not working.
Can anyone please help me to find out where I am going wrong? I am attaching the code of the Script.js file below:
Qva.LoadScript("QlikView/Examples/Gauge/d3.min.js", function () {
/*
function loadGoogleCoreChart() {
google.load('visualization', '1', {
packages: ['corechart'],
callback: googleCoreChartLoaded
});
}
function googleCoreChartLoaded() {
*/
Qva.AddExtension('QlikView/Examples/Gauge', function () {
//this.Element.innerHTML = "<script type=\"text/javascript\" src=\"http://d3js.org/d3.v2.min.js\"></script>"
//this.Element.innerHTML = "";
var gauge = function(container, configuration) {
var that = {};
var config = {
size : 200,
clipWidth : 200,
clipHeight : 110,
ringInset : 20,
ringWidth : 20,
pointerWidth : 10,
pointerTailLength : 5,
pointerHeadLengthPercent : 0.9,
minValue : 0,
maxValue : 10,
minAngle : -90,
maxAngle : 90,
transitionMs : 750,
majorTicks : 5,
labelFormat : d3.format(',g'),
labelInset : 10,
arcColorFn : d3.interpolateHsl(d3.rgb('#e8e2ca'), d3.rgb('#3e6c0a'))
};
var range = undefined;
var r = undefined;
var pointerHeadLength = undefined;
var value = 0;
var svg = undefined;
var arc = undefined;
var scale = undefined;
var ticks = undefined;
var tickData = undefined;
var pointer = undefined;
var donut = d3.layout.pie();
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function newAngle(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return newAngle;
}
function configure(configuration) {
var prop = undefined;
for ( prop in configuration ) {
config[prop] = configuration[prop];
}
range = config.maxAngle - config.minAngle;
r = config.size / 2;
pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);
// a linear scale that maps domain values to a percent from 0..1
scale = d3.scale.linear()
.range([0,1])
.domain([config.minValue, config.maxValue]);
ticks = scale.ticks(config.majorTicks);
tickData = d3.range(config.majorTicks).map(function() {return 1/config.majorTicks;});
arc = d3.svg.arc()
.innerRadius(r - config.ringWidth - config.ringInset)
.outerRadius(r - config.ringInset)
.startAngle(function(d, i) {
var ratio = d * i;
return deg2rad(config.minAngle + (ratio * range));
})
.endAngle(function(d, i) {
var ratio = d * (i+1);
return deg2rad(config.minAngle + (ratio * range));
});
}
that.configure = configure;
function centerTranslation() {
return 'translate('+r +','+ r +')';
}
function isRendered() {
return (svg !== undefined);
}
that.isRendered = isRendered;
function render(newValue) {
svg = d3.select(container)
.append('svg:svg')
.attr('class', 'gauge')
.attr('width', config.clipWidth)
.attr('height', config.clipHeight);
var centerTx = centerTranslation();
var arcs = svg.append('g')
.attr('class', 'arc')
.attr('transform', centerTx);
arcs.selectAll('path')
.data(tickData)
.enter().append('path')
.attr('fill', function(d, i) {
return config.arcColorFn(d * i);
})
.attr('d', arc);
var lg = svg.append('g')
.attr('class', 'label')
.attr('transform', centerTx);
lg.selectAll('text')
.data(ticks)
.enter().append('text')
.attr('transform', function(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return 'rotate(' +newAngle +') translate(0,' +(config.labelInset - r) +')';
})
.text(config.labelFormat);
var lineData = [ [config.pointerWidth / 2, 0],
[0, -pointerHeadLength],
[-(config.pointerWidth / 2), 0],
[0, config.pointerTailLength],
[config.pointerWidth / 2, 0] ];
var pointerLine = d3.svg.line().interpolate('monotone');
var pg = svg.append('g').data([lineData])
.attr('class', 'pointer')
.attr('transform', centerTx);
pointer = pg.append('path')
.attr('d', pointerLine/*function(d) { return pointerLine(d) +'Z';}*/ )
.attr('transform', 'rotate(' +config.minAngle +')');
update(newValue === undefined ? 0 : newValue);
}
that.render = render;
function update(newValue, newConfiguration) {
if ( newConfiguration !== undefined) {
configure(newConfiguration);
}
var ratio = scale(newValue);
var newAngle = config.minAngle + (ratio * range);
pointer.transition()
.duration(config.transitionMs)
.ease('elastic')
.attr('transform', 'rotate(' +newAngle +')');
}
that.update = update;
configure(configuration);
return that;
};
function onDocumentReady() {
var powerGauge = gauge(this.Element, {
size: 300,
clipWidth: 300,
clipHeight: 300,
ringWidth: 60,
maxValue: 10,
transitionMs: 4000,
});
alert("Some Text");
powerGauge.render();
function updateReadings() {
// just pump in random data here...
powerGauge.update(Math.random() * 10);
}
// every few seconds update reading values
updateReadings();
setInterval(function() {
updateReadings();
}, 5 * 1000);
}
if ( !window.isLoaded ) {
window.addEventListener("load", function() {
onDocumentReady();
}, false);
} else {
onDocumentReady();
}
});
});
Please help me with this as I am unable to find out the glitch.
I'm using d3 and roughjs to draw some pie charts. I draw the arcs, then put some text, but the text is not "showing up". The text flashes on the screen right where it should be as soon as the pages loads, but then it gets covered by the pie charts. I looked for some z-index but I haven't been able to find a solution. What am I getting wrong?
I can see the labels in place also when debugging
Debugging:
http://i.imgur.com/jKQzcH2.png
Right after:
http://i.imgur.com/KPz5Oxa.png
Here's the code
<!DOCTYPE html>
<meta charset="utf-8">
<canvas></canvas>
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
<script type="text/javascript" src="https://roughjs.com/builds/rough.min.js"></script>
<script>
var twData = [{"concept" : "macri", "sentiment" : [0.1, 0.9]},
{"concept" : "paritarias", "sentiment" : [0.2, 0.8]},
{"concept" : "churros", "sentiment" : [0.7, 0.3]}];
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d");
rough = new RoughCanvas(canvas, 960, 500);
context.translate(width / 2, height / 2);
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "#000";
context.font="25px Verdana";
var width = canvas.width,
height = canvas.height,
radius = (Math.min(width, height) / 2) / twData.length;
rough.hachureGap = 1;
rough.strokeWidth = 0.5;
rough.fillWeight = 1;
var colors = ["#009933", "#800000"];
var labelArc = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40)
.context(context);
var dx = (width/2) / twData.length + 50;
dy = (height/2) / twData.length;
for(k=0;k<twData.length;k++){
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
// Arcs
var arcs = pie(twData[k].sentiment);
arcs.forEach(function(d, i) {
var a = rough.arc(dx*(k+1), dy, radius * 2, radius * 2, d.startAngle - Math.PI/2, d.endAngle - Math.PI/2, true);
a.fill = colors[i];
//a.hachureAngle = Math.random() * 360;
});
// Labels
arcs.forEach(function(d) {
var c = labelArc.centroid(d);
context.fillText(d.data, dx*(k+1)+c[0], dy+c[1]);
console.log("Agregando label " + d.data + " en (" + Math.round(dx*(k+1)+c[0]) + "," + Math.round(dy+c[1])+")");
});
context.fillText(twData[k].concept,dx*(k+1), dy+100)
console.log("Agregando label " + twData[k].concept + " en (" + Math.round(dx*(k+1)) + "," + Math.round(dy+100)+")");
} //for
</script>
Thanks!
rough.js takes control of the canvas and clears it for it's own purposes. I found the key in a comment on this example:
// since we are going to draw on canvas directly, i.e using context2d and not rough.js,
// we call requestAnimationFrame. This will prevent our drawing to be cleared by rough.js
So, your code becomes:
<!DOCTYPE html>
<meta charset="utf-8">
<canvas></canvas>
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
<script type="text/javascript" src="https://roughjs.com/builds/rough.min.js"></script>
<script>
var twData = [{
"concept": "macri",
"sentiment": [0.1, 0.9]
}, {
"concept": "paritarias",
"sentiment": [0.2, 0.8]
}, {
"concept": "churros",
"sentiment": [0.7, 0.3]
}];
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d");
rough = new RoughCanvas(canvas, 960, 500);
context.translate(width / 2, height / 2);
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "#000";
context.font = "25px Verdana";
var width = canvas.width,
height = canvas.height,
radius = (Math.min(width, height) / 2) / twData.length;
rough.hachureGap = 1;
rough.strokeWidth = 0.5;
rough.fillWeight = 1;
var colors = ["#009933", "#800000"];
var labelArc = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40)
.context(context);
var dx = (width / 2) / twData.length + 50;
dy = (height / 2) / twData.length;
for (k = 0; k < twData.length; k++) {
var pie = d3.pie()
.sort(null)
.value(function(d) {
return d;
});
// Arcs
var arcs = pie(twData[k].sentiment);
arcs.forEach(function(d, i) {
var a = rough.arc(dx * (k + 1), dy, radius * 2, radius * 2, d.startAngle - Math.PI / 2, d.endAngle - Math.PI / 2, true);
a.fill = colors[i];
//a.hachureAngle = Math.random() * 360;
});
} //for
window.requestAnimationFrame(function() {
for (k = 0; k < twData.length; k++) {
// Labels
arcs.forEach(function(d) {
var c = labelArc.centroid(d);
context.fillText(d.data, dx * (k + 1) + c[0], dy + c[1]);
console.log("Agregando label " + d.data + " en (" + Math.round(dx * (k + 1) + c[0]) + "," + Math.round(dy + c[1]) + ")");
});
context.fillText(twData[k].concept, dx * (k + 1), dy + 100)
//console.log("Agregando label " + twData[k].concept + " en (" + Math.round(dx * (k + 1)) + "," + Math.round(dy + 100) + ")");
}
});
</script>
I want to make a Gauge from d3.js as a component I can use in Vue. But I'm struggling import it the right way so I can wrap it in a custom component.
Besides installed the d3 from npm: npm install d3. I have this for the gauge
// data which need to be fetched
var name = " ";
var value = 840;
var gaugeMaxValue = 1680;
// donn�es � calculer
var percentValue = value / gaugeMaxValue;
////////////////////////
var needleClient;
(function(){
var barWidth, chart, chartInset, degToRad, repaintGauge,
height, margin, numSections, padRad, percToDeg, percToRad,
percent, radius, sectionIndx, svg, totalPercent, width,
valueText, formatValue, k;
percent = percentValue;
numSections = 1;
sectionPerc = 1 / numSections / 2;
padRad = 0.025;
chartInset = 10;
// Orientation of gauge:
totalPercent = .75;
el = d3.select('.chart-gauge');
margin = {
top: 30,
right: 30,
bottom: 30,
left: 30
};
width = el[0][0].offsetWidth - margin.left - margin.right;
height = width;
radius = Math.min(width, height) / 2;
barWidth = 40 * width / 300;
//Utility methods
percToDeg = function(perc) {
return perc * 360;
};
percToRad = function(perc) {
return degToRad(percToDeg(perc));
};
degToRad = function(deg) {
return deg * Math.PI / 180;
};
// Create SVG element
svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width) / 2 + margin.left) + ", " + ((height + margin.top) / 2) + ")");
chart.append('path').attr('class', "arc chart-red");
chart.append('path').attr('class', "arc chart-yellow");
chart.append('path').attr('class', "arc chart-green");
chart.append('path').attr('class', "arc chart-yellow_");
chart.append('path').attr('class', "arc chart-red_");
valueText = chart.append("chart")
formatValue = d3.format('1%');
arc5 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc4 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
repaintGauge = function ()
{
perc = 0.5;
var next_start = totalPercent;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(0.175);
next_start += 0.175;
arc1.startAngle(arcStartRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(0.05);
next_start += 0.05;
arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(0.05);
next_start += 0.05;
arc3.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(0.05);
next_start += 0.05;
arc4.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(0.175);
next_start += 0.175;
arc5.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
chart.select(".chart-red").attr('d', arc1);
chart.select(".chart-yellow").attr('d', arc2);
chart.select(".chart-green").attr('d', arc3);
chart.select(".chart-yellow_").attr('d', arc4);
chart.select(".chart-red_").attr('d', arc5);
}
/////////
var Needle = (function() {
//Helper function that returns the `d` value for moving the needle
var recalcPointerPos = function(perc) {
var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
thetaRad = percToRad(perc / 2);
centerX = 0;
centerY = 0;
topX = centerX - this.len * Math.cos(thetaRad);
topY = centerY - this.len * Math.sin(thetaRad);
leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
};
function Needle(el) {
this.el = el;
this.len = width / 2.5;
this.radius = this.len / 8;
}
Needle.prototype.render = function() {
this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);
return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));
};
Needle.prototype.moveTo = function(perc) {
var self,
oldValue = this.perc || 0;
this.perc = perc;
self = this;
// Reset pointer position
this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function() {
return function(percentOfPercent) {
var progress = (1 - percentOfPercent) * oldValue;
repaintGauge(progress);
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function() {
return function(percentOfPercent) {
var progress = percentOfPercent * perc;
repaintGauge(progress);
var thetaRad = percToRad(progress / 2);
var textX = - (self.len + 45) * Math.cos(thetaRad);
var textY = - (self.len + 45) * Math.sin(thetaRad);
valueText.text(formatValue(progress))
.attr('transform', "translate("+textX+","+textY+")")
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
};
return Needle;
})();
needle = new Needle(chart);
needle.render();
needle.moveTo(percent);
setTimeout(displayValue, 1350);
})();
This is the working HTML but not as a component
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css" src="gauge.css">
.chart-gauge
{
width: 400px;
margin: 100px auto
}
.chart-green
{
fill: #9FBD35;
}
.chart-yellow
{
fill: #F2BA3A;
}
.chart-yellow_
{
fill: #F2BA3A;
}
.chart-red
{
fill: #FB3033;
}
.chart-red_
{
fill: #FB3033;
}
.needle, .needle-center
{
fill: #000000;
}
.text {
color: "#112864";
font-size: 16px;
}
svg {
font: 10px sans-serif;
}
</style>
</head>
<body>
<div class="chart-gauge"></div>
<script type="text/javascript" src="./gaugeClient.js"></script>
<script type="text/javascript" src="./labels.js"></script>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</body>
</html>
First component are designed to help you from rewriting your code each and every time , so why not create a component for d3 that you can reuse each and everytime like below :
universal c3 component, c3.vue
<template>
<div :style="style" v-bind:class="class" id="{{ randomid }}" ></div>
</template>
<script>
import c3 from 'c3'
module.exports = {
props: {
legend: {
type: Object,
},
size: {
type: Object,
},
colour: {
type: Object,
},
axis: {
type: Object,
},
bar: {
type: Object,
},
chartdata:{
type: Object,
default: function () {
return {
columns: [
['data1', 30, 200, 100, 400, 150, 250],
['data2', 50, 20, 10, 40, 15, 25]
]
}
}
},
class:{
type: Object,
},
styles: {
type: Object,
}
},
created: function() {
},
ready: function(){
this.drawChart();
},
methods : {
drawChart: function () {
var self = this
var chart = c3.generate({
bindto: document.getElementById(self.randomid) ,
data: self.chartdata,
size : self.size,
colour : self.colour,
legend : self.legend,
bar : self.bar,
axis : self.axis
});
}
},
computed: {
randomid: function () {
return _.uniqueId('c3_')
}
}
}
</script>
next register the component :
Vue.component('c3-chart', require('./c3.vue'))
now you can use this to create any chart you want , yes + gauges
<template>
<div>
<c3-chart :chartdata="gauge.data" :colour="gauge.colour" :size="gauge.size"></c3-chart>
</div>
</template>
<script>
module.exports = {
props: {
},
components: {
},
data: function () {
return {
gauge : {
data: {
columns: [
['data', 91.4]
],
type: 'gauge',
onclick: function (d, i) { console.log("onclick", d, i); },
onmouseover: function (d, i) { console.log("onmouseover", d, i); },
onmouseout: function (d, i) { console.log("onmouseout", d, i); }
},
color: {
pattern: ['#FF0000', '#F97600', '#F6C600', '#60B044'],
threshold: {
values: [30, 60, 90, 100]
}
},
size: {
height: 180
}
}
}
},
created: function() {
},
ready: function(){
},
methods : {
},
events: {
},
computed: {
}
}
</script>
I have built a force-directed graph, however, in my case, I have also a grid with 80px*80px boxes. I'd like that each node in graph was positioned not only according to existing gravity and forces, but also in the middle of the closest grid box (without being fixed).
Is it possible to do this in d3js?
Moritz Stefaner came up with a way to do this
code: https://github.com/moritzstefaner/gridexperiments/
demo: http://moritzstefaner.github.io/gridexperiments/
EDIT:
as mentioned by #altocumulus, this didn't have a copy of the code. Normally I only copy the code from individual's sites as they're much more likely to disappear than something on github. Or I'll copy it when it is short (less than 50 loc?). Anyway, since the meat of the code can probably be pulled out, I've copied mortiz's index.html file below. the other referenced js files can be found elsewhere very easily. (just note that you should probably pull the versions of each library as of Dec 9, 2011)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta charset="utf-8">
<title>Forces and grids</title>
<script type="text/javascript" src="d3.min.js"></script>
<script type="text/javascript" src="d3.layout.min.js"></script>
<script type="text/javascript" src="d3.geom.min.js"></script>
<script type="text/javascript" src="underscore-min.js"></script>
<script src="jquery-1.7.2.min.js" charset="utf-8"></script>
<style type="text/css" media="screen">
.menu { position:absolute; top :20px; right:20px; }
</style>
</head>
<body>
<script type="text/javascript" charset="utf-8">
var w = 700, h = 700;
var vis = d3.select("body").append("svg:svg").attr("width", w).attr("height", h);
var background = vis.append("g");
var nodes = [];
var links = [];
var USE_GRID = true;
var GRID_SIZE = 60;
var GRID_TYPE = "HEXA";
// set up event handlers
$(document).ready(function(){
$("#USE_GRID").click(
function(){
USE_GRID = $(this).is(":checked");
$(this).blur();
force.start();
}
);
//$("#CELL_SIZE").rangeinput();
$("#CELL_SIZE").bind("change",
function(){
console.log($(this).attr("value"));
GRID_SIZE = $(this).attr("value");
grid.init();
force.start();
}
);
$("[name=GRID_TYPE]").click(
function(){
GRID_TYPE = $(this).attr("value");
grid.init();
force.start();
}
);
});
for(var i = 0; i < 30; i++) {
var node = {
label : "node " + i
};
nodes.push(node);
};
for(var i = 0; i < nodes.length; i++) {
for(var j = 0; j < i; j++) {
if(Math.random() > .99-Math.sqrt(i)*.02)
links.push({
source : i,
target : j,
weight :1
});
}
};
var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).linkDistance(function(d){return (1-d.weight)*100}).charge(-3000).linkStrength(function(x) {
return x.weight * 5
});
force.start();
var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke-width", 1.5).style("stroke", "#555").style("opacity", function(d){return d.weight*.7});
var node = vis.selectAll("g.node").data(force.nodes()).enter().append("svg:g").attr("class", "node");
node.append("svg:circle").attr("r", 6).style("fill", "#555").style("stroke", "#FFF").style("stroke-width", "4px");
node.call(force.drag);
var updateLink = function() {
this.attr("x1", function(d) {
return d.source.screenX;
}).attr("y1", function(d) {
return d.source.screenY;
}).attr("x2", function(d) {
return d.target.screenX;
}).attr("y2", function(d) {
return d.target.screenY;
});
}
var updateNode = function() {
this.attr("transform", function(d) {
if(USE_GRID) {
var gridpoint = grid.occupyNearest(d);
if(gridpoint) {
d.screenX = d.screenX || gridpoint.x;
d.screenY = d.screenY || gridpoint.y;
d.screenX += (gridpoint.x - d.screenX) * .2;
d.screenY += (gridpoint.y - d.screenY) * .2;
d.x += (gridpoint.x - d.x) * .05;
d.y += (gridpoint.y - d.y) * .05;
}
} else {
d.screenX = d.x;
d.screenY = d.y;
}
return "translate(" + d.screenX + "," + d.screenY + ")";
});
};
var grid = function(width, height) {
return {
cells : [],
init : function() {
this.cells = [];
for(var i = 0; i < width / GRID_SIZE; i++) {
for(var j = 0; j < height / GRID_SIZE; j++) {
// HACK: ^should be a better way to determine number of rows and cols
var cell;
switch (GRID_TYPE) {
case "PLAIN":
cell = {
x : i * GRID_SIZE,
y : j * GRID_SIZE
};
break;
case "SHIFT_ODD_ROWS":
cell = {
x : i * GRID_SIZE,
y : 1.5 * (j * GRID_SIZE + (i % 2) * GRID_SIZE * .5)
};
break;
case "HEXA":
cell = {
x : i * GRID_SIZE + (j % 2) * GRID_SIZE * .5,
y : j * GRID_SIZE * .85
};
break;
}
this.cells.push(cell);
};
};
},
sqdist : function(a, b) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
},
occupyNearest : function(p) {
var minDist = 1000000;
var d;
var candidate = null;
for(var i = 0; i < this.cells.length; i++) {
if(!this.cells[i].occupied && ( d = this.sqdist(p, this.cells[i])) < minDist) {
minDist = d;
candidate = this.cells[i];
}
}
if(candidate)
candidate.occupied = true;
return candidate;
}
}
}(w, h);
force.on("tick", function() {
vis.select("g.gridcanvas").remove();
if(USE_GRID) {
grid.init();
var gridCanvas = vis.append("svg:g").attr("class", "gridcanvas");
_.each(grid.cells, function(c) {
gridCanvas.append("svg:circle").attr("cx", c.x).attr("cy", c.y).attr("r", 2).style("fill", "#555").style("opacity", .3);
});
}
node.call(updateNode);
link.call(updateLink);
});
</script>
<div class="menu">
<div>
<input type="checkbox" id="USE_GRID" checked>use grid</input>
</div>
<div>
<input type="range" min="30" step="10" max="150" id="CELL_SIZE" value="60"></input>
</div>
<div>
<input type="radio" name="GRID_TYPE" value="PLAIN">plain</input>
<input type="radio" name="GRID_TYPE" value="SHIFT_ODD_ROWS">Shift odd rows</input>
<input type="radio" name="GRID_TYPE" value="HEXA" checked>Hexa</input>
</div>
</div>
</body>
You have to apply your custom forces in
force.on("tick", function() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});
so there is no built-in way to do such a thing...
So in your case you have to find the close grid box centers and count the x and y values using the distance between the nodes and the box centers and some gravity equation.
In you case
node
.attr("cx", function(d) {
d.x += f(d).x;
return d.x;
})
.attr("cy", function(d) {
d.y += f(d).y;
return d.y;
});
where f(d) is the vector of your gravity force depends on the distance between the box centers and the actual node d. For example
var blackHole = function (d) {
var gc = {
x: 100,
y: 100
};
var k = 0.1;
var dx = gc.x - d.px;
var dy = gc.y - d.py;
return {
x: k * dx,
y: k * dy
};
};
It is pretty hard to find out an f(d) which really works by multiple gravity centers, so I suggest you to read about such force algorithms. I tried out some funny examples, but none of them works the way you want. ;-)
Now at least:
var grid = function (d) {
var fx = d.px % 100;
if (fx < 0)
fx += 100;
if (fx > 50)
fx -= 100;
var fy = d.py % 100;
if (fy < 0)
fy += 100;
if (fy > 50)
fy -= 100;
var k = -1;
return {
x: k * fx,
y: k * fy
};
};
This is a 100px dense grid with very simple forces... But I guess the result is not what you expected, nodes can overlap, because by force layout only nodes with common links repel each other, at least that is my experience (edit: that's because the negative charge)... I think is could be much easier to build a custom force layout using d3 quad...