I have been challenge to recreate the image below using HTML/CSS/JS without using any img files. I was thinking that I could make the shapes with and CSS but I know this is probably the most stupid way to do it.
Do any of you have suggestions how to approach this? Maybe a HTML5 canvas? I see the pattern is made of two layers, one with triangles and the top layer with circles. How would I approach this if I wanted to have the triangles and the circles randomly generated?
Thanks
You could definitely achieve this by using the canvas element, but have you thought about using d3.js ?
D3.js is a library which is capable of manipulating documents based on data.
Since all the elements within your picture have an exact position within a cartesian coordinate system you would just have to provide the data and then it would be fairly simple to append the elements to your document.
You could be very precise by using the exact coordinates of each element. Take a look at the snippet and i am sure you will get the idea. The D3 way of selecting an manipulating elements is very similar to what jQuery does, so if you are familiar with jQuery you will like D3js.
Hope this helps you out.
var margin = { top: 50, right: 50, bottom: 50, left: 50},
w = 300 - margin.left - margin.right,
h = 600 - margin.top - margin.bottom,
circleRadii = 15,
triData = [{x: 20, y: 30}, {x: 50, y: 120}, {x: 140, y: 160}],
circleData = [{x: 10, y: 10}, {x: 40, y: 80}, {x: 160, y: 70}];
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tri = svg.selectAll(".point")
.data(triData)
.enter().append("path")
.attr("class", "point")
.attr("stroke", "none")
.attr("fill", "rgba(30,110,160,.5)")
.attr("d", d3.svg.symbol().type("triangle-up").size(1024*2))
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
var circles = svg.selectAll("circle")
.data(circleData)
.enter()
.append("circle");
var circleAttr = circles
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", circleRadii)
.style("fill", "rgba(10,100,0,.5)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Related
I am building a SVG visualisation using d3.js (in PowerBI - so version 3 of d3), and I am struggling aligning my data points and fixed lines with the appropriate y-axis tick marker.
For example, with 8 axis points, the lines are almost right, just slightly above
But when there is only 1 or 2 points, it's way off
I am trying to dynamically calculate the offset as the number of y-axis ticks will depend on the PowerBI filter I have.
My current calculation is that I am taking the height of the svg, dividing it by the number of ticks, and then dividing that by two so it lands in the centre. The y-axis is ordinal.
Relevant code is:
var margin = {top: 20, right: 250, bottom: 50, left: 50},
width = pbi.width - margin.left - margin.right,
height = pbi.height - margin.top - margin.bottom,
legendleft = pbi.width - margin.right;
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], barPad, barOuterPad);
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + legendleft)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
y.domain(Data.map(function(d) { return d.reportingyear; }));
var YearSize = new Set(yearArray).size // Gets the number of ticks on the y-axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0, 6)")
.call(d3.svg.axis().scale(y).orient("left"));
// Chart the rows
var publishedRow = svg.append("g").attr("id", "publishedgroup").selectAll(null)
.data(rowArray)
.enter();
publishedRow.append("line")
.style("stroke", "grey")
.style("stroke-width", 1)
.style("stroke-dasharray", ("2, 2"))
.attr("x1", function(d) { return 0; })
.attr("y1", function(d) { return y(d.entry)+((pbi.height-margin.top-margin.bottom) / (new Set(yearArray).size) / 2); })
.attr("x2", function(d) { return width; })
.attr("y2", function(d) { return y(d.entry)+((pbi.height-margin.top-margin.bottom) / (new Set(yearArray).size) / 2); });
publishedRow.append("circle")
.attr("cx", function(d) {return x(new Date(d.date))} )
.attr("cy", function(d) {return y(d.year)+((pbi.height-margin.top-margin.bottom) / (new Set(yearArray).size) / 2); })
.attr("r", 7)
.style("fill", function(d, i) { return milestoneMap[d.milestone]; })
});
It is the .attr lines that have the code that dynamically calculates the offset.
Is there an easier way to do this? Or can I get some advice as to why my calculation isn't working?
Thank you!
I should have used
.rangePoints rather than .rangeRoundBands
Then a static offset of 6 worked.
Problem solved
I am currently trying to draw a map of the US with the counties-albers-10m.json file found on the topojson repo. I initially got a solid rectangle and, after changing fill to none, I am getting specks here and there. Going through stack, I found that the winding order may be wrong so I incorporated turf.js, but nothing is really changing. Here is the code:
var margin = {top: 0, left: 0, right: 0, bottom: 0},
height = 600 - margin.top - margin.bottom,
width = 1200 - margin.left - margin.right;
var svg = d3.select("#map")
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top +")");
d3.json("counties-albers-10m.json").then(function(data) {
console.log(data);
var projection = d3.geoAlbersUsa();
var path = d3.geoPath()
.projection(projection);
var counties = topojson.feature(data, data.objects.counties).features
console.log(counties)
counties.forEach(function(feature) {
feature.geometry = turf.rewind(feature.geometry, {reverse:true});
})
svg.selectAll(".county")
.data(counties)
.enter().append("path")
.attr("class", "county")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", path);
})
The dreaded black box
I'm facing problem with wrong point of origin when multiple elements are added to an SVG.
JSFiddle: https://jsfiddle.net/sbc6ejeu/2/
I've added an SVG and associated path and couple of circles to it. They seem to correspond to the correct origin. However when I move the slider, I expect the circle of id=movingCicle (as mentioned in the code) to move along the green curve (line). I'm unable to start the initial
position of the circle to the same origin as other svg elements.
Also I observe that the range of the red circle is not same as the other elements or the SVG to which it is appended. For the 2nd and 3rd drop down options, the red cicle moves out of the graph when the slider is increased. I feel I'm missing out on something.
Appretiate any help on this.Thanks!
function initialize() {
// Add circle data
jsonCircles = [{
"xAxis": 50,
"yAxis": 154
}, {
"xAxis": 150,
"yAxis": 154
}];
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
width = 600 - margin.left - margin.right;
height = 270 - margin.top - margin.bottom;
// Set the ranges
x = d3.scale.linear().range([0, width]);
y = d3.scale.linear().range([height, 0]);
// Define the axes
xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(7);
valueLine = d3.svg.line()
.x(function(d, i) {
return x(i);
})
.y(function(d) {
return y(d);
});
// Adds the svg canvas
svg = d3.select("#graph")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svg1")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
}
function updateCirclePosition(i) {
d3.select("#movingCircle").remove();
svg.append("circle")
.attr("cx", +i)
.attr("cy", yValues[i])
.attr("r", 5)
.attr("id", "movingCircle")
.style("fill", "red");
}
function addSvgElements() {
// Add the valueline path.
var path = svg.append("path")
.attr("class", "line")
.attr("id", "lineId")
.attr("d", valueLine(yValues));
}
Inside the function updateCirclePosition, the variable i contains the value of the budget, and yValues[i] is the corresponding revenue.
The corresponding coordinates in the chart can be found using x and y functions, therefore x(i) and y(yValues[i]) should be used to set the correct cx and cy:
svg.append("circle")
.attr("cx", x(i))
.attr("cy", y(yValues[i]))
updated fiddle: https://jsfiddle.net/sbc6ejeu/5/
I'm trying to display single bars in D3. I have a data of the type:
data = [
"value1": 1,
"value2": 2,
"value3": 3,
]
Because the y scale is not the same, I'm trying to display three different bar-charts, each one of them just with a bar. I don't need x-axis.
As you can see in this fiddle: http://jsfiddle.net/GPk7s/, The bar is not showing up, although if you inspect the source code, it has been added. I think it is because I'm not providing a x range, but I don't know how, because I don't really have one.
I just want a bar whose height is related to the range I provide (in the fiddle example this is [10, 30]).
I copy here the code just in case:
var margin = {
top: 50,
right: 0,
bottom: 100,
left: 30
},
width = 200 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var data = [{ "Value": 22.5 } ];
var yRange = d3.scale.linear().range([height, 0]);
yRange.domain([10, 30]);
var svg = d3.select("#chart").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 + ")");
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return yRange(d.Value);
})
.attr("height", function (d) {
return height - yRange(d.Value);
})
.attr("y", function (d) {
return yRange(d.Value) + 3;
})
.attr("dy", ".75em")
.text(function (d) {
return d.Value;
});
Thanks for your help!
There are two problems with what you are doing:
1) Your rectangle doesn't have a width. I added this:
...
.attr("class", "bar")
.attr("width", 10)
.attr("y", function (d) {
...
2) Your data is not an array. D3 expects arrays of data to be provided with the .data() method, and you have data = {Value : 22.5} (effectively). I changed it to this:
...
var data = [{'Value' : 22.5}];
...
Updated fiddle is here.
I'm creating a line chart based on https://github.com/mbostock/d3/blob/master/examples/line/line.html .
var data = [
{x: 0, y: 3},
{x: 1, y: 4},
{x: 2, y: 5}
];
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var line = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
line.interpolate('monotone');
var svg = d3.select("#chart").append("svg")
.datum(data)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("path")
.attr("class", "line")
.attr("d", line);
How do I remove the last element from the path (i.e. delete a line segment)? Similar examples for removing elements involve changing the data array, and re-initializing via exit. In the case of 'circles' this looks something like:
data.shift();
var circles = svg.selectAll(".dot").data(data);
circles.exit().remove();
However this approach doesn't work with path. Any ideas?
Replace data.shift() with data = data.splice(0, data.length - 1)
array.splice(index , howMany[, element1[, ...[, elementN]]])
You could remove the last data element before passing it to d3. Your code would be clearer if you moved the call to .data() further down to where the paths are appended, as it is used only there, i.e. something like
data.shift();
svg.selectAll("path").data(data).enter()
.append("path")
.attr("class", "line")
.attr("d", line);