Related
I've recently begun learning D3.js and I am struggling to create a transition in a scatter plot with the following data:
var data = [
{"year" : "2004", "x":100, "y":300, "size": 2, "type": "A"},
{"year" : "2005", "x":200, "y":200, "size": 2, "type": "A"},
{"year" : "2006", "x":300, "y":100, "size": 2, "type": "A"},
{"year" : "2004", "x":150, "y":250, "size": 2.382450, "type": "B"},
{"year" : "2005", "x":150, "y":250, "size": 3.078548, "type": "B"},
{"year" : "2006", "x":150, "y":250, "size": 4.265410, "type": "B"}];
Where in the scatter plot there are 2 points (type A&B) and they change location (x&y) and size by year. I've created a fiddle where I try to nest the data and plot the points, but making the next step of using transition() function is confusing. More specifically, I am still declaring the whole data, but to make transitions work I only need part of the data.
The key to understand what you want lies here:
There are 2 points and they change location (x&y) and size by year
Therefore, this is clearly a XY problem. Your problem is not "how to transition with nested data". Your problem is "how to transition by year".
My proposed solution involves, first of all, dropping that nested array. You don't need that.
Instead, get all the years in the data...
var years = [...new Set(data.map(function(d) {
return d.year
}))];
..., filter the data by year...
var dataStart = data.filter(function(d) {
return d.year === years[0]
});
... and loop trough the years. Here, I'm using d3.interval():
var counter = 1;
var timer = d3.interval(transition, 1500);
function transition() {
var newData = data.filter(function(d) {
return d.year === years[counter]
});
svg.selectAll("circle").data(newData)
.transition()
.duration(1000)
.attr("cx", function(d) {
console.log(d)
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.size * 10;
});
counter += 1;
if (counter === 3) {
timer.stop()
}
}
Here is the demo:
var data = [{
"year": "2004",
"x": 100,
"y": 100,
"size": 2,
"type": "A"
}, {
"year": "2005",
"x": 200,
"y": 180,
"size": 2,
"type": "A"
}, {
"year": "2006",
"x": 300,
"y": 50,
"size": 2,
"type": "A"
}, {
"year": "2004",
"x": 150,
"y": 150,
"size": 2.382450,
"type": "B"
}, {
"year": "2005",
"x": 150,
"y": 50,
"size": 3.078548,
"type": "B"
}, {
"year": "2006",
"x": 150,
"y": 100,
"size": 4.265410,
"type": "B"
}];
var width = 400,
height = 200;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var years = [...new Set(data.map(function(d) {
return d.year
}))];
var dataStart = data.filter(function(d) {
return d.year === years[0]
});
var cell = svg.selectAll("circle")
.data(dataStart);
cell.enter()
.append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.size * 10;
});
var counter = 1;
var timer = d3.interval(transition, 1500);
function transition() {
var newData = data.filter(function(d) {
return d.year === years[counter]
});
svg.selectAll("circle").data(newData)
.transition()
.duration(1000)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.size * 10;
});
counter += 1;
if (counter === 3) {
timer.stop()
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I'm using Donut3D.js library (which is based on D3.js chart library) (http://bl.ocks.org/NPashaP/9994181).
I've created a javascript event listener to listen for changes in a select option Html combo box control. Users select an option from the combo box and based on the selected option, the data for the 3d pie chart is fetched from a SQL Server database and the chart is re-drawn. However, my chart is not rendering, although when I'm in Firebug debug mode, it is re-drawn. I'm using Firefox and Firebug for debugging. My Web app is using an MVC pattern and C# programming language. Following are the code snippets:
In Partial View1:
<select id=hucDdl></select>
In Partial View2:
<script>
$(document).ready(function(){
//Event listener when selection changes
$("#hucDdl").change(function () {
//Get huc value
var huc;
if($("#hucDdl").val() != null){
huc = $("#hucDdl").val();
});
//Call function
ChangeData();
});
function ChangeData(){
<blockquote>var huc = $("#hucDdl").val();
var arr = [];
var lulcData = null;
//get data from SQL Server
$.ajax({<br/></blockquote>
url: "/Home/GetBaseLulcJson/",
type: "GET",
data: {huccode: huc},
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function(result){
arr = result;
},
error: function(data){
}
})
lulcData = [
{ "label": "Cropland", "value": arr[0], "color": "#ffb3ba" },
{ "label": "Forest", "value": arr[1], "color": "#ffdfba" },
{ "label": "Pasture", "value": arr[2], "color": "#ffffba" },
{ "label": "Rangeland", "value": arr[3], "color": "#baffc9" },
{ "label": "Urban", "value": arr[4], "color": "#bae1ff" }
];
//Draw the 3d pie chart
Donut3D.draw("blulcpie", getData(), 90, 50, 90, 40, 30, 0);
function getData(){
<blockquote>return lulcData.map(function (d) {
return { label: d.label, value: +d.value, color: d.color };
});
}
});
</script>
The ChangeData() function is not firing on selection change.
Does anyone know how to get the chart to re-draw when data changes?
The data fetched from SQL Server are correct.
I'm just not sure what's causing the chart not to re-draw.
Solved this issue. I revised my codes as follows:
Index.cshmtl (main view):
<!--d3.js references-->
<script src="http://d3js.org/d3.v3.min.js" type="text/javascript"></script>
<script src="~/myscripts/donut3d.js" type="text/javascript"></script>
<div id="bLulc">
<label>Major Landuse</label>
#Html.Partial("~/Views/Shared/_BaseLanduse.cshtml")
</div>
Partial View1 (holds the select HTML control):
<select id = "hucDdl"></select>
Partial View2 (contains the 3d pie chart) "_BaseLanduse.cshtml":
<script type="text/javascript">
$(document).ready(function () {
//Set default array values for initial display on page load
var def_arr = [0.2, 80.3, 1.9, 16.9, 0.7];
var defData = [
{ "label": "Cropland", "value": def_arr[0], "color": "#ffb3ba" },
{ "label": "Forest", "value": def_arr[1], "color": "#ffdfba" },
{ "label": "Pasture", "value": def_arr[2], "color": "#ffffba" },
{ "label": "Rangeland", "value": def_arr[3], "color": "#baffc9" },
{ "label": "Urban", "value": def_arr[4], "color": "#bae1ff" }
];
//Define chart parameters
var margin = { top: 0, right: 20, bottom: 0, left: 20 }
var width = 180,
height = 230 - margin.bottom;
var svg = d3.select("#bLulc")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g")
.data([defData])
.attr("id", "blulcpie");
//Draw the chart
Donut3D.draw("blulcpie", defData, 90, 50, 90, 40, 30, 0);
//Define legend square size
var legendSpace = 4;
var rectSize = 8;
//Add legend
defData.forEach(function (d, i) {
svg.append("rect")
.attr("transform", "translate(0," + i * (rectSize + legendSpace) + ")")
.attr("class", "rect")
.attr("width", rectSize)
.attr("height", rectSize)
.attr("x", 50) //x-axis of rect
.attr("y", 130) //y-axis of rect
.style("stroke", "#000000")
.style("stroke-width", .25)
.style("fill", defData[i].color);
svg.append("text")
.attr("class", "legend")
.attr("x", rectSize + legendSpace)
.attr("y", (i * legendSpace) + (i * rectSize))
.attr("dx", 50) //x-axis of text
.attr("dy", 138) //y-axis of text
.style("font-size", "10px")
.text(defData[i].label);
});
//Event listener when huccode changes
$("#hucDdl").bind("mousedown mouseup", function () {
debugger;
//Get data from SQL Server via Controller
$.ajax({
url: "/Home/GetBaseLulcJson/",
type: "GET",
data: { huccode: $("#hucDdl").val() },
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (result) {
arr = result;
//alert(arr);
},
error: function (data) {
//alert(data);
}
})
var currData = [
{ label: "Cropland", value: arr[0], color: "#ffb3ba" },
{ label: "Forest", value: arr[1], color: "#ffdfba" },
{ label: "Pasture", value: arr[2], color: "#ffffba" },
{ label: "Rangeland", value: arr[3], color: "#baffc9" },
{ label: "Urban", value: arr[4], color: "#bae1ff" }
];
Donut3D.transition("blulcpie", currData, 90, 40, 30, 0);
});
});
Controller:
[HttpGet]
public JsonResult GetBaseLulcJson(string huccode)
{
//Returns single row, selected columns
var lulcBase = (from f in db.FractionsLulcs
where f.HUCCODE == huccode
select new
{
f.Cropland,
f.Forest,
f.Pasture,
f.Range,
f.Urban
}).SingleOrDefault();
//Convert to percentage
double?[] lulc = new double?[5];
lulc[0] = Math.Round(Convert.ToDouble(lulcBase.Cropland) * 100, 1);
lulc[1] = Math.Round(Convert.ToDouble(lulcBase.Forest) * 100, 1);
lulc[2] = Math.Round(Convert.ToDouble(lulcBase.Pasture) * 100, 1);
lulc[3] = Math.Round(Convert.ToDouble(lulcBase.Range) * 100, 1);
lulc[4] = Math.Round(Convert.ToDouble(lulcBase.Urban) * 100, 1);
return Json(lulc, JsonRequestBehavior.AllowGet);
}
I would like to ask that is it possible to plot grouped box plots by D3.js like this example from Plotly? So far, I didn't see any example on the internet yet.
Thanks in advance.
4th UPDATE: [SOLVED on 3/3/2016]
Boxplot grouped by dates at x axis and colored according to grouping on respective dates.
Solution is as below:
var state = svg1.selectAll(".state2")
.data(dataset2)
.enter().append("g")
.attr("class", "state")
.attr("transform", function(d) { return "translate(" + x0(d.Date) + ",0)"; } );
state.selectAll(".box")
.data(function(d) { return d.Data; })
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x_1(d.group) + ",0)"; } )
.call(boxplot);
3rd UPDATE:
Done modifying data format.
Issue: Boxplot for both dates are the same.
2nd UPDATE:
Managed to group the Box plots but facing difficulties in formatting data into groups. plunker version 9
UPDATE:
I am trying to change the first chart (Grouped Bar chart) to Grouped Boxplot. Hit error on line 140 at index.html. plunker version 1.
state.selectAll(".box")
.data(function(d) { return d.data; })
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x_1(d) + "," + margin.top + ")"; } )
.call(boxplot.width(x0.rangeBand())); //This line
Yes, it's doable, here is an example : https://bl.ocks.org/mbostock/4061502.
The main idea here is just to use svg rect path and circle to achieve the box and whisker style.
If you are will to use nvd3, there is a built-in boxPlot graph (example)
Try this
<!DOCTYPE html>
<html>
<head>
<style>
#chartdiv {
width: 100%;
height: 500px;
}
</style>
</head>
<body>
<div id="chartdiv"></div>
<script src="https://cdn.amcharts.com/lib/4/core.js"></script>
<script src="https://cdn.amcharts.com/lib/4/charts.js"></script>
<script src="https://cdn.amcharts.com/lib/4/themes/animated.js"></script>
<!-- Chart code -->
<script>
am4core.ready(function() {
// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end
var chart = am4core.create("chartdiv", am4charts.XYChart);
chart.paddingRight = 20;
chart.dateFormatter.inputDateFormat = "yyyy-MM-dd";
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.minGridDistance = 40;
dateAxis.renderer.grid.template.location = 0;
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.tooltip.disabled = true;
var series = chart.series.push(new am4charts.CandlestickSeries());
series.dataFields.dateX = "date";
series.dataFields.valueY = "close";
series.dataFields.openValueY = "open";
series.dataFields.lowValueY = "low";
series.dataFields.highValueY = "high";
series.simplifiedProcessing = true;
series.tooltipText = "Open:${openValueY.value}\nLow:${lowValueY.value}\nHigh:${highValueY.value}\nClose:${valueY.value}\nMediana:{mediana}";
series.riseFromOpenState = undefined;
series.dropFromOpenState = undefined;
chart.cursor = new am4charts.XYCursor();
var medianaSeries = chart.series.push(new am4charts.StepLineSeries());
medianaSeries.noRisers = true;
medianaSeries.startLocation = 0.1;
medianaSeries.endLocation = 0.9;
medianaSeries.dataFields.valueY = "mediana";
medianaSeries.dataFields.dateX = "date";
medianaSeries.strokeWidth = 2;
medianaSeries.stroke = am4core.color("#fff");
var topSeries = chart.series.push(new am4charts.StepLineSeries());
topSeries.noRisers = true;
topSeries.startLocation = 0.2;
topSeries.endLocation = 0.8;
topSeries.dataFields.valueY = "high";
topSeries.dataFields.dateX = "date";
topSeries.stroke = chart.colors.getIndex(0);
topSeries.strokeWidth = 2;
var bottomSeries = chart.series.push(new am4charts.StepLineSeries());
bottomSeries.noRisers = true;
bottomSeries.startLocation = 0.2;
bottomSeries.endLocation = 0.8;
bottomSeries.dataFields.valueY = "low";
bottomSeries.dataFields.dateX = "date";
bottomSeries.stroke = chart.colors.getIndex(0);
bottomSeries.strokeWidth = 2;
chart.scrollbarX = new am4core.Scrollbar();
chart.data = [ {
"date": "2019-08-01",
"open": 132.3,
"high": 136.96,
"low": 131.15,
"close": 136.49
}, {
"date": "2019-08-02",
"open": 135.26,
"high": 135.95,
"low": 131.50,
"close": 131.85
}, {
"date": "2019-08-03",
"open": 129.90,
"high": 133.27,
"low": 128.30,
"close": 132.25
}, {
"date": "2019-08-04",
"open": 132.94,
"high": 136.24,
"low": 132.63,
"close": 135.03
}, {
"date": "2019-08-05",
"open": 136.76,
"high": 137.86,
"low": 132.00,
"close": 134.01
}, {
"date": "2019-08-06",
"open": 131.11,
"high": 133.00,
"low": 125.09,
"close": 126.39
}, {
"date": "2019-08-07",
"open": 130.11,
"high": 133.00,
"low": 125.09,
"close": 127.39
}, {
"date": "2019-08-08",
"open": 125.11,
"high": 126.00,
"low": 121.09,
"close": 122.39
}, {
"date": "2019-08-09",
"open": 131.11,
"high": 133.00,
"low": 122.09,
"close": 124.39
}];
addMediana();
function addMediana(){
for(var i = 0; i < chart.data.length; i++){
var dataItem = chart.data[i];
dataItem.mediana = Number(dataItem.low) + (Number(dataItem.high) - Number(dataItem.low)) / 2;
}
}
}); // end am4core.ready()
</script>
</body>
</html>
I am working with JSON like the following:
[
{
"source": "Google",
"date": "2014-02-01",
"spend": 21,
"clicks": 1000
},
{
"source": "Bing",
"date": "2014-02-01",
"spend": 5,
"clicks": 541
},
{
"source": "Google",
"date": "2014-02-02",
"spend": 24,
"clicks": 1029
},
{
"source": "Bing",
"date": "2014-02-02",
"spend": 12,
"clicks": 754
}
]
And want to feed it into Crossfilter to create a line chart with NVD3. I don't know where to start, but I want the line chart to use the following:
X Axis - Date
Y Axis - Clicks
1 line per source
This is what I've been able to build without Crossfilter:
(function () {
var ymdFormat = d3.time.format('%Y-%m-%d');
d3.json('./data.json', function (err, json) {
nv.addGraph(function () {
var chart = nv.models.lineChart()
chart.margin({ left: 100 })
.useInteractiveGuideline(true)
.transitionDuration(350)
.showLegend(true)
.showYAxis(true).showXAxis(true);
chart.xAxis
.axisLabel('Date')
.tickFormat(function (d) {
return d3.time.format('%b %Y')(new Date(d));
});
chart.yAxis
.axisLabel('Clicks')
.tickFormat(d3.format(','));
data = parseData(json);
d3.select('#graph').append('svg')
.datum(data).call(chart);
});
})
function parseData(json) {
var data, result, key;
data = {};
json.forEach(function (elmt) {
if (!(elmt.source in data)) {
data[elmt.source] = { values: [] }
}
data[elmt.source].values.push({ x: ymdFormat.parse(elmt.date), y: elmt.clicks })
});
result = [];
for (key in data) {
if (data.hasOwnProperty(key)) {
result.push({
key: key,
values: data[key].values
});
}
}
console.log(result);
return result;
}
})()
I have this problem. I have a solution for this, not very nice, however.
var filterByMonthSource = filter.dimension(function(d) { return d.date + ':' + d.source });
var clicksGroup = filterByMonth.group().reduceSum(function(d) { return d.clicks });
var clicksData = clicksGroup.all();
clicksData will have [{key: "2014-02-01:Google", value: ...}, ...]
I then have to break clicksData into array of 3 fields.
I want to create a timeseries by passing a dictionary in the function. The code from the examples is this:
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
//etc...
What I'm trying to do is to convert this into a function which takes an object with date and close rows (data) and produces the chart from this, i.e.
function makeGraph(timeseriesdata){
// create chart above from data
// what format??
}
d3.csv does most of the hard work for you. It takes a csv and coverts it to an array of objects, each corresponding to a row of the csv.
https://github.com/mbostock/d3/wiki/CSV has some examples.
After calling d3.csv and cleaning up the data with parseDate, you can pass data to makeGraph.
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
makeGraph(data);
});
function makeGraph(timeseriesdata){
}
To create a line path given a dictionary make a list with a dict, such as:
var parseDate = d3.time.format("%Y-%m-%d").parse;
var lineData = [ { "date": "2013-04-01", "close": 5}, { "date": "2013-03-28", "close": 20},
{ "date": "2013-03-27", "close": 10}, { "date": "2013-03-26", "close": 40},
{ "date": "2013-03-25", "close": 5}, { "date": "2013-03-24", "close": 60}];
lineData.forEach(function(d,i) {
d.date = parseDate(d.date);
d.close = +d.close;
});
makeGraph(lineData);