This is probably a pretty specific question:
My problem is that in d3.js i need to create a radial chart.
I created the axis and labels.
Now i want to draw the radialLine.
It creates the path objects in my HTML document,
but without any coordinates.
I think it has something to do with the way the radius/data is provided to the radialLine, but can't figure out what to change...
Hopefully someone sees my mistake.
I also created a JSfiddle:
complete JSfiddle
//Data:
var notebookData = [{
model: "Levecchio 620RE",
data: [579, 8, 2.4, 256, 13.3]
}];
var categories = [
"Price",
"RAM",
"CPU",
"Storage",
"Display"
];
var priceScale = d3.scaleLinear().domain([2500,300]).range([0,100]);
var ramScale = d3.scaleLinear().domain([0,32]).range([0,100]);
var cpuScale = d3.scaleLinear().domain([1.0,3.2]).range([0,100]);
var storageScale = d3.scaleLinear().domain([64,2048]).range([0,100]);
var displaySizeScale = d3.scaleLinear().domain([10.0,20.0]).range([0,100]);
function selectScale(category_name) {
switch(category_name) {
case "Price":
return priceScale;
case "RAM":
return ramScale;
case "CPU":
return cpuScale;
case "Storage":
return storageScale;
case "Display":
return displaySizeScale;
}
}
var scaledData = notebookData.map(function (el) {
return el.data.map(function (el2, i) { //el = 1 notebook
return selectScale(categories[i])(el2);
});
});
//My RadialLine
//generatorfunction
var radarLine = d3.radialLine()
.radius(function(d) { return scaledData(d.value); })
.angle(function(d,i) { return i*angleSlice; })
.curve(d3.curveLinearClosed)
;
//Create the wrapper
var radarWrapper = g.selectAll(".radarWrapper")
.data(notebookData)
.enter().append("g")
.attr("class", "radarWrapper")
;
//Create pathlines
radarWrapper.append("path")
.attr("class", "radarStroke")
.attr("d", function(d,i) { return radarLine(d); })
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function(d,i) { return cfg.color(i); })
.style("fill", "none")
;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've edited your fiddle a bit to make it work:
https://jsfiddle.net/2qgygksL/75/
Basicly what i've done:
fix the color scheme
var colors = d3.scale.category10();
instead of
var colors = d3.scale.ordinal(d3.schemeCategory10);
added data to path
radarWrapper.append("path")
.data(scaledData)
change radius to
.radius(function(d, i) {
return d;
})
since You used something like return scaledData(d.value); where your scaledData is an array.
Related
I have two elements I need to render and a context of the big picture I am trying to achieve (a complete dashboard).
One is a chart that renders fine.
$scope.riskChart = new dc.pieChart('#risk-chart');
$scope.riskChart
.width(width)
.height(height)
.radius(Math.round(height/2.0))
.innerRadius(Math.round(height/4.0))
.dimension($scope.quarter)
.group($scope.quarterGroup)
.transitionDuration(250);
The other is a triangle, to be used for a more complex shape
$scope.openChart = d3.select("#risk-chart svg g")
.enter()
.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", d3.symbol('triangle-up'))
.attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
.style("fill", fill);
On invocation of render functions, the dc.js render function is recognized and the chart is seen, but the d3.js render() function is not recognized.
How do I add this shape to my dc.js canvas (an svg element).
$scope.riskChart.render(); <--------------Works!
$scope.openChart.render(); <--------------Doesn't work (d3.js)!
How do I make this work?
EDIT:
I modified dc.js to include my custom chart, it is a work in progress.
dc.starChart = function(parent, fill) {
var _chart = {};
var _count = null, _category = null;
var _width, _height;
var _root = null, _svg = null, _g = null;
var _region;
var _minHeight = 20;
var _dispatch = d3.dispatch('jump');
_chart.count = function(count) {
if(!arguments.length)
return _count;
_count = count;
return _chart;
};
_chart.category = function(category) {
if(!arguments.length)
return _category
_category = category;
return _chart;
};
function count() {
return _count;
}
function category() {
return _category;
}
function y(height) {
return isNaN(height) ? 3 : _y(0) - _y(height);
}
_chart.redraw = function(fill) {
var color = fill;
var triangle = d3.symbol('triangle-up');
this._g.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", triangle)
.attr("transform", function(d) { return "translate(" + 25 + "," + 25 + ")"; })
.style("fill", fill);
return _chart;
};
_chart.render = function() {
_g = _svg
.append('g');
_svg.on('click', function() {
if(_x)
_dispatch.jump(_x.invert(d3.mouse(this)[0]));
});
if (_root.select('svg'))
_chart.redraw();
else{
resetSvg();
generateSvg();
}
return _chart;
};
_chart.on = function(event, callback) {
_dispatch.on(event, callback);
return _chart;
};
_chart.width = function(w) {
if(!arguments.length)
return this._width;
this._width = w;
return _chart;
};
_chart.height = function(h) {
if(!arguments.length)
return this._height;
this._height = h;
return _chart;
};
_chart.select = function(s) {
return this._root.select(s);
};
_chart.selectAll = function(s) {
return this._root.selectAll(s);
};
function resetSvg() {
if (_root.select('svg'))
_chart.select('svg').remove();
generateSvg();
}
function generateSvg() {
this._svg = _root.append('svg')
.attr({width: _chart.width(),
height: _chart.height()});
}
_root = d3.select(parent);
return _chart;
}
I think I confused matters by talking about how to create a new chart, when really you just want to add a symbol to an existing chart.
In order to add things to an existing chart, the easiest thing to do is put an event handler on its pretransition or renderlet event. The pretransition event fires immediately once a chart is rendered or redrawn; the renderlet event fires after its animated transitions are complete.
Adapting your code to D3v4/5 and sticking it in a pretransition handler might look like this:
yearRingChart.on('pretransition', chart => {
let tri = chart.select('svg g') // 1
.selectAll('path.triangle') // 2
.data([0]); // 1
tri = tri.enter()
.append('path')
.attr('class', 'triangle')
.merge(tri);
tri
.attr("d", d3.symbol().type(d3.symbolTriangle).size(200))
.style("fill", 'darkgreen'); // 5
})
Some notes:
Use chart.select to select items within the chart. It's no different from using D3 directly, but it's a little safer. We select the containing <g> here, which is where we want to add the triangle.
Whether or not the triangle is already there, select it.
.data([0]) is a trick to add an element once, only if it doesn't exist - any array of size 1 will do
If there is no triangle, append one and merge it into the selection. Now tri will contain exactly one old or new triangle.
Define any attributes on the triangle, here using d3.symbol to define a triangle of area 200.
Example fiddle.
Because the triangle is not bound to any data array, .enter() should not be called.
Try this way:
$scope.openChart = d3.select("#risk-chart svg g")
.attr("width", 55)
.attr("height", 55)
.append('path')
.attr("d", d3.symbol('triangle-up'))
.attr("transform", function(d) { return "translate(" + 100 + "," + 100 + ")"; })
.style("fill", fill);
I am trying to use custom animation in plottable.js when data updates.
Below is my code : -
<script type="text/javascript">
var xScale = new Plottable.Scales.Category();
var yScale = new Plottable.Scales.Linear().domain([0,30]);
var xAxis = new Plottable.Axes.Category(xScale, "bottom");
var yAxis = new Plottable.Axes.Numeric(yScale, "left");
var dataset;
var data;
function createChart() {
data = [];
for(var i=0; i<10; i++) {
data.push({x:"Data" + (i + 1),y:Math.abs(Math.random() * 10)});
}
dataset = new Plottable.Dataset(data);
makeChart();
}
function updateChart() {
data = [];
for(var i=0; i<10; i++) {
data.push({x:"Data" + (i + 1),y:Math.abs(Math.random() * 10)});
}
dataset.data(data);
}
function makeChart() {
var linePlot = new Plottable.Plots.Line()
.addDataset(dataset)
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale)
.attr("stroke","#FA8116")
.animated(true)
.animator("test",new Plottable.Animators.Easing().easingMode("bounce"));
var label_y = new Plottable.Components.AxisLabel("Parameter 2", -90);
var label_x = new Plottable.Components.AxisLabel("Parameter 1", 0);
var chart = new Plottable.Components.Table([
[label_y, yAxis, linePlot],
[null, null, xAxis],
[null, null, label_x]
]);
chart.renderTo("svg#lineChart");
// Responsive Layout
window.addEventListener("resize", function() {
chart.redraw();
});
}
$(document).ready(function() {
createChart();
setInterval(function(d) {
updateChart();
},5000);
});
</script>
I want to animate lineplot other than default and I did this :-
var linePlot = new Plottable.Plots.Line()
.addDataset(dataset)
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale)
.attr("stroke","#FA8116")
.animated(true)
.animator("test",new Plottable.Animators.Easing().easingMode("bounce"));
I don`t understand where I am in correct and since I am new to plottable can you guys help me out, also is there a way to use d3 based animation with plottable ?? If yes can you provide a code snippet
Thanx in advance
Plots normally have two Animators: MAIN and RESET. You need to specify that you want to change the primary Animator on the Plot:
plot.animator(Plottable.Plots.Animator.MAIN,
new Plottable.Animators.Easing().easingMode("bounce"));
I want to use d3.chart() for the charts I have written already. I found examples of d3.chart() for circle and barcharts but not for line charts. My charts are line charts, I need to use following code in d3.charts()
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
but am facing problem when try to use like this
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="d3.v3.min.js"></script>
<script type="text/javascript" src="d3.chart.min.js"></script>
</head>
<body>
<div id="vis"></div>
<script type="text/javascript">
d3.chart("linechart", {
initialize: function() {
// create a base scale we will use later.
var chart = this;
chart.w = chart.base.attr('width') || 200;
chart.h = chart.base.attr('height') || 150;
chart.w = +chart.w;
chart.h = +chart.h;
chart.x = d3.scale.linear()
.range([0, chart.w]);
chart.y = d3.scale.linear()
.range([chart.h,0]);
chart.base.classed('line', true);
this.areas = {};
chart.areas.lines = chart.base.append('g')
.classed('lines', true)
.attr('width', chart.w)
.attr('height', chart.h)
chart.line = d3.svg.line()
.x(function(d) { return chart.x(d.x);})
.y(function(d) { return chart.y(d.y);});
this.layer("lines", chart.areas.lines, {
dataBind: function(data) {
// update the domain of the xScale since it depends on the data
chart.y.domain([d3.min(data,function(d){return d.y}),d3.max(data,function(d){return d.y})])
chart.x.domain(d3.extent(data, function(d) { return d.x; }));
// return a data bound selection for the passed in data.
return this.append("path")
.datum(data)
.attr("d", chart.line)
.attr('stroke','#1ABC9C')
.attr('stroke-width','2')
.attr('fill','none');
},
insert: function() {
return null;
},
});
},
// configures the width of the chart.
// when called without arguments, returns the
// current width.
width: function(newWidth) {
if (arguments.length === 0) {
return this.w;
}
this.w = newWidth;
return this;
},
// configures the height of the chart.
// when called without arguments, returns the
// current height.
height: function(newHeight) {
if (arguments.length === 0) {
return this.h;
}
this.h = newHeight;
return this;
},
});
var data = [
{x: 0,y:190},
{x: 1,y:10},{x: 2,y:40},{x: 3,y:90},
{x: 4,y:30},{x: 5,y:20},{x: 6,y:10}
];
var chart1 = d3.select("#vis")
.append("svg")
.chart("linechart")
.width(720)
.height(320)
chart1.draw(data);
</script>
</body>
</html>
error:
Uncaught Error: [d3.chart] Layer selection not properly bound.
I have get the line and error as well.
Note: Get d3.chart.min.js from this link
Get d3.v3.min.js from this link
Updated: I got answer from #LarsKotthoff answer, but there is different in image. check this links Before apply D3 and After apply D3.
It looks like you have confused the insert and dataBind actions -- in the former, you're supposed to append the new elements while the latter only binds the data. With the modifications below, your code works fine for me.
dataBind: function(data) {
// update the domain of the xScale since it depends on the data
chart.y.domain([d3.min(data,function(d){return d.y}),d3.max(data,function(d){return d.y})])
chart.x.domain(d3.extent(data, function(d) { return d.x; }));
// return a data bound selection for the passed in data.
return this.selectAll("path").data([data]);
},
insert: function() {
return this.append("path")
.attr("d", chart.line)
.attr('stroke','#1ABC9C')
.attr('stroke-width','2')
.attr('fill','none');
}
Note that this won't work for several lines -- to do that, change .data([data]) to .data(data) and use a nested array, e.g. [[{x:0,y:0},...], [{x:1,y:1},...], ...].
In my line chart, initially all data needed for plot line. There is two button Make and sell, If I click on any one of the button, data related to that button should be plotted for line with transition effect as shown in this link . I have tried to make this work, but I can't. I tried to make relation between button and line chart as given below(code), its not working. I have hard coded buttonId in my code with sell to have data related to sell, if I change it to Make I will get data related to Make but I need to hard coded here. what I want is when a Make button clicked data related to Make should come with transition as above link shown. My code is
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="d3.v3.min.js"></script>
<script type="text/javascript" src="d3.chart.min.js"></script>
</head>
<body>
<div id="vis"></div>
<button id="Make">Make</button>
<button id="sell">sell</button>
<script type="text/javascript">
d3.chart("linechart", {
initialize: function() {
// create a base scale we will use later.
var chart = this;
chart.w = chart.base.attr('width') || 200;
chart.h = chart.base.attr('height') || 150;
chart.w = +chart.w;
chart.h = +chart.h;
buttonId = 'sell'
chart.x = d3.scale.linear()
.range([0, chart.w]);
chart.y = d3.scale.linear()
.range([chart.h,0]);
chart.base.classed('line', true);
this.areas = {};
chart.areas.lines = chart.base.append('g')
.classed('lines', true)
.attr('width', chart.w)
.attr('height', chart.h)
chart.line = d3.svg.line()
.x(function(d) { return chart.x(d.x);})
.y(function(d) { return chart.y(d.y);});
this.layer("lines", chart.areas.lines, {
dataBind: function(data) {
// update the domain of the xScale since it depends on the data
chart.y.domain([d3.min(data,function(d){return d.y}),d3.max(data,function(d){return d.y})])
var mydata=new Array();
if(buttonId)
{
var j=0;
for(var i in data)
{
if(data[i].custody === buttonId){mydata[j] = data[i]; j++;};
}
chart.x.domain(d3.extent(mydata, function(d) { return d.x; }));
}
else
{
chart.x.domain(d3.extent(data, function(d) { return d.x; }));
}
// return a data bound selection for the passed in data.
return this.selectAll("path").data([data]);
},
insert: function() {
return this.append("path")
.datum(data)
.attr("d", chart.line)
.attr('stroke','#1ABC9C')
.attr('stroke-width','2')
.attr('fill','none');
}
});
},
// configures the width of the chart.
// when called without arguments, returns the
// current width.
width: function(newWidth) {
if (arguments.length === 0) {
return this.w;
}
this.w = newWidth;
return this;
},
// configures the height of the chart.
// when called without arguments, returns the
// current height.
height: function(newHeight) {
if (arguments.length === 0) {
return this.h;
}
this.h = newHeight;
return this;
},
});
var data = [
{x: 0,y:190, custody: "Make"},
{x: 1,y:10, custody: "Make"},{x: 2,y:40, custody: "Make"},{x: 3,y:90, custody: "Make"},
{x: 4,y:30, custody: "sell"},{x: 5,y:20, custody: "sell"},{x: 6,y:10, custody: "sell"},
{x: 7,y:40, custody: "sell"}
];
var chart1 = d3.select("#vis")
.append("svg")
.chart("linechart")
.width(720)
.height(320)
chart1.draw(data);
</script>
</body>
</html>
Get d3.chart.min.js from d3.chart.min.js
and get d3.v3.min.js from d3.v3.min.js
I am trying to create a data visualisation for some student related data (sample record below) but when d3 renders it, it goes through the data twice and overwrites it, leaving only the results for the second time through only on the screen. I am using a row counter here to so I have a way to set the y coord of each rectangle based how many rectangles there are. And I think this has somehow messed things up a little. Any help on how to make it so the data does not get iterated through twice would be greatly appreciated.
Also, just in case it matters, this code is living within an angular.js directive.
Apologies if I am just doing something really silly here
// student records sample...
var studentData = [
{
"studentID" : 1001,
"firstName" : "jill",
"lastName" : "smith",
"workLoadDifficulty" : 16,
"smileStartAngle" : -90,
"smileEndAngle" : 90,
},
{
"studentID" : 1008,
"firstName" : "bob",
"lastName" : "smith",
"workLoadDifficulty" : 99,
"smileStartAngle" : 90,
"smileEndAngle" : -90,
}
];
(function () {
'use strict';
angular.module('learnerApp.directives')
.directive('d3Bars', ['d3', function(d3) {
return {
restrict: 'EA',
scope: {
data: "=",
label: "#",
onClick: "&"
},
link: function(scope, iElement, iAttrs) {
var paddingForShape = 10;
var rowCounter = -1;
var height = 400;
var width = 300;
var svgContainer = d3.select(iElement[0])
.append("svg")
.attr("width", width)
.attr('height', height);
// on window resize, re-render d3 canvas
window.onresize = function() {
return scope.$apply();
};
scope.$watch(function(){
return angular.element(window)[0].innerWidth;
}, function(){
return scope.render(scope.data);
}
);
// watch for data changes and re-render
scope.$watch('studentData', function(newVals, oldVals) {
return scope.render(newVals);
}, true);
// define render function
scope.render = function(data){
// remove all previous items before render
svgContainer.selectAll("*").remove();
var workLoadColor = d3.scale.category10()
.domain([0,100])
.range(['#02FA28', '#73FA87', '#C0FAC9','#FAE4C0', '#FAC775', '#FAA823','#FA9A00','#FA8288', '#FC4750', '#FA0511' ])
var studentRects = svgContainer.selectAll('rect')
.data(studentData, function(d) {
console.log(d.studentID);
console.log('hello');
return "keyVal" + d.studentID;
})
.enter()
.append("rect");
var studentRectAttributes = studentRects
.attr("x", function(d,i) {
return ((i * 50) % width) + paddingForShape;
})
.attr("y", function(d,i) {
var value = ((i * 50) % width)
if (value === 0) {
rowCounter = rowCounter + 1;
}
var value = (rowCounter * 50);
console.log('Y Val: ', i);
console.log(value);
return value;
})
.attr("height", 30)
.attr("width", 40)
.style("fill", function(d) {
return workLoadColor(d.workLoadDifficulty)
});
};
}
};
}]);
}());
If you're performing a data join twice, you need to specify a key so you don't overwrite the elements that are in your current selection. You may want to change your studentRects definition to:
var studentRects = svgContainer.selectAll('rect')
.data(studentData, function(d) { return d.firstName + ' ' + d.lastName; });
studentRects.enter().append("rect");
See selection.data([values[, key]])
If a key function is not specified, then the first datum in the specified array is assigned to the first element in the current selection, the second datum to the second selected element, and so on.
Try changing your selector to var studentRects = svgContainer.selectAll('rect') which will match the <rect> elements you are adding on enter()
** UPDATED **
Along with the key advice #Wex gave, I plopped the code in plunker and got it working. You have an extra watch on your scope, removing it addresses the problem (you may want to revisit some of the d3 docs regarding enter/exit though):
scope.$watch(function(){
return angular.element(window)[0].innerWidth;
}, function(){
return scope.render(scope.data);
});
Plunker here: http://plnkr.co/edit/z0MXkUVFNmMEGJAaz7dw?p=preview