d3.js chemical tube bar chart - javascript

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);
});
}
}

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();
}
})
})

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

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>

How to create vertically grouped bar chart in d3.js using json data?

In a web application I was supposed to create a vertically grouped bar chart using d3.js using json data. Previously I create a horizontal grouped bar using the following code. Can anyone help me out? Thanks in advance.
var data = {
labels: [
'resilience', 'maintainability', 'accessibility',
'uptime', 'functionality', 'impact'
],
series: [
{
label: '2012',
values: [4, 8, 15, 16, 23, 42]
},
{
label: '2013',
values: [12, 43, 22, 11, 73, 25]
},
{
label: '2014',
values: [31, 28, 14, 8, 15, 21]
},]
};
var chartWidth = 300,
barHeight = 20,
groupHeight = barHeight * data.series.length,
gapBetweenGroups = 10,
spaceForLabels = 150,
spaceForLegend = 150;
// Zip the series data together (first values, second values, etc.)
var zippedData = [];
for (var i=0; i<data.labels.length; i++) {
for (var j=0; j<data.series.length; j++) {
zippedData.push(data.series[j].values[i]);
}
}
// Color scale
var color = d3.scale.category20();
var chartHeight = barHeight * zippedData.length + gapBetweenGroups * data.labels.length;
var x = d3.scale.linear()
.domain([0, d3.max(zippedData)])
.range([0, chartWidth]);
var y = d3.scale.linear()
.range([chartHeight + gapBetweenGroups, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.tickFormat('')
.tickSize(0)
.orient("left");
// Specify the chart area and dimensions
var chart = d3.select(".chart")
.attr("width", spaceForLabels + chartWidth + spaceForLegend)
.attr("height", chartHeight);
// Create bars
var bar = chart.selectAll("g")
.data(zippedData)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + spaceForLabels + "," + (i * barHeight + gapBetweenGroups * (0.5 + Math.floor(i/data.series.length))) + ")";
});
// Create rectangles of the correct width
bar.append("rect")
.attr("fill", function(d,i) { return color(i % data.series.length); })
.attr("class", "bar")
.attr("width", x)
.attr("height", barHeight - 1);
// Add text label in bar
bar.append("text")
.attr("x", function(d) { return x(d) - 3; })
.attr("y", barHeight / 2)
.attr("fill", "red")
.attr("dy", ".35em")
.text(function(d) { return d; });
// Draw labels
bar.append("text")
.attr("class", "label")
.attr("x", function(d) { return - 10; })
.attr("y", groupHeight / 2)
.attr("dy", ".35em")
.text(function(d,i) {
if (i % data.series.length === 0)
return data.labels[Math.floor(i/data.series.length)];
else
return ""});
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + spaceForLabels + ", " + -gapBetweenGroups/2 + ")")
.call(yAxis);
// Draw legend
var legendRectSize = 18,
legendSpacing = 4;
var legend = chart.selectAll('.legend')
.data(data.series)
.enter()
.append('g')
.attr('transform', function (d, i) {
var height = legendRectSize + legendSpacing;
var offset = -gapBetweenGroups/2;
var horz = spaceForLabels + chartWidth + 40 - legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function (d, i) { return color(i); })
.style('stroke', function (d, i) { return color(i); });
legend.append('text')
.attr('class', 'legend')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) { return d.label; });
After continuous digging I found the correct way of doing this. Thanks to Mike Bostock for the example he provided in here. In here you can also find out the elaborate discussion of that example. Thanks for your support :)
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
//console.log(margin.left);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
/*Our json object is [{letter: "A", frequency: .08167,depth:.32},{letter: "B", frequency: .01492,depth:.69}]
To use csv file you just need to follow the link I provided
*/
var data = [
{letter: "A", frequency: .08167,depth:.32},
{letter: "B", frequency: .01492,depth:.69}
];
var groupNames=d3.keys(data[0]).filter(function(key){return key!="letter";})
data.forEach(function(d){
d.groups=groupNames.map(function(name){return {name:name,value:+d[name]};})
});
x0.domain(data.map(function(d){return d.letter;}));
x1.domain(groupNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0,d3.max(data,function(d){
return d3.max(d.groups,function(d){
return d.value;
});
})]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Letter Fun");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.letter) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.groups; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
Please let me know if you have anything to know about the code.

Set axis range based on data set minimum and maximum in d3.js

I'm trying to set the x and y axes ranges based on the maximum and minimum values for each (i.e., max and min for x and max and min for y). I know that domain controls this range, but I can't figure out how to set it dynamically based on the data. I know a bunch of this code isn't applicable to the issue, but figured I'd post it all. Here goes:
var margin = {t:30, r:20, b:20, l:40 },
w = 600 - margin.l - margin.r,
h = 500 - margin.t - margin.b,
x = d3.scale.linear().range([0, w]),
y = d3.scale.linear().range([h - 60, 0]),
//colors that will reflect geographical regions
color = d3.scale.category10();
var svg = d3.select("#chart").append("svg")
.attr("width", w + margin.l + margin.r)
.attr("height", h + margin.t + margin.b);
fig = svg.append("g").attr("transform", "translate(40, 20)");
// set axes, as well as details on their ticks
var xAxis = d3.svg.axis()
.scale(x)
.ticks(20)
.tickSubdivide(true)
.tickSize(6, 3, 0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.ticks(20)
.tickSubdivide(true)
.tickSize(6, 3, 0)
.orient("left");
var startX = d3.min(x.domain()),
endX = d3.max(x.domain()),
startY = d3.min(y.domain()),
endY = d3.max(y.domain());
var lines = [{x1: startX, x2: endX, y1: (startY + endY)/2, y2: (startY + endY)/2},
{x1: (startX + endX)/2, x2: (startX + endX)/2, y1: startY, y2: endY}]
fig.selectAll(".grid-line")
.data(lines).enter()
.append("line")
.attr("x1", function(d){ return x(d.x1); })
.attr("x2", function(d){ return x(d.x2); })
.attr("y1", function(d){ return y(d.y1); })
.attr("y2", function(d){ return y(d.y2); })
.style("stroke", "#000")
// group that will contain all of the plots
var groups = svg.append("g").attr("transform", "translate(" + margin.l + "," + margin.t + ")");
// array of the regions, used for the legend
var regions = ["Demographic", "Financial", "Content Interests", "Social Media", "Behaviors & Attributes", "Location Distribution","CPG Buying"]
d3.csv("trust-business.csv", function(data) {
// sort data alphabetically by region, so that the colors match with legend
data.sort(function(a, b) { return d3.ascending(a.region, b.region); })
console.log(data)
var x0 = Math.max(-d3.min(data, function(d) { return d.trust; }), d3.max(data, function(d) { return d.trust; }));
x.domain([0,100]);
y.domain([0,100])
// style the circles, set their locations based on data
var circles =
groups.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "circles")
.attr({
cx: function(d) { return x(+d.trust); },
cy: function(d) { return y(+d.business); },
r: 8,
id: function(d) { return d.country; }
})
.style("fill", function(d) { return color(d.region); });
// what to do when we mouse over a bubble
var mouseOn = function() {
var circle = d3.select(this);
// transition to increase size/opacity of bubble
circle.transition()
.duration(800).style("opacity", 1)
.attr("r", 16).ease("elastic");
// append lines to bubbles that will be used to show the precise data points.
// translate their location based on margins
svg.append("g")
.attr("class", "guide")
.append("line")
.attr("x1", circle.attr("cx"))
.attr("x2", circle.attr("cx"))
.attr("y1", +circle.attr("cy") + 26)
.attr("y2", h - margin.t - margin.b)
.attr("transform", "translate(40,20)")
.style("stroke", circle.style("fill"))
.transition().delay(200).duration(400).styleTween("opacity",
function() { return d3.interpolate(0, .5); })
svg.append("g")
.attr("class", "guide")
.append("line")
.attr("x1", +circle.attr("cx") - 16)
.attr("x2", 0)
.attr("y1", circle.attr("cy"))
.attr("y2", circle.attr("cy"))
.attr("transform", "translate(40,30)")
.style("stroke", circle.style("fill"))
.transition().delay(200).duration(400).styleTween("opacity",
function() { return d3.interpolate(0, .5); });
// function to move mouseover item to front of SVG stage, in case
// another bubble overlaps it
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
// skip this functionality for IE9, which doesn't like it
if (!$.browser.msie) {
circle.moveToFront();
}
};
// what happens when we leave a bubble?
var mouseOff = function() {
var circle = d3.select(this);
// go back to original size and opacity
circle.transition()
.duration(800).style("opacity", .5)
.attr("r", 8).ease("elastic");
// fade out guide lines, then remove them
d3.selectAll(".guide").transition().duration(100).styleTween("opacity",
function() { return d3.interpolate(.5, 0); })
.remove()
};
// run the mouseon/out functions
circles.on("mouseover", mouseOn);
circles.on("mouseout", mouseOff);
// tooltips (using jQuery plugin tipsy)
circles.append("title")
.text(function(d) { return d.country; })
$(".circles").tipsy({ gravity: 's', });
// the legend color guide
var legend = svg.selectAll("rect")
.data(regions)
.enter().append("rect")
.attr({
x: function(d, i) { return (40 + i*80); },
y: h,
width: 25,
height: 12
})
.style("fill", function(d) { return color(d); });
// legend labels
svg.selectAll("text")
.data(regions)
.enter().append("text")
.attr({
x: function(d, i) { return (40 + i*80); },
y: h + 24,
})
.text(function(d) { return d; });
// draw axes and axis labels
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.l + "," + (h - 60 + margin.t) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.l + "," + margin.t + ")")
.call(yAxis);
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", w + 50)
.attr("y", h - margin.t - 5)
.text("Total Incidence");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("x", -20)
.attr("y", 45)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Relative Variance");
});
You can use d3.extent() to get the extent of an array and use it in a domain:
x.domain(d3.extent(data, function(d) { return +d.trust; }));
y.domain(d3.extent(data, function(d) { return +d.business; }));

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