Use CSS to move charts close to each other - css

I have two charts. One is bigger than the other. The bigger one consist of two labels called a and b. The small one consist of label c and d. Code for both charts are similar as the following.
var chart = c3.generate({
data: {
columns: [
['Rra', 3880],
['b', 50],
],
type : 'donut'
},
});
var chart = c3.generat
size: {
height: 200,
width: 450
},
data: {
columns: [
['c', 50],
['d', 50],
],
},
});
I was wondering if it is possible to move the small chart between the empty space in the big chart. So they end up looking like two layered charts. I did inspect element on the small chart and tried to use css to tweak the transform="translate(225,83)" to different values. However i noticed when the small chart got close to the bigger chart parts of it disappeared.
I am not expert in css or charting libraries and would love inputs on how to achieve this. Here is my FIDDLE

Although I do agree with YourConscious's answer (using the d3 plugin to create what you want instead of using hacky CSS), here is the CSS method I have come up with:
.chart_container{
position:relative;
}
#chart1{
position:absolute !important;
top:50%;
left:50%;
transform:translate(-50%, -50%);
}
You can see it in action in this Fiddle
The magic is wrapping both charts in a container div, setting it to position:relative; so you can position your smaller chart off of it. Then give your smaller chart the above styles to always position it in the middle of the larger chart.
The !important is there because it seems the plugin is putting an inline style of position:relative

You could probably do it.. with position:absolute; and nesting. But I wouldn't really recommend it and will likely not be that stable (z-index issues, mobile, updating it later on, etc).
I think that library has direct documentation and capabilities to create multiple layers / rings. Why not just do the right way?
Exaggerated example (5 layers / rings)
var dataset = {
apples: [53245, 28479, 19697, 24037, 40245],
oranges: [53245, 28479, 19697, 24037, 40245],
lemons: [53245, 28479, 19697, 24037, 40245],
pears: [53245, 28479, 19697, 24037, 40245],
pineapples: [53245, 28479, 19697, 24037, 40245],
};
var width = 460,
height = 300,
cwidth = 25;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) { return arc.innerRadius(10+cwidth*j).outerRadius(cwidth*(j+1))(d); });
J Fiddler.
Also, this might be a good resource:

Related

Highcharts: How to add another(custom) label/ legend/ something else to the top right of the graph?

In my highcharts, I already have a legend at the bottom left of the graph but want to add a custom status indicator for the entire graph. It's going to be a colored oval with a colored dot and a status on it like in the picture.
Should I be trying to use a custom legend or something else? I don't need to hide/show abiliity of the legend so maybe I'm thinking a label is more appropriate but it seems like those only go in the graph. I want something on the top right of the graph. I'm looking through the API to see what else is available but haven't found one that suites my needs.
edit-
seems like I might have to do "chart.renderer.text" but I'm not sure how to convert and make it work in typescript http://jsfiddle.net/phpdeveloperrahul/HW7Rm/
function (chart) {
var point = chart.series[0].data[8],
text = chart.renderer.text(
'Max',
point.plotX + chart.plotLeft + 10,
point.plotY + chart.plotTop - 10
).attr({
zIndex: 5
}).add(),
box = text.getBBox();
chart.renderer.rect(box.x - 5, box.y - 5, box.width + 10, box.height + 10, 5)
.attr({
fill: '#FFFFEF',
stroke: 'gray',
'stroke-width': 1,
zIndex: 4
})
.add();
});
The best way to add a custom element inside the legend is to use the legend.labelFormatter.
events: {
render() {
let chart = this,
legendAttr = chart.legend.box.getBBox(),
padding = 5;
chart.plus = chart.renderer.text('| +', chart.legend.box.parentGroup.alignAttr.translateX + legendAttr.width + padding, chart.spacingBox.height + padding / 2).attr({
cursor: 'pointer',
}).css({
fontSize: 20
}).on(
"click",
function() {
alert('plus was clicked')
}
).add()
}
}
API References:
https://api.highcharts.com/highcharts/legend.labelFormatter,
Demo: https://jsfiddle.net/BlackLabel/yu7qm9jx/1/
I decided to just get rid of the title and add custom css to the top of it.

Style D3 chart with CSS

Aiming to keep good separation of concerns between content and styling in my code, while acknowledging that creating charts with D3 blurs the lines a little, I am trying to figure out how to fully control the styling of a D3 chart with CSS. For example, consider the following where I create my SVG:
d3.select(container).append('svg')
.attr('id', 'mySvg')
.attr('width', window.innerWidth)
.attr('height', window.innerHeight).
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
Or where I create a scale:
d3.scaleTime().domain(d3.extent(myData)).range([ 0, this.width ]);
In both cases I am referencing style values in the code, i.e. in order to create my SVG chart I have to hardcode style-related values.
Is it possible to do all of this styling etc. with CSS?
const link = gLink.selectAll('path')
.data(links, d => d.target.id);
const linkEnter = link.enter().append('path')
.attr('stroke-width', function (d) {
return 10;
}
Try to provide Stroke width for the lines to increase the thickness
You can use CSS as much as you are able. The demands of a interactive chart sometimes don't make it easy though. The simplest solution, I find is:
d3.select(container).append('svg')
.attr('id', 'mySvg')
.classed('chart', true)
Now the svg element will have the class, .chart.

D3 Invert Selection From Event Listener (this)

I have a visual with many rects, and I have given them the class graph_rects. My goal is when the user clicks on a rect with that class, it will keep that rect the same color but turn all the other graph_rects gray. However my approach did not work and unfortunately it's hard to trouble shoot because there were no errors in the log.
My CSS rule for the graying out:
.graph_rects unselected {
fill:gray;
}
My D3 event listener at rect creation/appending:
.on('click', function(d) {
return d3.selectAll(".graph_rects:not(this)").classed('unselected',true)
})
I'm pretty sure my CSS is ok, my hunch is my d3 logic is not right.
Update
Here are some relevant lines of code that may help piece together why the proposed solutions is not working for me (yet). I am trying to troubleshoot whether or not my matrix of graphs (such as this one) is the culprit by making the selector have redundant ids. So I created a id that has the i and j for the corresponding dimensions as well as i for the index. This way it should be impossible for a rect to have the same id with another rect from a different graph.
.attr('id', function(d,i) {
return String(p.i+p.j)+i
})
Also here is my event listener as per the suggested solution:
.on('click', function(d) {
d3.selectAll('.graph_rects:not(#'+this.id+')').classed('unselected',true)
d3.select(this).classed('unselected',false)
})
Unfortunately I get the following error:
Failed to execute 'querySelectorAll' on 'Document': '.graph_rects:not(#219)' is not a valid selector
What did I not do right I wonder?
First problem, your CSS should be:
.graph_rects.unselected {
fill: gray;
}
But that's not the big issue here. The big issue lies here:
d3.selectAll(".graph_rects:not(this)")
In that line, you're telling D3 to not select any <this></this> element. Of course, such elements don't exist!
There are several ways to achieve what you want, and different coders have their favourite approach (I have mine, which doesn't use the not). However, I'll try to provide a solution keeping that not selector: instead of using not(this), which literally selects something called this, you can select by class or by id (remember, IDs must be unique). For example:
d3.selectAll(".graph_rects:not(#" + this.id + ")")
Here is an example, click the bars:
var svg = d3.select("svg");
var rects = svg.selectAll("planck")
.data(d3.range(10))
.enter()
.append("rect")
.attr("class", "graph_rects")
.attr("id", function(d, i) {
return "rect" + i
})
.attr("x", function(d, i) {
return i * 25
})
.attr("y", 0)
.attr("width", 20)
.attr("height", 150)
.attr("fill", "firebrick")
.on('click', function() {
d3.selectAll(".graph_rects:not(#" + this.id + ")").classed('unselected', true);
d3.select(this).classed("unselected", false)
})
.graph_rects.unselected {
fill: gray;
}
.graph_rects {
cursor: pointer;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Why doesn't this D3 chart redraw when I repeatedly call it's original draw function?

I'm trying to create a dashboard with resizable charts using the D3.js library.
To do this I'm creating the chart SVG elements within DIVs, and those DIVs are made resizable using the jQuery-ui .resizable class.
The DIV's resized event is monitored by another library (css-element-queries) and fires per-pixel, which should call my drawGraph() function and redraw the graph in proportion to it's parent DIV, but this doesn't happen and I'm not sure why. Am I doing something stupid?
See this fiddle: https://jsfiddle.net/Hoppertron/ymf90p6v/17/
By the way, I know that in production I would want to throttle this redraw event - I'm just trying to get it working first.
Here's the code:
<body>
<div id="div1" class="container resizable draggable">
<svg id="chart1" class="chart"></svg>
<script>
//Watches for div resize event (using ResizeSensor.js) and calls drawGraph() to redraw SVG
var element = document.getElementById('div1');
new ResizeSensor(element, function(element) {
var chartWidth = $("#div1").width(),
chartHeight = $("#div1").height();
drawGraph(chartWidth, chartHeight)
});
</script>
</div>
<script>
//Calls drawGraph() on initial page load
var chartWidth = $("#div1").width(),
chartHeight = $("#div1").height();
$(function() {
drawGraph(chartWidth, chartHeight);
});
</script>
</body>
<script>
function drawGraph(width, height) {
var data = [4, 8, 15, 16, 23, 42];
var barHeight = height / data.length;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * barHeight + ")";
});
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) {
return x(d) - 3;
})
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) {
return d;
});
};
</script>
You need to clear the chart before redrawing it. Add this line at the start of your drawGraph function:
d3.selectAll(".chart > *").remove();
Fiddle here:
https://jsfiddle.net/ymf90p6v/19/
If you don't clear it, the data remains attached to the existing elements and no new elements are created.
This might sound stupid, but i think you need to destroy the original graph first before redrawing it.
When you do a d3.selectAll(element), in the background, d3 is going to check the amount of elements vs the number of data elements. Then when you do enter(), d3 looks if there are data items that don't have a matching element yet and draws those elements when you do .append(). In your case, the data doesn't change, only the size of the graph. So nothing will get redrawn.

d3.js Map (<svg>) Auto Fit into Parent Container and Resize with Window

UPDATE: I have posted and accepted a fully working solution in the answers section. Any code in this section is to be used as reference for comparison to your own NON-WORKING code, but is not to be used as the solution.
I'm building a dashboard and using d3.js to add a world map that will plot tweets in real time based on geo location.
The world.json file referenced in the d3.json() line is downloadable HERE (it's called world-countries.json).
The map is on the page as an SVG container and is rendered using d3.
Below are the relevant code slices.
<div id="mapContainer">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="500"></svg>
</div>
#mapContainer svg {
display:block;
margin:0 auto;
}
#mapContainer path {
fill:#DDD;
stroke:#FFF;
}
// generate US plot
function draw() {
var map = d3.select("svg");
var width = $("svg").parent().width();
var height = $("svg").parent().height();
var projection = d3.geo.equirectangular().scale(185).translate([width/2, height/2]);
var path = d3.geo.path().projection(projection);
d3.json('plugins/maps/world.json', function(collection) {
map.selectAll('path').data(collection.features).enter()
.append('path')
.attr('d', path)
.attr("width", width)
.attr("height", height);
});
}
draw();
latestLoop();
$(window).resize(function() {
draw();
});
UPDATE: I have scaled the map to an acceptable size (for my particular browser size), but it still will not scale and center when I change the size of the window. IF, however, I resize the window, then hit refresh, then the map will be centered once the page is reloaded. However, since the scale is static, it is not scaled properly.
COMPLETE SOLUTION:
Here's the solution which will resize the map AFTER the user has released the edge of the window to resize it, and center it in the parent container.
<div id="mapContainer"></div>
function draw(ht) {
$("#mapContainer").html("<svg id='map' xmlns='http://www.w3.org/2000/svg' width='100%' height='" + ht + "'></svg>");
map = d3.select("svg");
var width = $("svg").parent().width();
var height = ht;
// I discovered that the unscaled equirectangular map is 640x360. Thus, we
// should scale our map accordingly. Depending on the width ratio of the new
// container, the scale will be this ratio * 100. You could also use the height
// instead. The aspect ratio of an equirectangular map is 2:1, so that's why
// our height is half of our width.
projection = d3.geo.equirectangular().scale((width/640)*100).translate([width/2, height/2]);
var path = d3.geo.path().projection(projection);
d3.json('plugins/maps/world.json', function(collection) {
map.selectAll('path').data(collection.features).enter()
.append('path')
.attr('d', path)
.attr("width", width)
.attr("height", width/2);
});
}
draw($("#mapContainer").width()/2);
$(window).resize(function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
$(this).trigger('resizeEnd');
}, 500);
});
$(window).bind('resizeEnd', function() {
var height = $("#mapContainer").width()/2;
$("#mapContainer svg").css("height", height);
draw(height);
});
The selection object is an multidimensional array, although in most cases it will probably have only one object in it. That object has a "clientWidth" field that tells you how wide its parent is.
So you can do this:
var selection = d3.select("#chart");
width = selection[0][0].clientWidth;
This should work:
<svg
xmlns="http://www.w3.org/2000/svg"
width="860"
height="500"
viewBox="0 0 860 500"
preserveAspectRatio="xMinYMin meet">
The best choice is to have a combined use of aspect ratio on normal definition of d3 graph's width and height. This has helped me in lot of my graph works.
Step 1 : Dynamically get the height of the div to which the graph has to be appended.
Step 2 : Declare width as aspect ratio with respect to the dynamically caught height.
var graph_div = document.getElementById(graph.divId);
graph.height = graph_div.clientHeight;
graph.width = (960/1200)*graph.height;
In d3 v4, we could do this
const projection = d3.geo.equirectangular().fitSize([width, height], geojson);
const path = d3.geo.path().projection(projection);
fitSize is equivalent to
fitExtent([[0, 0], [width, height]], geojson)
fill free to add padding

Resources