How can I get a time axis onto an object, using D3? - d3.js

I'm very new to d3 and trying to learn by building a visualization.
My goal right now is to make a circle and color the circle based on some temporal data. I've made the circle, and want to add a timescale to it. The circle I have created fine using d3.arc() on an svg element. I have also created a time scale (seen below). My question is, how can I "attach" this time scale to the circle? I want to be able to say that at xyz point in time, my data holds this value, so now color the circle based on a color scale.
Or...am I going about this wrong?
var time = d3.scale.ordinal()
.domain(d3.extent(data, function(d) {
return d.date;
}))

I think you may need to use a quantitative scale instead of ordinal.
https://github.com/mbostock/d3/wiki/Ordinal-Scales says -
Ordinal scales have a discrete domain, such as a set of names or categories
and in your code, you use the "extent" of the date property, which only gives you 2 values - the earliest and most recent date in your data. That is a discrete domain, but a very limited one, and wouldn't represent your data very well. The scale will only output at most 2 values.
var now = Date.now();
var then = now - 1000;
var colors = d3.scale.ordinal()
.domain([then, now])
.range(['#ff0000','#0000ff']);
colors(then); // red
colors(now); // blue
colors(now - 500); // red... expecting violet
change 'ordinal' to 'linear' and leave the rest as is.
var now = Date.now();
var then = now - 1000;
var colors = d3.scale.linear()
.domain([then, now])
.range(['#ff0000','#0000ff']);
colors(then); // red
colors(now); // blue
colors(now - 500); // violet
The tricky part (at least for me) was remembering that the output of d3.scale.linear() (the 'colors' variable above) is a function. It can be called just like any other function.
var fakeData = d3.range(then, now, 10);
var svg = d3.select('body')
.append('svg')
.attr({ height: 500, width: 500 });
var circle = svg.append('circle')
.attr({ r: 100, cx: 250, cy: 250 });
function changeTime(time){
circle.attr('fill', colors(time));
}

Related

How to use d3.scaleLinear to find the radius of a map in d3

I have a chart as shown below
where the radius of the pie chart on the chart is am trying to do it as shown below
<PieChartMarkers
totalSites={totalSites}
key={i}
keymarket={name}
pieData={pieData}
x={projection(coordinates)[0]}
y={projection(coordinates)[1]}
**radius={this.getMarketRadius(totalCase)}**
mouseOverHandler={this.showTooltip}
mouseOutHandler={this.hideTooltip}
/>
getMarketRadius = (totalCase) => {
let radius = null;
// let data=d3.scaleLinear([10,130]).range([0,960]);
let callback= d3.scaleLinear()
.range([0, totalCase])
.domain([10, 130])
radius = totalCase / 650 + 10;
console.log(radius)
return radius;
};
currently i am getting the radius radius = totalCase / 650 + 10; which is working fine but suggestion is to use d3.scaleLinear to get the radius of the on chart when trying to use i am getting the value as 1313316 using the below code snippet
//totalCase is the variable value coming from API
let callback= d3.scaleLinear()
.range([0, totalCase])
.domain([10, 130]
please help me understand how to get the radius using d3.scaleLinear to draw the pie chart on the map
First of all, you should not use a linear scale to get these radii: we encode the data as the area of the circles, not as their radii. That said, you should use d3.scaleSqrt. Also, set the 0 range for the 0 domain, because there should be no circle if there are no cases.
So, your function would be something like this:
getMarketRadius = totalCase => {
return d3.scaleLinear().range([0, totalCase]).domain([0, 130])(totalCase);
};
A better option is just setting the scale outside the function, and using it directly for setting the radius.
myScale = d3.scaleLinear()
.range([0, totalCase])
.domain([0, 130]);
radius={this.myScale(totalCase)}

plotting tick text above the axis

I am using d3 v4 for ploting the graph. And currently the tick text on the x-axis is coming below the axis. and I want that text on above the axis.
//Set the Xaxis scale Range
let x = scaleLinear().rangeRound([0, width]);
let x_axis = axisBottom(x);
x.domain(extent(graphData, function (d) {
return d.weeks;
}));
g.append("g").attr("transform", "translate(0," + height + ")").call(axisBottom(x).ticks(5)).attr("transform", "translate(0, 120)");
so can you help me how to put the tick text above the x-axis.
If you want the ticks on top of the axis, you should use axisTop, instead of axisBottom.
The names are pretty easy to understand and the API is very clear:
d3.axisTop(scale): In this orientation, ticks are drawn above the horizontal domain path.
d3.axisBottom(scale): In this orientation, ticks are drawn below
the horizontal domain path. (emphases mine)
Here is a demo, the first axis uses axisTop, and the second one, below, uses axisBottom:
var svg = d3.select("svg");
var x = d3.scaleLinear().range([20, 280]);
var xAxisTop = d3.axisTop(x)(svg.append("g").attr("transform", "translate(0,50)"))
var xAxisBottom = d3.axisBottom(x)(svg.append("g").attr("transform", "translate(0,100)"))
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

How can I get the D3.js axis ticks and positions as an array?

I usually place my axis ticks on the svg using this:
d3.svg.axis().scale(xScale(width)).ticks(4)
Is it possible to get these tick values and their svg coordinates so I can use a custom axis outside the svg using d3.svg.axis() ?
Yes, xScale.ticks(4) should give you the actual tick points as values, and you can pipe those back through your xScale to the the X position. You can also just pull the tick points back from the generated elements after you apply the axis to an actual element:
var svg = d3.select("svg");
var scale = d3.scale.linear()
.range([20, 280])
.domain([0, 100])
var axis = d3.svg.axis().scale(scale).orient("bottom").ticks(9);
// grab the "scale" used by the axis, call .ticks()
// passing the value we have for .ticks()
console.log("all the points", axis.scale().ticks(axis.ticks()[0]));
// note, we actually select 11 points not 9, "closest guess"
// paint the axis and then find its ticks
svg.call(axis).selectAll(".tick").each(function(data) {
var tick = d3.select(this);
// pull the transform data out of the tick
var transform = d3.transform(tick.attr("transform")).translate;
// passed in "data" is the value of the tick, transform[0] holds the X value
console.log("each tick", data, transform);
});
jsbin
In d3 v4 I ended up just parsing the rendered x values from the tick nodes
function parseX(transformText) {
let m = transformText.match(/translate\(([0-9\.]*)/);
let x = m[1];
if (x) {
return parseFloat(x);
}
}

D3: Create a continuous color scale with many strings/inputs for the range and dynamically changing values of the domain

I am trying to create a linear color scale for a heatmap. I want to color scale to go through a large set of specific colors, where the first color corresponds to the min of the data and the last color should be given to the max of the data.
I know that I can do this by also giving the domain 17 values in between the min and max, but I do not know how to do this dynamically if the user is able to change the dataset (and thus change the coloring of the heatmap)
In essence I would like to following, but I know it does not work
var colorScale = d3.scale.linear()
.range(["#6363FF", "#6373FF", "#63A3FF", "#63E3FF", "#63FFFB", "#63FFCB",
"#63FF9B", "#63FF6B", "#7BFF63", "#BBFF63", "#DBFF63", "#FBFF63",
"#FFD363", "#FFB363", "#FF8363", "#FF7363", "#FF6364"])
.domain([d3.min(dataset, function(d) {return d;}),
d3.max(dataset, function(d) {return d;})]);
Can anybody please tell me what I need to put into 'domain' to make it work?
EDIT:
I did find something that does what I want. Using R I calculated 256 colors in between the 17 from above with the designer.colors functions and put this into the range. This does give the feeling of a continous color scale
var colorScale = d3.scale.linear()
.range(["#6363FF", "#6364FF", "#6364FF", "#6365FF",
"... several other lines with color codes ..."
"#FF6764", "#FF6564", "#FF6464", "#FF6364"])
.domain(d3.range(1,257));
var quantize = d3.scale.quantile()
.range(d3.range(1,257))
.domain([d3.min(dataset, function(d) {return d;}),
d3.max(dataset, function(d) {return d;})]);
Now I can use the color in this fashion
colorScale(quantize(dataset))
But I'm wondering if this can also be done in less lines of code?
You want to split the problem up. First define a scale for your heatmap that maps 0-1 to your colours. Then define a second (dynamic) scale that maps your dataset to 0-1. You can then combine the scales to paint your shapes.
var colours = ["#6363FF", "#6373FF", "#63A3FF", "#63E3FF", "#63FFFB", "#63FFCB",
"#63FF9B", "#63FF6B", "#7BFF63", "#BBFF63", "#DBFF63", "#FBFF63",
"#FFD363", "#FFB363", "#FF8363", "#FF7363", "#FF6364"];
var heatmapColour = d3.scale.linear()
.domain(d3.range(0, 1, 1.0 / (colours.length - 1)))
.range(colours);
// dynamic bit...
var c = d3.scale.linear().domain(d3.extent(dataset)).range([0,1]);
// use the heatmap to fill in a canvas or whatever you want to do...
canvas.append("svg:rect")
.data(dataset)
.enter()
// snip...
.style("fill", function(d) {
return heatmapColour(c(d));
Plus you can use the d3.extent function to get the min and max of the dataset in one go.
Use a Quantitative Scale plus Color Brewer
// pick any number [3-9]
var numColors = 9;
var heatmapColour = d3.scale.quantize()
.domain(d3.extent(dataset))
.range(colorbrewer.Reds[numColors]);
// use the heatmap to fill in a canvas or whatever you want to do...
canvas.append("svg:rect")
.data(dataset)
.enter()
// snip...
.style("fill", function(d) {return heatmapColour(d);})
Use threshold scales. Here is a quick example:
coffee> d3 = require 'd3'
coffee> color = d3.scale.threshold().domain([5,30,100]).range(["red","orange","green"]);
coffee> color 6
'orange'
coffee> color 3
'red'
coffee> color 33
'green'

d3 pie chart transition with attrtween

i'm trying to somehow sweep in a half-donut-chart, meaning starting with a blank screen the chart starts drawing at -90 degree (or 270) and performs a halfcircle until reaching 90 degree. the code looks like:
var width = 800;
var height = 400;
var radius = 300;
var grad=Math.PI/180;
var data = [30, 14, 4, 4, 5];
var color = d3.scale.category20();
var svg = d3.select("body").append("svg").attr("width", width).attr("height",
`height).append("g").attr("transform", "translate(" + radius*1.5 + "," + radius*1.5 +
")");
var arc = d3.svg.arc().innerRadius(radius - 100).outerRadius(radius - 20);
var pie = d3.layout.pie().sort(null);
svg.selectAll("path").data(pie(data)).enter().append("path").attr("d",
arc).attr("fill",
function(d, i) { return color(i); }).transition().duration(500).attrTween("d", sweep);
function sweep(a) {
var i = d3.interpolate({startAngle: -90*grad, endAngle: -90*grad},{startAngle: -90*grad, endAngle: 90*grad});
return function(t) {
return arc(i(t));
};
}
looking at several examples i managed to get the animation, however, i fail at binding (or converting) the data to the arc. my feeling is that there is only one path drawn and then it stops.
if i change the interpolation to start/end -90/90 and a, i get different colors but not all of them. adding the start/end-angle to the pie-var gives me a transition where a one-colored-arc is shown at the beginning and then the other parts slide in (which would be correct if there was no arc at the beginning - the proportions also seem a bit wrong). setting the initial color to white does not help because then everything stays white.
i'm afraid i'm missing an obvious point, but so far i'm stuck, maybe someone can point me in the right direction.
after quite some variations and tests it somehow started to work, using these to lines of code:
var pie = d3.layout.pie().sort(null).startAngle(-90*grad).endAngle(90*grad);
var i = d3.interpolate({startAngle: -90*grad, endAngle: -90*grad},a);
one final "problem" was that the height of the svg was too small and so some segments got cut off, so changing it to
var height = 800;
ended my search. thanks for any considerations.
A small typo on the
var svg = d3.select("body").append("svg").attr("width", width).attr("height", `height)
should be:
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height)

Resources