Apologies for such a basic question. Is it possibly to fill the area between two lines?
For example, I have historical high/low temperature data. I'd like to create a shaded area between these two timeseries.
I've tried using the area feature of a lineChart like this:
return [
{
values: tsData.normal,
key: 'Historical Normal',
classed: 'dashed'
},
{
area: true,
values: tsData.hi,
key: 'Historical Max',
color: '#0000ff'
},
{
area: true,
values: tsData.low,
key: 'Historical Min',
color: '#ffffff',
fillOpacity: 1
}
];
Which results in this image:
Note that the gridlines below the Historical Min line are hidden by the filled areas. This solution is a bit hacky and ugly. Is there a more direct way to do this?
I achieved a better solution by drawing an area using d3.
First I created an array (I called areaData) that merges tsData.hi and tsData.low. For each data point I push for eg:
{x: "The x value", y0:"The y value from tsData.hi", y1:"The y value from tsData.low"}
Then I defined the x and y scale based on the chart's scales:
var x = chart.xScale();
var y = chart.yScale();
Then I added an area definition as
var area = d3.svg.area()
.x(function (d) { return x(d.x); })
.y0(function (d) { return y(d.y0); })
.y1(function (d) { return y(d.y1); });
Next I drew the area using:
d3.select('.nv-linesWrap')
.append("path")
.datum(areaData)
.attr("class", "area")
.attr("d", area)
.style("fill", "#AEC7E8")
.style("opacity", .2);
This achieves a better looking solution. Because nvd3 updates the chart when the window is resized. I wrapped the drawing of the area in a function. So that I can call the function when the window is resized while removing the previously drawn area as follows:
var drawArea = function () {
d3.select(".area").remove();
d3.select('.nv-linesWrap')
.append("path")
.datum(areaData)
.attr("class", "forecastArea")
.attr("d", area)
.style("fill", "#AEC7E8")
.style("opacity", .2);
}
drawArea();
nv.utils.windowResize(resize);
function resize() {
chart.update();
drawArea();
}
I also disabled switching the series on and off using the legend as I wasn't handling that case:
chart.legend.updateState(false);
The result:
Related
I want to draw a pie chart for every point on the map instead of a circle.
The map and the points are displaying well but the pie chart is not showing over the map points. There is no error also. I can see the added pie chart code inside map also.
Below is the code snippet .
var w = 600;
var h = 600;
var bounds = [[78,30], [87, 8]]; // rough extents of India
var proj = d3.geo.mercator()
.scale(800)
.translate([w/2,h/2])
.rotate([(bounds[0][0] + bounds[1][0]) / -2,
(bounds[0][1] + bounds[1][1]) / -2]); // rotate the project to bring India into view.
var path = d3.geo.path().projection(proj);
var map = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
var india = map.append("svg:g")
.attr("id", "india");
var gDataPoints = map.append("g"); // appended second
d3.json("data/states.json", function(json) {
india.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
d3.csv("data/water.csv", function(csv) {
console.log(JSON.stringify(csv))
gDataPoints.selectAll("circle")
.data(csv)
.enter()
.append("circle")
.attr("id", function (d,i) {
return "chart"+i;
})
.attr("cx", function (d) {
return proj([d.lon, d.lat])[0];
})
.attr("cy", function (d) {
return proj([d.lon, d.lat])[1];
})
.attr("r", function (d) {
return 3;
})
.each(function (d,i) {
barchart("chart"+i);
})
.style("fill", "red")
//.style("opacity", 1);
});
function barchart(id){
var data=[15,30,35,20];
var radius=30;
var color=d3.scale.category10()
var svg1=d3.select("#"+id)
.append("svg").attr('width',100).attr('height',100);
var group=svg1.append('g').attr("transform","translate(" + radius + "," + radius + ")");
var arc=d3.svg.arc()
.innerRadius('0')
.outerRadius(radius);
var pie=d3.layout.pie()
.value(function(d){
return d;
});
var arcs=group.selectAll(".arc")
.data(pie(data))
.enter()
.append('g')
.attr('class','arc')
arcs.append('path')
.attr('d',arc)
.attr("fill",function(d,i){
return color(d.data);
//return colors[i]
});
}
water.csv:
lon,lat,quality,complaints
80.06,20.07,4,17
72.822,18.968,2,62
77.216,28.613,5,49
92.79,87.208,4,3
87.208,21.813,1,12
77.589,12.987,2,54
16.320,75.724,4,7
In testing your code I was unable to see the pie charts rendering, at all. But, I believe I still have a solution for you.
You do not need a separate pie chart function to call on each point. I'm sure that there are a diversity of opinions on this, but d3 questions on Stack Overflow often invoke extra functions that lengthen code while under-utilizing d3's strengths and built in functionality.
Why do I feel this way in this case? It is hard to preserve the link between data bound to svg objects and your pie chart function, which is why you have to pass the id of the point to your function. This will be compounded if you want to have pie chart data in your csv itself.
With d3's databinding and selections, you can do everything you need with much simpler code. It took me some time to get the hang of how to do this, but it does make life easier once you get the hang of it.
Note: I apologize, I ported the code you've posted to d3v4, but I've included a link to the d3v3 code below, as well as d3v4, though in the snippets the only apparent change may be from color(i) to color[i]
In this case, rather than calling a function to append pie charts to each circle element with selection.each(), we can append a g element instead and then append elements directly to each g with selections.
Also, to make life easier, if we initially append each g element with a transform, we can use relative measurements to place items in each g, rather than finding out the absolute svg coordinates we would need otherwise.
d3.csv("water.csv", function(error, water) {
// Append one g element for each row in the csv and bind data to it:
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("id", function (d,i) { return "chart"+i; })
.append("g").attr("class","pies");
// Add a circle to it if needed
points.append("circle")
.attr("r", 3)
.style("fill", "red");
// Select each g element we created, and fill it with pie chart:
var pies = points.selectAll(".pies")
.data(pie([0,15,30,35,20]))
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
Now, what if we wanted to show data from the csv for each pie chart, and perhaps add a label. This is now done quite easily. In the csv, if there was a column labelled data, with values separated by a dash, and a column named label, we could easily adjust our code to show this new data:
d3.csv("water.csv", function(error, water) {
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("class","pies")
points.append("text")
.attr("y", -radius - 5)
.text(function(d) { return d.label })
.style('text-anchor','middle');
var pies = points.selectAll(".pies")
.data(function(d) { return pie(d.data.split(['-'])); })
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
The data we want to display is already bound to the initial g that we created for each row in the csv. Now all we have to do is append the elements we want to display and choose what properties of the bound data we want to show.
The result in this case looks like:
I've posted examples in v3 and v4 to show a potential implementation that follows the above approach for the pie charts:
With one static data array for all pie charts as in the example: v4 and v3
And by pulling data from the csv to display: v4 and v3
I try to display circles on a map for a particular dataset. The dataset provides where to center the circle. The dataset contains an identifier (attribute name), a year (attribute year) and a value (attribute value)
I would like to display two things at this level:
The radius of the circle according to the value
The background color (fill) of the circle according to the year and using threshold
A tooltip when the circle is clicked.
Here is the code I use:
var circle = d3.geo.circle();
var color = d3.scale.threshold()
.domain([ 1800, 1850, 1900, 1950, 2000, 2050 ])
.range("#f2f0f7", "#dadaeb", "#bcbddc", "#9e9ac8", "#756bb1", "#54278f");
var elements = layerElement.selectAll('circle')
.data(data)
.enter()
.append('path')
.attr('id', function(d) {
return d.name;
});
elements
.datum(function(d) {
return circle
.origin(origin({d: d}))
.angle(radius({d: d}))();
})
.attr('class', 'point')
.attr('d', path)
.style('fill', function(d) {
return color(d.year);
})
.style('opacity', '0.6');
elements.on('click', function(d) {
(...)
});
I set an identifier to each circle. I see them within the in-memory SVG DOM:
<path id="Hoba"
class="point"
d="M488.55415440889976,286.8579825670507L488.45185788883936,284.8328597859807L488.56757478032006,282.785303550314L488.90003726486356,280.73774731464727L489.445602813917,278.71262453357724L490.1982940971579,276.7321228760774L491.14986447137636,274.8179411327541L492.2898883324236,272.99105147935524L493.6058753403125,271.2714697012249L495.0834072659968,269.6780358961697L496.7062959605052,268.2282080584055L498.45676071568937,266.9378708051187L500.3156230733832,265.82116134127676L502.2625169486039,264.890314569452...L508.2862800372698,302.266499816963L506.206659850514,302.3738076922315L504.15052774957604,302.26649981696306L502.14041114802717,301.9457518786948L500.19833330399786,301.41507805895924L498.3455720287532,300.680292531176L496.6024265625401,299.74944575935126L494.9879951718629,298.63273629550935L493.5199659048794,297.34239904222255L492.2144227974436,295.8925712044583L491.08566965304294,294.2991373994032L490.1460733273343,292.5795556212728L489.4059282342853,290.752665967874L488.8733435584207,288.8384842245506Z"
style="fill: yellow; opacity: 0.75;">
My problems are:
Within the function attr, the first parameter corresponds to the selected shape but there is no identifier in it. Here is the content I get:
{"type":"Polygon","coordinates":[[[5.279833759995999,-21.628058088269754],(...)[5.525725679844768,-22.85403683844725],[5.279833759996005,-21.628058088269807]]]}
So I can't get the corresponding value to apply a background color
I have the same problem when clicking the element
My question is how to get the identifier for the selected element (circle shape in my case).
Thanks very much for your help!
Thierry
Your call to datum is generating a path from each data item but throwing away the rest of it, including the properties. If you want to keep the properties, nest the object that it generates:
.datum(function(d) {
return {
circle: circle
.origin(origin({d: d}))
.angle(radius({d: d}))(),
d: d
};
})
And then the rest of your calls will follow: like
.attr('d', function(d) { return path(d.circle); })
And
.style('fill', function(d) {
return color(d.d.year);
})
I'm trying to highlight some points in a time series modeled using a nvd3.js eventLineChart - more precisely I have a json object with time-stamps and for each time-stamp I would like to add a vertical line at this particular date/time. The highlighted points may not exist in the time-series data source and are global over all groups of the time-series data (like ticks).
Any ideas on how this could be achieved? - I tried adding a standard line to my plot (fixed y1 and y2 and x according to the timestamp of the events i want to highlight) but wasn't able to have the timestamps scaled to the same range as the original time series.
Here are some parts of the model I started to build for that purpose based on nv.models.lineChart. - (just an excerpt of the model as most of the code is just a copy from the lineChart model):
nv.models.eventLineChart = function() {
// adds vertical highlights to line chart
"use strict";
var chartEvents = {}
function chart(selection) {
selection.each(function(data) {
// Setup Scales
x = lines.xScale();
y = lines.yScale();
// Setup containers and skeleton of chart
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
var g = wrap.select('g');
gEnter.append('g').attr('class', 'nv-eventLinesWrap');
//------------------------------------------------------------
// Main Chart Component(s)
var eventWrap = wrap
.select('.nv-eventLinesWrap')
.selectAll('.nv-eventLines')
.data(function(d) {return d });
eventWrap
.enter()
.append('g')
.attr('class', 'nv-eventLines');
// chartEvents json ==> [{decsription: "test,"timestamp: 1375031820000}]
var eventLine = eventWrap
.selectAll('.nv-eventLine')
.data(chartEvents, function(d){return (d.timestamp)});
var eventLineEnter = eventLine.enter()
.append('line').attr('class', 'nv-eventLine')
.style('stroke-opacity', 0);
// #todo set ymin and ymax
eventLine
.attr('x1', function(d){ return x(d.timestamp);})
.attr('x2', function(d){ return x(d.timestamp);})
.attr('y1', 300)
.attr('y2', 800)
.style('stroke', function(d,i,j) { return color(d,j) })
.style('stroke-opacity', function(d,i) {
return 1;
});
});
return chart;
}
chart.setChartEvents = function(_) {
if (!arguments.length) return chartEvents;
chartEvents = _;
return chart;
};
return chart;}
This model is called by using:
nv.addGraph(function() {
var nv3dChart = nv.models.eventLineChart().useInteractiveGuideline(true).setChartEvents(json.chartEvents);
// json.chartEvents >> [{decsription: "EventDescription,"timestamp: 1375031820000}]
nv3dChart.xAxis
.showMaxMin(false);
.tickFormat(function(d) { return d3.time.format("%Y-%m-%d")(new Date(d)) });
nv3dChart.yAxis
.axisLabel(widgetConfig.action.data.kpiName)
.tickFormat(d3.format(',.f'));
var ndg = d3.select(renderToElementId+' svg');
ndg.datum([{
values: json.data,
key: widgetConfig.action.data.tagName
}])
.transition().duration(500);
nv.utils.windowResize(nv3dChart.update);
return nv3dChart;})
Which produces currently this svg output (events that should be displayed by vertical lines only)
<g class="nv-eventLines">
<line class="nv-eventLine" x1="1375031820000" x2="1375031820000" y1="300" y2="800" style="stroke: #1f77b4;"></line>
</g>
.. as described I haven't yet figured out a way to implement the scaling of the events x values according to the scale of the line chart
Would greatly appreciate any help regarding this problem
I now manually created all scales for x and y and added them to the nvd3 elements. I'm not particularly happy with that solution as it prevents me from creating a more modular feature for multiple nvd3 charts but it is a starting point.
Here is an outline of my current solution:
nv.models.eventLineChart = function() {
// initialize scales
var y = d3.scale.linear(),
x = d3.scale.linear();
// set scales of lines
lines = nv.models.line()
.yScale(y)
function chart(selection) {
//#todo support for multiple series
// set domain and range for scales
x
.domain(d3.extent(data[0].values, function(d){return d.x}))
.range([0,availableWidth]);
y
.domain(d3.extent(data[0].values, function(d){return d.y}))
.range([0,availableHeight]);
// add container for vertical lines
gEnter.append('g').attr('class', 'nv-eventLinesWrap');
// setup container
var eventWrap = wrap.select('.nv-eventLinesWrap').selectAll('.nv-eventLines')
.data(function(d) {return d });
eventWrap.enter().append('g').attr('class', 'nv-eventLines');
eventWrap.select('.nv-eventLinesWrap').attr('transform', 'translate(0,' + (-margin.top) +')');
var eventLine = eventWrap.selectAll('.nv-eventLine').data(chartEvents, function(d){return (d.timestamp)});
var eventLineEnter = eventLine.enter()
.append('line').attr('class', 'nv-eventLine')
// configure and style vertical lines
//#todo: set color for lines
eventLine
.attr('x1', function(d){ return x(d.timestamp)})
.attr('x2', function(d){ return x(d.timestamp)})
.attr('y1', y.range()[0])
.attr('y2', y.range()[1])
.style('stroke', function(d,i,j) { return "#000"; })
.style('stroke-width',1)
// #todo add vertical lines via transitions, add vLine content to toolbox
}}
Thank you, Lars, for your contributions .. they really helped a lot to understand certain parts in more detail.
If anyone has come up with a better idea to solve this problem I would be very grateful if you could post these suggestions here!
I'm working on a little project to print scaled down archery targets for practice at shorter range indoors. For example, the target face shown here:
...is 16cm in diameter at full size for 20 yards, but needs to be printed at half that size for practice at 10 yards. There are many different styles of target faces, and I'd like to support as many as possible. I have D3 working to pull the relevant sizes from a CSV file.
I'm specifying the sizes of the elements of the SVG in centimeters whenever possible. Is it possible to use D3 to generate a PDF that can be printed at a specific size?
Here's my code that loads the CSV and generates the SVG.
var dataset;
var w = "16cm";
var h = "16cm";
d3.csv("./data/nfaa5spot.csv", function(error, data) {
if (error) {
console.log(error);
} else {
console.log(data);
// Hand data off to global var for processing
dataset = data;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "-400,-400,1700,1700");
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr({
cx: function(d) { return d["cx"]; },
cy: function(d) { return d["cy"]; },
r: function(d) { return d["r"]; }
})
.style("fill", function(d) { return d["fill"]; })
.style("stroke", function(d) { return d["stroke"]; })
.style("stroke-width", function(d) { return d["stroke-width"]; });
}
});
Here are the contents of the relevant CSV file:
r,cx,cy,fill,stroke,stroke-width
16cm,8cm,8cm,#164687,#164687,0
12cm,8cm,8cm,#164687,white,3
8cm,8cm,8cm,white,white,0
4cm,8cm,8cm,white,#164687,3
Resizing is just first part. I'm also working on drawing additional lines on the target face to compensate for arrow shaft diameter at shorter range. Fun project, and a good intro to working with D3.
So I am playing around with the d3.js Sankey diagram.
In this example (pictured above) color is defined using
var color = d3.scale.category20();
For each node there is a rectangle, and that rectangle is filled by altering the style:
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
I'm looking for suggestions on using custom colors. If I wanted to use only say 6 colors, but have the node rectangle colors chosen based on a value in the .json file.
For example, lets say I wanted to show a snakey chart of teams in the NFL. The colours each represent which division the teams play in. So if they move to a different division, the color changes. And the nodes are created for every season. Something along those lines.
So is it possible to run the
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
with the color based on a value in the json file? I am thinking just an if statement, but is there an easier way? Could I just include the hex color code in the json?
Alternatively, you could map the colors to the division explicitly with a d3 ordinal scale as mentioned in the documentation. See Colorbrewer at the bottom.
var color = d3.scale.ordinal()
.domain(["foo", "bar", "baz"])
.range(["#fff","#000","#333"]);
and then
.attr("fill", function(d) { return color(d.division); });
Sounds like you want to include the colour in the JSON in this case. You can include it in any way that the browser recognises, e.g. as a name ("white") or hex ("#fff"). See the SVG spec for a full list of supported colour specifications.
Replace const color = d3.scaleOrdinal(d3.schemeCategory20); with:
const color = d3.scaleOrdinal()
.domain(["Crude oil","Natural gas",...])
.range(["#FD3E35","#FFCB06",...]);
And stay with:
.style('fill', (d,i) => {
d.color = color(d.name.replace(/ .*/, ''));
return d.color;})