How to use x and width in a bar chart with scaleTime? - javascript

I have a codepen here - https://codepen.io/anon/pen/xpaYYw?editors=0010
Its a simple test graph but the date will be formatted like this.
I have dates on the x axis and amounts on the y
How can I use the x scale to set the width and x position of the bars.
layers.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', function(d, i) {
return height - y(d.one);
})
.attr('y', function(d, i) {
return y(d.one);
})
.attr('width', function(d, i) {
return 50;
})
.attr('x', function(d, i) {
return 80*i;
})
.style('fill', (d, i) => {
return colors[i];
});

The problem with your question has nothing to do with programming, or JavaScript, or D3... the problem is a basic dataviz concept (that's why I added the data-visualization tag in your question):
What you're trying to do is not correct! You should not use bars with a time scale. Time scales are for time series (in which we use dots, or dots connected by lines).
If you use bars with time in the x axis you'll face problems:
Positioning the bar: the left margin of the bar will be always at the date you set. The whole bar will lie after that date;
Setting the width of the bar: in a real bar chart, which uses categorical variables for the x axis, the width has no meaning. But in a time scale the width represents time.
However, just for the sake of explanation, let's create this bar chart with a time scale (despite the fact that this is a wrong choice)... Here is how to do it:
First, set the "width" of the bars in time. Let's say, each bar will have 10 days of width:
.attr("width", function(d){
return x(d3.timeDay.offset(d.date, 10)) - x(d.date)
})
Then, set the x position of the bar to the current date less half its width (that is, less 5 days in our example):
.attr('x', function(d, i) {
return x(d3.timeDay.offset(d.date, -5));
})
Finally, don't forget to create a "padding" in the time scale:
var x = d3.scaleTime()
.domain([d3.min(data, function(d) {
return d3.timeDay.offset(d.date, -10);
}), d3.max(data, function(d) {
return d3.timeDay.offset(d.date, 10);
})])
.range([0, width]);
Here is your code with those changes:
var keys = [];
var legendKeys = [];
var maxVal = [];
var w = 800;
var h = 450;
var margin = {
top: 30,
bottom: 40,
left: 50,
right: 20,
};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var colors = ['#FF9A00', '#FFEBB6', '#FFC400', '#B4EDA0', '#FF4436'];
var data = [{
"one": 4306,
"two": 2465,
"three": 2299,
"four": 988,
"five": 554,
"six": 1841,
"date": "2015-05-31T00:00:00"
}, {
"one": 4378,
"two": 2457,
"three": 2348,
"four": 1021,
"five": 498,
"six": 1921,
"date": "2015-06-30T00:00:00"
}, {
"one": 3404,
"two": 2348,
"three": 1655,
"four": 809,
"five": 473,
"six": 1056,
"date": "2015-07-31T00:00:00"
},
];
data.forEach(function(d) {
d.date = new Date(d.date)
})
for (var i = 0; i < data.length; i++) {
for (var key in data[i]) {
if (!data.hasOwnProperty(key) && key !== "date")
maxVal.push(data[i][key]);
}
}
var x = d3.scaleTime()
.domain([d3.min(data, function(d) {
return d3.timeDay.offset(d.date, -10);
}), d3.max(data, function(d) {
return d3.timeDay.offset(d.date, 10);
})])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(maxVal, function(d) {
return d;
})])
.range([height, 0]);
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', w)
.attr('height', h);
var chart = svg.append('g')
.classed('graph', true)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var layersArea = chart.append('g')
.attr('class', 'layers');
var layers = layersArea.append('g')
.attr('class', 'layer');
layers.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', function(d, i) {
return height - y(d.one);
})
.attr('y', function(d, i) {
return y(d.one);
})
// .attr('width', function(d, i) {
// return 50;
// })
.attr("width", function(d) {
return x(d3.timeDay.offset(d.date, 10)) - x(d.date)
})
.attr('x', function(d, i) {
return x(d3.timeDay.offset(d.date, -5));
})
.style('fill', (d, i) => {
return colors[i];
});
chart.append('g')
.classed('x axis', true)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y-%m-%d")).tickValues(data.map(function(d) {
return new Date(d.date)
})));
chart.append('g')
.classed('y axis', true)
.call(d3.axisLeft(y)
.ticks(10));
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

d3js beeswarm with force simulation

I try to do a beeswarm plot with different radius; inspired by this code
The issue I have, is that my point are offset regarding my x axis:
The point on the left should be at 31.7%. I don't understand why, so I would appreciate if you could guide me. This could be improved by changing the domain of x scale, but this can't match the exact value; same issue if I remove the d3.forceCollide()
Thank you,
Data are available here.
Here is my code:
$(document).ready(function () {
function tp(d) {
return d.properties.tp60;
}
function pop_mun(d) {
return d.properties.pop_mun;
}
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 1280 - margin.right - margin.left,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var z = d3.scaleThreshold()
.domain([.2, .3, .4, .5, .6, .7])
.range(["#35ff00", "#f1a340", "#fee0b6",
"#ff0000", "#998ec3", "#542788"]);
var loading = svg.append("text")
.attr("x", (width) / 2)
.attr("y", (height) / 2)
// .attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");
var formatPercent = d3.format(".0%"),
formatNumber = d3.format(".0f");
d3.json('static/data/qp_full.json').then(function (data) {
features = data.features
//1 create scales
var x = d3.scaleLinear()
.domain([0, d3.max(features, tp)/100])
.range([0, width - margin.right])
var y = d3.scaleLinear().domain([0, 0.1]).range([margin.left, width - margin.right])
var r = d3.scaleSqrt().domain([0, d3.max(features, pop_mun)])
.range([0, 25]);
//2 create axis
var xAxis = d3.axisBottom(x).ticks(20)
.tickFormat(formatPercent);
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
var nodes = features.map(function (node, index) {
return {
radius: r(node.properties.pop_mun),
color: '#ff7f0e',
x: x(node.properties.tp60 / 100),
y: height + Math.random(),
pop_mun: node.properties.pop_mun,
tp60: node.properties.tp60
};
});
function tick() {
for (i = 0; i < nodes.length; i++) {
var node = nodes[i];
node.cx = node.x;
node.cy = node.y;
}
}
setTimeout(renderGraph, 10);
function renderGraph() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
// Of course, don't run too long—you'll hang the page!
const NUM_ITERATIONS = 1000;
var force = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().strength(-3))
.force('center', d3.forceCenter(width / 2, height/2))
.force('x', d3.forceX(d => d.x))
.force('y', d3.forceY(d => d.y))
.force('collide', d3.forceCollide().radius(d => d.radius))
.on("tick", tick)
.stop();
force.tick(NUM_ITERATIONS);
force.stop();
svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.radius)
.style("fill", d => z(d.tp60/100))
.on("mouseover", function (d, i) {
d3.select(this).style('fill', "orange")
console.log(i.tp60,i)
svg.append("text")
.attr("id", "t")
.attr("x", function () {
return d.x - 50;
})
.attr("y", function () {
return d.y - 50;
})
.text(function () {
return [x.invert(i.x), i.tp60]; // Value of the text
})
})
.on("mouseout", function (d, i) {
d3.select("#t").remove(); // Remove text location
console.log(i)
d3.select(this).style('fill', z(i.tp60/100));
});
loading.remove();
}
})
})

D3.js v5 modular swarm clusters (variable radius?)

I want to create a visual whereby a swarm contains one big circle and a bunch of satellite circles clinging around it. For a simple demonstration, I have prepared a small version of the data set; each item in the array should have one big circle and then however many smaller circles clinging to it:
var data = [
{'wfoe':'wfoe1','products':d3.range(20)},
{'wfoe':'wfoe2','products':d3.range(40)},
{'wfoe':'wfoe3','products':d3.range(10)}
];
Here is a snippet of my progress:
var margins = {
top: 100,
bottom: 300,
left: 100,
right: 100
};
var height = 250;
var width = 900;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(" + margins.left + "," + margins.top + ")");
var data = [
{'wfoe':'wfoe1','products':d3.range(20)},
{'wfoe':'wfoe2','products':d3.range(40)},
{'wfoe':'wfoe3','products':d3.range(10)}
];
var columns = 4;
var spacing = 250;
var vSpacing = 250;
var fmcG = graphGroup.selectAll('.fmc')
.data(data)
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var xScale = d3.scalePoint()
.range([0, width])
.domain([0, 100]);
var rScale = d3.scaleThreshold()
.range([50,5])
.domain([0,1]);
data.forEach(function(d, i) {
d.x = (i % columns) * spacing;
d.y = ~~((i / columns)) * vSpacing;
});
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d,i) {
return (i % columns) * spacing;
}).strength(0.1))
.force("y", d3.forceY(function(d,i) {
return ~~((i / columns)) * vSpacing;
}).strength(0.01))
.force("collide", d3.forceCollide(function(d,i) { return rScale(i)}))
.stop();
simulation.tick(75);
fmcG.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", function(d,i) {
return rScale(i)
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.style('fill',"#003366");
<script src="https://d3js.org/d3.v5.min.js"></script>
I want to quickly point out that the big circle doesn't represent any data point (they are just going to house a name / logo). I just thought that including it in the simulation data would be the easiest way to introduce the needed force logic for the swarm circles. I thought that an elegant solution would be to use a threshold scale and let the first (i=0) datum always be the biggest circle. Here is what I mean:
var rScale = d3.scaleThreshold()
.range([0, 1])
.domain([50, 5]);
fmcG.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", function(d,i) {
return rScale(i)
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.style('fill',"#003366");
The result I mentioned above (three big circles with little circles all around them) was not achieved, and in fact very few circles were appended and the variable radius component didn't seem to be working as I thought it would. (also no errors displayed in the log).
Question
How can I iteratively create swarms that start with one big circle and append subsequent smaller circles around the initial big circle, as applicable to the sample data set?
You could use a force simulation, like below, only this gives non-deterministic results. However, it's really good when you want to gradually add more nodes. In the below solution, I gave all related nodes a link to the center node, but didn't draw it. This made it possible for linked nodes to attract heavily.
On the other hand, you could also use a bubble chart if you want D3 to find the optimal packing solution for you, without the force working on them. Only downside is you'd have to call the packing function with all nodes every time, and the other nodes might shift because of the new one.
var margins = {
top: 100,
bottom: 300,
left: 100,
right: 100
};
var height = 250;
var width = 900;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(" + margins.left + "," + margins.top + ")");
var data = [{
'wfoe': 'wfoe1',
'products': d3.range(20).map(function(v) {
return v.toString() + '_wfoe1';
})
},
{
'wfoe': 'wfoe2',
'products': d3.range(40).map(function(v) {
return v.toString() + '_wfoe2';
})
},
{
'wfoe': 'wfoe3',
'products': d3.range(10).map(function(v) {
return v.toString() + '_wfoe3';
})
}
];
var columns = 4;
var spacing = 250;
var vSpacing = 250;
function dataToNodesAndLinks(d) {
// Create one giant array of points and
// one link between each wfoe and each product
var nodes = [{
id: d.wfoe,
center: true
}];
var links = [];
d.products.forEach(function(p) {
nodes.push({
id: p,
center: false
});
links.push({
source: d.wfoe,
target: p
});
});
return {
nodes: nodes,
links: links
};
}
var fmcG = graphGroup.selectAll('.fmc')
.data(data.map(function(d, i) {
return dataToNodesAndLinks(d, i);
}))
.enter()
.append('g')
.attr('class', 'fmc')
.attr('id', (d, i) => 'fmc' + i)
.attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate(" + horSpace + "," + vertSpace + ")";
});
var xScale = d3.scalePoint()
.range([0, width])
.domain([0, 100]);
var rScale = d3.scaleThreshold()
.range([50, 5])
.domain([0, 1]);
fmcG.selectAll("circle")
.data(function(d) {
return d.nodes;
})
.enter()
.append("circle")
.attr("id", function(d) {
return d.id;
})
.attr("r", function(d, i) {
return d.center ? rScale(i) * 5 : rScale(i);
})
.style('fill', function(d) { return d.center ? "darkred" : "#003366"; })
fmcG
.each(function(d, i) {
d3.forceSimulation(d.nodes)
.force("collision", d3.forceCollide(function(d) {
return d.center ? rScale(i) * 5 : rScale(i);
}))
.force("center", d3.forceCenter(0, 0))
.force("link", d3
.forceLink(d.links)
.id(function(d) {
return d.id;
})
.distance(0)
.strength(2))
.on('tick', ticked);
});
function ticked() {
fmcG.selectAll("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
<script src="https://d3js.org/d3.v5.js"></script>

d3.js chemical tube bar chart

I am interested in creating this liquid bar chart of sorts. With the pointers/dotted markers pulling off to the side like shown.
22 April - latest code to work with dynamic data http://jsfiddle.net/NYEaX/1855/
latest bar chart code
http://jsfiddle.net/NYEaX/1827/
latest cleaned up water wave code
http://jsfiddle.net/Qh9X5/10331/
//I want to strip down this waterwave code
http://jsfiddle.net/Qh9X5/10091/
//progress chart
http://jsfiddle.net/NYEaX/1740/
latest base code for this bar chart.
http://jsfiddle.net/NYEaX/1822/
var $this = $("#checmicalbars");
var data = [{
"label": "Rendering",
"value": 90,
"startcolor": "#c3da54",
"endcolor": "#c1e500"
},
{
"label": "Character Design",
"value": 95,
"startcolor": "#e94adc",
"endcolor": "#aae3dd"
},
{
"label": "Sketching",
"value": 80,
"startcolor": "#c3da54",
"endcolor": "#fa5283"
},
{
"label": "Story Boarding",
"value": 90,
"startcolor": "#e94adc",
"endcolor": "#f83b03"
},
{
"label": "Drawing",
"value": 82,
"startcolor": "#c3da54",
"endcolor": "#f88504"
},
{
"label": "Painting",
"value": 90,
"startcolor": "#e94adc",
"endcolor": "#f7d200"
}];
var h = 150;
var w = 300;
var options = {
minlimit: 0,
maxlimit: 100
}
// setup scales
var x = d3.scale.ordinal()
.rangeRoundBands([0, w], .1);
var y = d3.scale.linear()
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(this.x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(this.y)
.orient("left");
// setup scales
// chart container
var progresschart = d3.select($this[0]).append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(0,5)");
var barrectsholder = progresschart.append("g")
.attr("class", "chart")
.attr("transform", "translate(15,0)");
// chart container
y.domain([options["minlimit"], options["maxlimit"]]);
//__ bars
var bar = barrectsholder.selectAll("rect")
.data(data);
//__ enter
bar.enter()
.append("rect")
.attr("class", "bar")
.attr("y", h);
//__ update
bar
.attr("y", h)
.attr("height", 0)
.style("fill", function(d){
return d.startcolor;
})
.transition()
.duration(2500)
.style("fill", function(d){
return d.endcolor;
})
.attr("width", 20)
.attr("x", function(d, i) {
return 30 * i;
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return h - y(d.value);
})
//__ exit
bar.exit()
.transition()
.duration(250)
.attr("y", 0)
.attr("height", 0)
.remove();
//__ bars
I've merged the two charts together - but the watercode is translated correctly if its a separate svg - be good to get this code cleaned up/reviewed. Also ensuring the pointers/labels adjust/adapt with more/less data sets.
latest jsfiddle
http://jsfiddle.net/NYEaX/1843/
var $this = $("#checmicalbars");
var data = [{
"label": "Rendering",
"value": 90,
"startcolor": "#c3da54",
"endcolor": "#c1e500"
},
{
"label": "Character Design",
"value": 95,
"startcolor": "#e94adc",
"endcolor": "#aae3dd"
},
{
"label": "Sketching",
"value": 80,
"startcolor": "#c3da54",
"endcolor": "#fa5283"
},
{
"label": "Story Boarding",
"value": 90,
"startcolor": "#e94adc",
"endcolor": "#f83b03"
},
{
"label": "Drawing",
"value": 82,
"startcolor": "#c3da54",
"endcolor": "#f88504"
},
{
"label": "Painting",
"value": 90,
"startcolor": "#e94adc",
"endcolor": "#f7d200"
}];
var h = 450;
var w = 400;
var barHeight = 150;
var barWidth = 180;
var options = {
minlimit: 0,
maxlimit: 100
}
// setup scales
var x = d3.scale.ordinal()
.rangeRoundBands([0, barWidth], .1);
var y = d3.scale.linear()
.range([barHeight, 0]);
var xAxis = d3.svg.axis()
.scale(this.x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(this.y)
.orient("left");
// setup scales
// chart container
var progresschart = d3.select($this[0]).append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(0,5)");
var barrectsholder = progresschart.append("g")
.attr("class", "barrectsholder")
.attr("transform", "translate(15,0)");
var labelsholder = progresschart.append("g")
.attr("class", "labelsholder")
.attr("transform", "translate(10,"+(barHeight+ 20)+")");
var lineholder = progresschart.append("g")
.attr("class", "lineholder")
.attr("transform", "translate(25,"+(barHeight+ 15)+")");
// chart container
y.domain([options["minlimit"], options["maxlimit"]]);
/*
var gauge = barrectsholder
.append("g")
.attr("width", config.w)
.attr("height", config.h)
.append("g");
liquidBar(gauge, config);
*/
var bar = barrectsholder.selectAll("svg")
.data(data);
bar.enter()
.append("svg")
.attr("class", function(d, i) {
return "bar"+i;
})
.attr("width", 20)
.attr("x", function(d, i) {
return 30 * i;
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return barHeight - y(d.value);
})
$.each(data, function( index, value ) {
//alert( index + ": " + value );
var config = {
w: 20,
h: barHeight,
value: value.value,
amplitude: 0.02, // The wave height as a percentage of the radius of the wave circle.
countPerWidth: 1, // The number of full waves per width of the wave circle.
riseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
animateTime: 1000, // The amount of time in milliseconds for a full wave to enter the wave circle.
rise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
colorTransition: 1000,
colorBefore: value.startcolor, // The color before of the fill wave.
colorAfter: value.endcolor, // The color after of the fill wave.
offset: 0 // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
};
var gauge = barrectsholder.selectAll("svg.bar"+index)
liquidBar(gauge, config);
});
/*
//__ bars
var bar = barrectsholder.selectAll("rect")
.data(data);
//__ enter
bar.enter()
.append("rect")
.attr("class", "bar")
.attr("y", barHeight);
//__ update
bar
.attr("y", barHeight)
.attr("height", 0)
.style("fill", function(d){
return d.startcolor;
})
.transition()
.duration(2500)
.style("fill", function(d){
return d.endcolor;
})
.attr("width", 20)
.attr("x", function(d, i) {
return 30 * i;
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return barHeight - y(d.value);
})
//__ exit
bar.exit()
.transition()
.duration(250)
.attr("y", 0)
.attr("height", 0)
.remove();
//__ bars
*/
//__ labels
var labels = labelsholder.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("class", "barlabels")
.attr("x", 200)
.attr("y", function(d, i) {
return 20 * i;
})
.text(function(d) {
return d.label;
})
var lines = lineholder.selectAll("text")
.data(data);
lines.enter()
.append("line")// attach a line
.style("stroke-dasharray", ("3, 3"))
.style("stroke", "black")// colour the line
.attr("x1", function(d, i) {
return barWidth-(30 * (i+1));
})//x pos of the 1st end of the line
.attr("y1", function(d, i) {
return 20 * i;
})//y pos of the 1st end of the line
.attr("x2", function(d, i) {
return barWidth;
})//x pos of the 2nd end of the line
.attr("y2", function(d, i) {
return 20 * i;
});//y pos of the 2nd end of the line
var lineHeights = 100;
lines.enter()
.append("line")// attach a line
.style("stroke-dasharray", ("3, 3"))
.style("stroke", "black")// colour the line
.attr("x1", function(d, i) {
return 30 * i;
})//x pos of the 1st end of the line
.attr("y1", function(d, i) {
return lineHeights - (20 * i);
})//y pos of the 1st end of the line
.attr("x2", function(d, i) {
return 30 * i;
})//x pos of the 2nd end of the line
.attr("y2", function(d, i) {
return -15;
});//y pos of the 2nd end of the line
function liquidBar(gauge, config) {
var fillPercent = Math.max(0, Math.min(100, config.value)) / 100;
var waveHeightScale = d3.scale.linear()
.range([0, config.amplitude, 0])
.domain([0, 50, 100]);
var waveHeight = (config.h / 2) * waveHeightScale(fillPercent * 100);
var waveLength = config.w / config.countPerWidth;
var waveClipCount = 1 + config.countPerWidth;
var waveClipWidth = waveLength * waveClipCount;
// Data for building the clip wave area.
var data = [];
for (var i = 0; i <= 40 * waveClipCount; i++) {
data.push({
x: i / (40 * waveClipCount),
y: (i / (40))
});
}
// Scales for controlling the size of the clipping path.
var waveScaleX = d3.scale.linear().range([0, waveClipWidth]).domain([0, 1]);
var waveScaleY = d3.scale.linear().range([0, waveHeight]).domain([0, 1]);
// Scales for controlling the position of the clipping path.
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(config.h + waveHeight), (waveHeight)])
.domain([0, 1]);
var waveAnimateScale = d3.scale.linear()
.range([0, waveClipWidth - config.w]) // Push the clip area one full wave then snap back.
.domain([0, 1]);
// Center the gauge within the parent SVG.
var gaugeGroup = gauge.append("g")
.attr("class", "gaugeGroup")
.attr("transform", "translate(0,0)");
var randomId = Math.floor(Math.random() * 26) + Date.now();
// The clipping wave area.
var clipArea = d3.svg.area()
.x(function(d) {
return waveScaleX(d.x);
})
.y0(function(d) {
return waveScaleY(Math.sin(Math.PI * 2 * config.offset * -1 + Math.PI * 2 * (1 - config.countPerWidth) + d.y * 2 * Math.PI));
})
.y1(function(d) {
return (config.h + waveHeight);
});
var waveGroup = gaugeGroup.append("defs")
.append("clipPath")
.attr("id", "clipWave" + randomId);
var wave = waveGroup.append("path")
.datum(data)
.attr("d", clipArea)
.attr("T", 0);
// The inner circle with the clipping wave attached.
var fillGroup = gaugeGroup.append("g")
.attr("clip-path", "url(#clipWave" + randomId + ")");
fillGroup.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", config.w)
.attr("height", config.h)
.style("fill", config.colorBefore)
.transition()
.duration(config.colorTransition)
.style("fill", config.colorAfter);
// Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
var waveGroupXPosition = config.w - waveClipWidth;
if (config.rise) {
waveGroup.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(0) + ')')
.transition()
.duration(config.riseTime)
.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')')
.each("start", function() {
wave.attr('transform', 'translate(1,0)');
}); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
} else {
waveGroup.attr('transform', 'translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')');
}
animateWave();
function animateWave() {
wave.attr('transform', 'translate(' + waveAnimateScale(wave.attr('T')) + ',0)');
wave.transition()
.duration(config.animateTime * (1 - wave.attr('T')))
.ease('linear')
.attr('transform', 'translate(' + waveAnimateScale(1) + ',0)')
.attr('T', 1)
.each('end', function() {
wave.attr('T', 0);
animateWave(config.animateTime);
});
}
}

Trying to transfer Tributary D3.js example into JSFiddle

I'm rather a beginner at both JS and D3.js; my JSFiddle is here.
JSHint shows no errors, so I think I'm doing the D3 wrong.
I'm trying to do the same thing as in these questions, adapting a Tributary example to run outside of that platform: "Exporting" a Tributary example that makes use of the tributary object - d3.js and Getting horizontal stack bar example to display using d3.js (I'm even adapting the same code as the latter) Unfortunately, there is no corrected JSFiddle or other example in either answer
Here's my code:
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
I think you are just missing a (); at the end of your function and before the semicolon. That would make it self executing. I forked your fiddle.
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
}();

d3.js Pictogram chart

I am working on a pictogram application.
Here is my latest code. I am going to attempt to place a texture layer on top of the rectangular blocks. Is there way of controlling the padding for the various axis?
var pictogramData = [
{
"label": "8",
"value": 8
},
{
"label": "9",
"value": 4
},
{
"label": "10",
"value": 9
},
{
"label": "11",
"value": 12
}
];
var margins = {
top: 0,
left: 30,
right: 24,
bottom: 0
};
var chart,
width = 300,
pixelGap = 2,
bar_height = 15,
height = ((bar_height + pixelGap) * pictogramData.length),
gapHeights = (pixelGap) * pictogramData.length;
svg = d3.select("#step-1")
.append('svg');
svg
.append('defs')
.append('pattern')
.attr('id', 'diagonalHatch')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 4)
.attr('height', 4)
.append('path')
.attr('d', 'M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2')
.attr('stroke', '#000000')
.attr('stroke-width', 1);
chartWidth = width * 0.8;
chart = svg.append('g')
.attr('class', 'chart')
.attr('width', chartWidth)
.attr('height', height+gapHeights)
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
valueList = svg.append('g')
.attr('class', 'axis')
.attr('width', width *0.2)
.attr('transform', 'translate(' + (width - margins.right) + ',' + margins.top + ')')
chart
.append('g')
var x, y;
var max = d3.max(pictogramData, function(d) { return +d.value;} );
function getValueDomain(data){
var valueDomain = new Array();
for (i = 0; i < data.length; ++i) {
valueDomain.push(data[i].value);
}
return valueDomain;
}
var valueArray = getValueDomain(pictogramData);
x = d3.scale.linear()
.domain([0, max])
.range([0, chartWidth]);
y = d3.scale.ordinal()
.domain(valueArray)
.rangeBands([0, height]);
function plotRectangleGroups(groupName, pictogramData, chartWidth){
//Add a group to hold the rects
var group = chart.append("g")
.attr("class", groupName+"group");
group.selectAll("rect")
.data(pictogramData)
.enter().append("rect")
.attr("x", 0)
.attr("y", function(d, i){
return y(d.value) + (pixelGap*i);
})
.attr("width", function(d, i){
var barWidth = chartWidth;
if(
groupName != "base" &&
groupName != "pattern"
){
barWidth = x(d.value);
}
return barWidth;
})
.attr("height", y.rangeBand())
.attr('fill', function(){
var fill;
if(groupName == "pattern"){
fill = 'url(#diagonalHatch)';
}
return fill;
});
}
plotRectangleGroups("base", pictogramData, chartWidth);
plotRectangleGroups("rects", pictogramData, chartWidth);
plotRectangleGroups("pattern", pictogramData, chartWidth);
//left labels
labels = pictogramData.map(function (d) {
return d.label;
});
yScale = d3.scale.ordinal()
.domain(labels)
.rangeRoundBands([0, height]),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
chart.append('g')
.attr('class', 'axis')
.call(yAxis);
//right labels
values = pictogramData.map(function (d) {
return d.value;
});
yScale = d3.scale.ordinal()
.domain(values)
.rangeRoundBands([0, height]),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('right'),
valueList
.call(yAxis);
http://jsfiddle.net/4zt64yj8/18/
The misalignment of the bars and the tick labels in the Y axis comes from the pixelGap value.
pixelGap = 1,
...
.attr("y", function(d, i){
return y(d.value) + (pixelGap*i);
})
As you can see the pixelgap is used to add a little white space between the bars, but you (or whoever created the chart) forgot to compensate for them in the range. The pixelGap*i means that every next bar is pushed down a bit further, while the corresponding labels are not pushed down.
Simplest fix is to remove the multiplication and modify both the y and the height attribute:
group.selectAll("rect")
.data(pictogramData)
.enter().append("rect")
.attr("x", 0)
.attr("y", function(d, i){
return y(d.value) + pixelGap;
})
.attr("width", function(d, i){
var barWidth = chartWidth;
if(
groupName != "base" &&
groupName != "pattern"
){
barWidth = x(d.value);
}
return barWidth;
})
.attr("height", y.rangeBand() - pixelGap)
With this change the vertical padding is essentially equally spread out above and below the bar, which in turn gets rid of the misalignment.

Categories