Puzzling problem:
My d3 chart, which has a day selector (right arrow) works in a stand-alone file. By "works" I mean when you click on the right arrow it advances through the data array and the chart bars update.
However, inside a jquery mobile multipage document, it won't work in Firefox. In jqm multipage it does work in Chrome (linux and windows) and IE.
Briefly, you click on the "chart page," then click on the right arrow to view the data for each day.
Don't worry about the arrow code, I have to fix it. What worries me is that this doesn't work in Firefox.
Here is the fiddle. Try it in those browsers.
html:
<!-- Start of first page: #one -->
<div data-role='page' id='one'>
<div data-role='header'>
<h1>Multi-page</h1>
</div>
<!-- /header -->
<div data-role='content'>
<h2>One</h2>
<p>I have an <code>id</code> of 'one' on my page container. I'm first in the source order so I'm shown when the page loads.</p>
<h3>Show internal pages:</h3>
<p><a href='#snapshot-chart-page' data-role='button'>Chart page</a></p>
</div>
<!-- /content -->
<div data-role='footer' data-theme='d'>
<h4>Page Footer</h4>
</div>
<!-- /footer -->
</div>
<!-- /page one -->
<div data-role='page' id='snapshot-chart-page' data-ajax='false'>
<div data-role='header' style='background:#82ca46;'>
<a href='#nav-panel' data-icon='bars' data-iconpos='notext' class='ui-nodisc-icon' style='background-color: transparent;'>Menu</a>
<div align='center' style='vertical-align:top;'>
<h1>Page title
</h1></div>
<a href='#add-form' data-icon='calendar' data-iconpos='notext' class='ui-nodisc-icon' style='background-color: transparent;'>Add</a>
</div>
<!-- /header -->
<div role='main' class='ui-content'>
<div style='width:100%; margin: 0;padding: 0; overflow: auto;'>
<form style='display: inline-block;
position: relative;
vertical-align: middle;
margin-right: 6px;'>
<input type='button' data-role='button' data-icon='arrow-l' data-iconpos='notext' class='ui-nodisc-icon previous-next-period left-previous select-custom-14' style='background-color: transparent;' field='quantity'>
<input type='text' name='quantity' value='0' class='qty' />
<input type='button' data-role='button' data-icon='arrow-r' data-iconpos='notext' class='ui-nodisc-icon previous-next-period right-next select-custom-14' style='background-color: transparent;' field='quantity'>
</form>
<table data-role='table' data-transition='fade' class='ghg-tables'>
<caption class='barchart_title tabletitle'></caption>
<thead>
</thead>
</table>
<div class='chart-here'></div>
</div>
</div>
<!-- /main -->
</div>
<!-- /snapshot-chart-page -->
js:
(function() {
var margin = {
top: 20,
right: 20,
bottom: 40,
left: 80
},
width = 340 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var svg = d3.select('.chart-here').append('svg')
.attr('viewBox', '0 0 340 250')
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left / 1.5 + ',' + margin.top / 1.5 + ')');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(5)
.outerTickSize(0)
.tickFormat(d3.format(',.3s'));
var barchart_i = 0;
var arr1 = [{
"period": "day",
"allcars_title": "Mileage by car, on Tuesday, March 01 2016. Total: 454.95M mi.",
"car": [{
"Chevvy": 33733000
}, {
"Ford": 32633000
}, {
"Honda": 119182000
}, {
"Tesla": 614000
}, {
"Audi": 268292000
}, {
"Hummer": 493000
}]
}, {
"period": "day",
"allcars_title": "Mileage by car, on Wednesday, March 02 2016. Total: 457.26M mi.",
"car": [{
"Chevvy": 23052000
}, {
"Ford": 44630000
}, {
"Honda": 121635000
}, {
"Tesla": 2599000
}, {
"Audi": 264247000
}, {
"Hummer": 1100000
}]
}, {
"period": "day",
"allcars_title": "Mileage by car, on Thursday, March 03 2016. Total: 452.96M mi.",
"car": [{
"Chevvy": 8577000
}, {
"Ford": 54172000
}, {
"Honda": 121661000
}, {
"Tesla": 1975000
}, {
"Audi": 265483000
}, {
"Hummer": 1089000
}]
}];
var period_grain = arr1[0].period;
var allcars_hour = arr1[barchart_i];
var car = allcars_hour.car;
var allcars_dash_title = allcars_hour.allcars_title;
jQuery('.barchart_title').text(allcars_dash_title);
var newobj = [];
for (var allcars_hourx1 = 0; allcars_hourx1 < car.length; allcars_hourx1++) {
var xx = car[allcars_hourx1];
for (var value in xx) {
var chartvar = newobj.push({
car: value,
miles: xx[value]
});
var data = newobj;
data = data.sort(function(a, b) {
return b.miles - a.miles;
});
}
}
x.domain(data.map(function(d) {
return d.car;
}));
if (period_grain == 'hour') {
var staticMax = 13000000;
}
if (period_grain == 'day') {
var staticMax = 300000000;
}
if (period_grain == 'month') {
var staticMax = 2000000;
}
if (period_grain == 'year') {
var staticMax = 35000000;
}
y.domain([0, d3.max(data, function(d) {
return d.miles > staticMax ? d.miles : staticMax;
})]);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'start');
var changeHour = function() {
var dataJoin = svg.selectAll('.bar')
.data(data, function(d) {
return d.car;
});
svg.selectAll('.y.axis')
.call(yAxis);
xtext = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(-20,' + height + ')') /*move tick text so it aligns with rects*/
.call(xAxis)
/* possible new elements, fired first time, set non-data dependent attributes*/
dataJoin
.enter()
.append('rect')
.attr('class', 'bar')
.attr('transform', 'translate(-20)') /*move rects closer to Y axis*/
/* changes to existing elements (now including the newly appended elements from above) which depend on data values (d)*/
dataJoin
.attr('x', function(d) {
return x(d.car);
})
.attr('width', x.rangeBand() * 1)
.attr('y', function(d) {
return y(d.miles);
})
.attr('height', function(d) {
return height - y(d.miles);
})
.style('fill', function(d) {
if (d.car == 'Audi' || d.car == 'Chevvy' || d.car == 'Honda' || d.car == 'Hummer') {
return 'green';
} else {
return '#404040';
}
})
xtext.selectAll('text')
.attr('transform', function(d) {
return 'translate(' + this.getBBox().height * 50 + ',' + this.getBBox().height + ')rotate(0)';
});
dataJoin.exit().remove();
}
changeHour();
//function to allow user to click arrows to view next/previous period_grain
// This button will increment the value
$('.right-next').click(function(e) {
// Stop acting like a button
e.preventDefault();
// Get the field name
fieldName = $(this).attr('field');
// Get its current value
barchart_i = parseInt($('input[name=' + fieldName + ']').val());
// If is not undefined
if (!isNaN(barchart_i)) {
// Increment
$('input[name=' + fieldName + ']').val(barchart_i + 1);
} else {
// Otherwise set to 0
$('input[name=' + fieldName + ']').val(0);
}
incrementHour();
});
// This button will decrement the value till 0
$('.left-previous').click(function(e) {
// Stop acting like a button
e.preventDefault();
// Get the field name
fieldName = $(this).attr('field');
// Get its current value
barchart_i = parseInt($('input[name=' + fieldName + ']').val());
// If it isn't undefined or if it is greater than 0
if (!isNaN(barchart_i) && barchart_i > 0) {
// Decrement one
$('input[name=' + fieldName + ']').val(barchart_i - 1);
} else {
// Otherwise set to 0
$('input[name=' + fieldName + ']').val(0);
}
incrementHour();
});
incrementHour = function() {
allcars_hour = arr1[barchart_i];
var car = allcars_hour.car;
var allcars_dash_title = allcars_hour.allcars_title;
var newobj = [];
for (var allcars_hourx1 = 0; allcars_hourx1 < car.length; allcars_hourx1++) {
var xx = car[allcars_hourx1];
for (var value in xx) {
var chartvar = newobj.push({
car: value,
miles: xx[value]
});
}
}
data = newobj;
console.log('data is ' + data);
data = data.sort(function(a, b) {
return b.miles - a.miles;
});
x.domain(data.map(function(d) {
return d.car;
}));
y.domain([0, d3.max(data, function(d) {
return d.miles > staticMax ? d.miles : staticMax;
})]);
jQuery('.barchart_title').text(allcars_dash_title);
changeHour();
};
})();
This is bizarre. I have researched this extensively, and cannot find anything that relates to it.
Has anybody else encountered this? If so, are there any ideas how to fix it?
Related
The first time I load data, the graph draws correctly, but when I load a different data set, the graph remains unchanged.
I switch between datasets using buttons. The first click always draws the graph correctly, no matter what button I click. But I can't update the graph after it is drawn by clicking on the other button. Any help is very much appreciated,thank you!
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 900;
let height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width], 0.9);
let y = d3.scale
.linear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("g")
.data(dataSet)
.enter()
.append("g")
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
});
bar
.append("rect")
.attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
CodePen: https://codepen.io/rfripp2/pen/porpaLL
This is the expected behavior. Let's look at your code:
let bar = chart
.selectAll("g")
.data(dataSet)
.enter()
.append("g")
The select all statement selects all existing elements matching the selector that are children of elements in the selection chart.
The data method binds a new data array to this selection.
The enter method returns a new selection, containing a placeholder for every item in the data array which does not have a corresponding element in the selection.
The append method returns a newly appended child element for every element in the selection it is called on.
Running the code
The first time you call the draw function you have no g elements, so the selection is empty. You bind data to this empty selection. You then use the enter selection. Because there are two data items and no elements in the selection, enter contains two placeholders/elements. You then use append to add those elements.
The second time you call the draw function you have two g elements, so the selection has two elements in it. You bind data to this selection. You then use the enter selection. Because you already have two elements and you only have two data points, the enter selection is empty. As a consequence, append does not create any new elements.
You can see this by using selection.size():
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 900;
let height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width], 0.9);
let y = d3.scale
.linear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("g")
.data(dataSet)
.enter()
.append("g")
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
});
console.log("The enter selection contains: " + bar.size() + "elements")
bar
.append("rect")
.attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
Solution
We want to use both the update and the enter selection (if the dataset ever changes in size, we'd likely want the exit selection too). We can use .join() to simplify this, join removes elements in the exit selection (surplus elements which don't have a corresponding data item), and returns the merged enter selection (new elements for surplus data items) and update selection (preexisting elements).
The nesting of your elements into a parent g and child rect is unnecessary here - and requires additional modifications. By positioning the bars directly we avoid the need for the parent g:
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 400;
let height = 200;
let x = d3.scaleBand().range([0, width], 0.9);
let y = d3.scaleLinear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("rect")
.data(dataSet)
.join("rect")
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
}).attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<br />
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
This requires updating your version of D3 from v3 (you are actually using two versions of D3, v3 and v4, both fairly outdated, and both with different method names, actually with different ways of handling the enter selection).
If you wish to use d3v4, then the join method is not available, but we can merge enter and update:
When I say update selection, I'm refering to the initial selection:
// update selection:
let bar = chart
.selectAll("rect")
.data(dataSet);
// enter selection:
bar.enter().append("rect")
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 400;
let height = 200;
let x = d3.scaleBand().range([0, width], 0.9);
let y = d3.scaleLinear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("rect")
.data(dataSet);
bar.enter().append("rect")
.merge(bar)
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
}).attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<br />
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.0/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
And lastly, if you wish to keep d3v3 (we are on v7 already), we can rely on an implicit merging of update and enter on enter (modifying the update selection). This "magic" was removed in v4, partly because it was not explicit. To do so we need to break your method chaining so that bar contains the
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 400;
let height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width], 0.9);
let y = d3.scale.linear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
// update selection:
let bar = chart
.selectAll("rect")
.data(dataSet);
// enter selection:
bar.enter().append("rect")
bar.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
}).attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<br />
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
Note: d3v4 made changes to method names that break code from v3. This required changes to d3.scale.linear / d3.scale.ordinal in the snippets using v4 and 7 (using merge and join respectively).
I have a project where I need to display a doughnut chart. For every slice in the chart there is a corresponding icon in the legend. This icon should also been shown on the slice itself inside the chart.
I have found a working example online on how to display images on doughnut charts: Working example. I have tried to implement this solution into my own project. The images get loaded in and when I inspect the SVG each path node(slice) contains an image element with the correct image. But the images don't show up on the graph.
This is the code i am running atm. If you have some pointers on how to improve my overall code then you're welcome to do so. I am still new to D3.JS and learning a lot about it at the moment:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div class="p-3 flex flex-col" id="one">
<div class="w-full flex-1">
<div id="my_dataviz"></div>
</div>
</div>
<script>
new Vue({
el: '#one',
data: {
type: Array,
required: true,
}, mounted() {
// set the dimensions and margins of the graph
var width = 450;
var height = 450;
var margin = 1;
var image_width = 32;
var image_height = 32;
var data = [
{
key: "One",
value: 20,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Two",
value: 30,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Three",
value: 10,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Four",
value: 15,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
}
]
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select('#my_dataviz')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 2 + ',' + height / 2 + ')'
);
var radius = Math.min(width, height) / 2 - margin;
// set the color scale
var color = d3
.scaleOrdinal()
.domain(
data.map(function(d) {
return d["key"];
})
)
.range(["#206BF3"]);
// Compute the position of each group on the pie:
var pie = d3.pie().value(function(d) {
return d[1];
});
var data_ready = pie(
data.map(function(d) {
return [d["key"], d["value"], d["icon"]];
})
);
// declare an arc generator function
var arc = d3
.arc()
.outerRadius(100)
.innerRadius(50);
console.log(arc);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
var paths = svg
.selectAll("whatever")
.data(data_ready)
.enter()
.append("path")
.attr("d", d => {
return arc(d);
})
.attr("fill", function(d) {
return color(d.data[0]);
})
.attr("stroke", "#2D3546")
.style("stroke-width", "2px")
.style("opacity", 0.7);
paths
.append("svg:image")
.attr("transform", function(d) {
var x = arc.centroid(d)[0] - image_width / 2;
var y = arc.centroid(d)[1] - image_height / 2;
return "translate(" + width / 2 + x + "," + height + y + ")";
})
.attr("xlink:href", function(d) {
console.log(d);
return d.data[2];
})
.attr("width", image_width)
.attr("height", image_height);
paths.on("mouseover", e => {
this.pathAnim(radius, d3.select(e.currentTarget), 1);
});
paths.on("mouseout", e => {
var thisPath = d3.select(e.currentTarget);
if (!thisPath.classed("clicked")) {
this.pathAnim(radius, thisPath, 0);
}
});
},
methods: {
pathAnim(radius, path, dir) {
switch (dir) {
case 0:
path
.transition()
.duration(500)
.ease(d3.easeBounce)
.attr(
"d",
d3
.arc()
.innerRadius(100)
.outerRadius(50)
);
path.style("fill", "#206BF3");
break;
case 1:
path.transition().attr(
"d",
d3
.arc()
.innerRadius(50)
.outerRadius(110)
);
path.style("fill", "white");
break;
}
}
}
});
</script>
</body>
</html>
A <path> element cannot contain an <image>. Instead of that, use the data to create <g> elements and append both the <path> and the <image> to them:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div class="p-3 flex flex-col" id="one">
<div class="w-full flex-1">
<div id="my_dataviz"></div>
</div>
</div>
<script>
new Vue({
el: '#one',
data: {
type: Array,
required: true,
},
mounted() {
// set the dimensions and margins of the graph
var width = 450;
var height = 450;
var margin = 1;
var image_width = 32;
var image_height = 32;
var data = [{
key: "One",
value: 20,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Two",
value: 30,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Three",
value: 10,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Four",
value: 15,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
}
]
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select('#my_dataviz')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 2 + ',' + height / 2 + ')'
);
var radius = Math.min(width, height) / 2 - margin;
// set the color scale
var color = d3
.scaleOrdinal()
.domain(
data.map(function(d) {
return d["key"];
})
)
.range(["#206BF3"]);
// Compute the position of each group on the pie:
var pie = d3.pie().value(function(d) {
return d[1];
});
var data_ready = pie(
data.map(function(d) {
return [d["key"], d["value"], d["icon"]];
})
);
// declare an arc generator function
var arc = d3
.arc()
.outerRadius(100)
.innerRadius(50);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
var g = svg
.selectAll("whatever")
.data(data_ready)
.enter()
.append("g")
.attr("transform", function(d) {
var x = arc.centroid(d)[0] - image_width / 2;
var y = arc.centroid(d)[1] - image_height / 2;
return "translate(" + width / 2 + x + "," + height + y + ")";
});
g.append("path")
.attr("d", d => {
return arc(d);
})
.attr("fill", function(d) {
return color(d.data[0]);
})
.attr("stroke", "#2D3546")
.style("stroke-width", "2px")
.style("opacity", 0.7);
g.append("svg:image")
.attr("transform", function(d) {
var x = arc.centroid(d)[0] - image_width / 2;
var y = arc.centroid(d)[1] - image_height / 2;
return "translate(" + x + "," + y + ")";
})
.attr("xlink:href", function(d) {
return d.data[2];
})
.attr("width", image_width)
.attr("height", image_height);
g.on("mouseover", e => {
this.pathAnim(radius, d3.select(e.currentTarget), 1);
});
g.on("mouseout", e => {
var thisPath = d3.select(e.currentTarget);
if (!thisPath.classed("clicked")) {
this.pathAnim(radius, thisPath, 0);
}
});
},
methods: {
pathAnim(radius, path, dir) {
switch (dir) {
case 0:
path
.transition()
.duration(500)
.ease(d3.easeBounce)
.attr(
"d",
d3
.arc()
.innerRadius(100)
.outerRadius(50)
);
path.style("fill", "#206BF3");
break;
case 1:
path.transition().attr(
"d",
d3
.arc()
.innerRadius(50)
.outerRadius(110)
);
path.style("fill", "white");
break;
}
}
}
});
</script>
</body>
</html>
I've updated #GerardoFurtado's code. I just moved the events to the paths and added pointer-events: none for images. Transitions work well.
g image {
pointer-events: none;
}
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/vue"></script>
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<body>
<div class="p-3 flex flex-col" id="one">
<div class="w-full flex-1">
<div id="my_dataviz"></div>
</div>
</div>
<script>
new Vue({
el: '#one',
data: {
type: Array,
required: true,
},
mounted() {
// set the dimensions and margins of the graph
var width = 450;
var height = 450;
var margin = 1;
var image_width = 32;
var image_height = 32;
var data = [{
key: "One",
value: 20,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Two",
value: 30,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Three",
value: 10,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
},
{
key: "Four",
value: 15,
icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
}
]
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select('#my_dataviz')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 2 + ',' + height / 2 + ')'
);
var radius = Math.min(width, height) / 2 - margin;
// set the color scale
var color = d3
.scaleOrdinal()
.domain(
data.map(function(d) {
return d["key"];
})
)
.range(["#206BF3"]);
// Compute the position of each group on the pie:
var pie = d3.pie().value(function(d) {
return d[1];
});
var data_ready = pie(
data.map(function(d) {
return [d["key"], d["value"], d["icon"]];
})
);
// declare an arc generator function
var arc = d3
.arc()
.outerRadius(100)
.innerRadius(50);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
var g = svg
.selectAll("whatever")
.data(data_ready)
.enter()
.append("g")
/* I commented this lines and nothing changed.
.attr("transform", function(d) {
var x = arc.centroid(d)[0] - image_width / 2;
var y = arc.centroid(d)[1] - image_height / 2;
return "translate(" + width / 2 + x + "," + height + y + ")";
});
*/
g.append("path")
.attr("d", d => {
return arc(d);
})
.attr("fill", function(d) {
return color(d.data[0]);
})
.attr("stroke", "#2D3546")
.style("stroke-width", "2px")
.style("opacity", 0.7)
.on("mouseover", e => {
console.log(this)
this.pathAnim(radius, d3.select(e.currentTarget), 1);
})
.on("mouseout", e => {
var thisPath = d3.select(e.currentTarget);
if (!thisPath.classed("clicked")) {
this.pathAnim(radius, thisPath, 0);
}
});
g.append("svg:image")
.attr("transform", function(d) {
var x = arc.centroid(d)[0] - image_width / 2;
var y = arc.centroid(d)[1] - image_height / 2;
return "translate(" + x + "," + y + ")";
})
.attr("xlink:href", function(d) {
return d.data[2];
})
.attr("width", image_width)
.attr("height", image_height);
},
methods: {
pathAnim(radius, path, dir) {
switch (dir) {
case 0:
path
.transition()
.duration(500)
.ease(d3.easeBounce)
.attr(
"d",
d3
.arc()
.innerRadius(100)
.outerRadius(50)
);
path.style("fill", "#206BF3");
break;
case 1:
path.transition().attr(
"d",
d3
.arc()
.innerRadius(50)
.outerRadius(110)
);
path.style("fill", "white");
break;
}
}
}
});
</script>
</body>
</html>
I am trying to put together an interactive bar chart in cshtml.
The good news is it works on every browser except for Firefox.
That being said I'd very much like to know why it is failing on Firefox when it even works on Internet Explorer.. I mean come on, the internet doesn't even work on Internet Explorer.
I have added in what I believe to be the relevant patch of code here:
function buildVisualization(dataSet) {
var barWidth = (chartWidth / dataSet.Items.length - 1) - 1;
var bars = svg.selectAll("rect")
.data(dataSet.Items);
// Build bars for each item
// Example "rect" element: <rect x="200" y="400" width="300" height="100" style="" class="" />
bars.enter()
.append("rect")
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) } )
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate)})
.attr("width", function (item) { return barWidth})
.attr("height", function (item) { return yScale(item.Rate)})
.attr("fill", "teal");
bars.exit().remove();
bars.transition()
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked))} )
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate)})
.attr("width", function (item) { return barWidth})
.attr("height", function (item) { return yScale(item.Rate)})
.attr("fill", "teal");
}
That being said I can provide any information required if requested.
I should point out that when run the chart itself is put into the right place however the bars (an important bit of a bar chart) are all pushed off to the left and stacked on top of each other though they do change height when different options are selected so it seems to be something wrong with the positioning rather than with how they are created. Any advice would be quite welcome.
Entire Snippet:
#{
ViewBag.Title = "Bar Chart";
var choices = new List<SelectListItem>
(){
new SelectListItem(){Text= "C#", Value="c#", Selected=true },
new SelectListItem(){Text= ".Net", Value=".net" },
new SelectListItem(){Text= "ASP.Net", Value="asp.net" },
new SelectListItem(){Text= "ASP.Net MVC", Value="asp.net-mvc" },
new SelectListItem(){Text= "C", Value="c" },
new SelectListItem(){Text= "C++", Value="c++" },
new SelectListItem(){Text= "JavaScript", Value="javascript" },
new SelectListItem(){Text= "Objective C", Value="objective-c" },
new SelectListItem(){Text= "PHP", Value="php" },
new SelectListItem(){Text= "Ruby", Value="ruby" },
new SelectListItem(){Text= "Python", Value="python" }
};
}
<style type="text/css">
svg g.axis {
font-size: .75em;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
svg g.axis text.label {
font-size: 2em;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
svg g.axis path,
svg g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
</style>
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-8">
<p>This demo takes tag information from data.stackexchange.com and projects it below.</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
#Html.Label("TagChoice", "Tag")
#Html.DropDownList("TagChoice", choices)
</div>
</div>
<div class="row">
<div id="chartContainer">
</div>
</div>
#Scripts.Render("~/bundles/d3")
#section Scripts{
<script type="text/javascript">
$(document).ready(function () {
$("#TagChoice").on("change", function () {
var tag = $(this).val();
var url = "/api/tags?tag=";
url += encodeURIComponent(tag);
$.getJSON(url, function (data) {
buildVisualization(data);
});
});
$("#TagChoice").change();
});
// Overall dimensions of the SVG
var height = 400;
var width = 900;
// Padding...
var leftPadding = 75;
var bottomPadding = 50;
// Actual space for the bars
var chartWidth = width - leftPadding;
var chartHeight = height - bottomPadding;
//Building the scale for the heights
var yScale = d3.scale
.linear()
.range([0, chartHeight])
.domain([0, 21000]);
var yAxisScale = d3.scale
.linear()
.range([chartHeight, 0])
.domain([0, 21000]);
//Building the scale for the bar locations
var xScale = d3.time.scale()
.domain([new Date("5-1-2008"), new Date("2-1-2014")])
.range([leftPadding, width - 10]);
//Building a Y axis
var yAxis = d3.svg.axis()
.scale(yAxisScale)
.orient("left");
// Building an X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(d3.time.format("%m/%d/%Y"));
// Build the overall SVG container
var svg = d3.select("#chartContainer")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "chart");
// Adding the Axes
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + leftPadding + ",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("dy", "-55px")
.attr("dx", "-50px")
.attr("class", "label")
.style("text-anchor", "end")
.text("Number of Questions Asked");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + chartHeight + ")")
.call(xAxis)
.append("text")
.attr("dy", "40px")
.attr("dx", "475px")
.attr("class", "label")
.style("text-anchor", "end")
.text("Month Asked");
function buildVisualization(dataSet) {
var barWidth = (chartWidth / dataSet.Items.length - 1) - 1;
var bars = svg.selectAll("rect")
.data(dataSet.Items);
// Build bars for each item
// Example "rect" element: <rect x="200" y="400" width="300" height="100" style="" class="" />
bars.enter()
.append("rect")
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) })
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate) })
.attr("width", function (item) { return barWidth })
.attr("height", function (item) { return yScale(item.Rate) })
.attr("fill", "teal");
bars.exit().remove();
bars.transition()
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) })
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate) })
.attr("width", function (item) { return barWidth })
.attr("height", function (item) { return yScale(item.Rate) })
.attr("fill", "teal");
}
</script>
}
Perhaps the reason of your problem is that in Chrome
>> new Date("5-1-2008")
Thu May 01 2008 ...
while in Firefox:
>> new Date("5-1-2008")
Invalid Date
(this is relevant to lines, where you construct xScale)
I'm trying to teach myself how to make interactive visualizations in d3.js, currently working through Elijah Meeks' D3.js In Action. I'm trying to make his pie chart example interactive using three buttons. I'm doing something wrong with my tweening - I'm trying to save the currently displayed pie so that the transition goes between it and the newly chosen pie. However, my current pie keeps resetting to the initial pie. I think it's probably something simple, but I just can't figure out what I'm doing wrong.
Can someone tell me what to change to make my transitions work? To demonstrate the problem:
Run the code below,
Click on the 'Stat 2' button,
Click on the 'Stat 2' button again - you will see the pie resets to 'Stat 1', then smoothly transition to 'Stat 2'.
.as-console-wrapper { max-height: 20% !important;}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="viz">
<button id="0"> Stat 1 </button>
<button id="1"> Stat 2 </button>
<button id="2"> Stat 3 </button>
<br>
<svg style="width:400px;height:300px;border:1px lightgray solid;" />
</div>
</body>
<script>
var obj = [{
name: "a",
stat1: 10,
stat2: 20,
stat3: 30,
}, {
name: "b",
stat1: 30,
stat2: 20,
stat3: 10,
}, {
name: "c",
stat1: 15,
stat2: 25,
stat3: 50,
}];
function piechart(data) {
var currentPie = 0; //Initialize to stat1
var fillScale = d3.scaleOrdinal(d3.schemeCategory10);
var pieChart = d3.pie().sort(null);
var newArc = d3.arc().innerRadius(50).outerRadius(100);
// Create each pie chart
pieChart.value(d => d.stat1);
var stat1Pie = pieChart(data);
pieChart.value(d => d.stat2);
var stat2Pie = pieChart(data);
pieChart.value(d => d.stat3);
var stat3Pie = pieChart(data);
// Embed slices on each name
data.forEach((d, i) => {
var slices = [stat1Pie[i], stat2Pie[i], stat3Pie[i]];
d.slices = slices;
});
d3.select("svg")
.append("g")
.attr("transform", "translate(200, 150)")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", d => newArc(d.slices[currentPie]))
.attr("fill", (d, i) => fillScale(i))
.attr("stroke", "black")
.attr("stroke-width", "2px");
function transPie(d) {
var newPie = this.id;
console.log("Transition from pie " + currentPie + " to pie " + newPie);
d3.selectAll("path")
.transition()
.delay(500)
.duration(1500)
.attrTween("d", tweenPies)
function tweenPies(d, i) {
console.log(i + ":start tween function \n current pie = " + currentPie + "\n new pie = " + newPie);
var currentAngleStart = d.slices[currentPie].startAngle;
var newAngleStart = d.slices[newPie].startAngle;
var currentAngleEnd = d.slices[currentPie].endAngle;
var newAngleEnd = d.slices[newPie].endAngle;
return t => {
var interpolateStartAngle = d3.interpolate(currentAngleStart, newAngleStart);
var interpolateEndAngle = d3.interpolate(currentAngleEnd, newAngleEnd);
d.startAngle = interpolateStartAngle(t);
d.endAngle = interpolateEndAngle(t);
return newArc(d);
};
};
};
d3.selectAll("button").on("click", transPie);
};
piechart(obj);
</script>
</html>
You never set the state of currentPie to the new state after a selection. I've added a .on('end', handler to the transition to set this state:
.on('end', function(){
currentPie = newPie;
});
Running code:
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="viz">
<button id="0"> Stat 1 </button>
<button id="1"> Stat 2 </button>
<button id="2"> Stat 3 </button>
<br />
<svg style="width:1000px;height:500px;border:1px lightgray solid;"></svg>
</div>
<script>
var obj = [{name: "a",stat1: 10,stat2: 20,stat3: 30,},
{name: "b",stat1: 30,stat2: 20,stat3: 10,},
{name: "c",stat1: 15,stat2: 25,stat3: 50,}];
function piechart(data){
var currentPie = 0; //Initialize to stat1
var fillScale = d3.scaleOrdinal(d3.schemeCategory10);
var pieChart = d3.pie().sort(null);
var newArc = d3.arc().innerRadius(50).outerRadius(100);
// Create each pie chart
pieChart.value(d => d.stat1);
var stat1Pie = pieChart(data);
pieChart.value(d => d.stat2);
var stat2Pie = pieChart(data);
pieChart.value(d => d.stat3);
var stat3Pie = pieChart(data);
// Embed slices on each name
data.forEach( (d,i) => {
var slices = [stat1Pie[i], stat2Pie[i], stat3Pie[i]];
d.slices = slices;
});
d3.select("svg")
.append("g")
.attr("transform", "translate(250, 250)")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", d => newArc(d.slices[currentPie]))
.attr("fill", (d,i) => fillScale(i))
.attr("stroke", "black")
.attr("stroke-width", "2px");
function transPie(d) {
var newPie = +this.id;
console.log("Transition from pie " +currentPie+ " to pie " + newPie);
d3.selectAll("path")
.transition()
.delay(500)
.duration(1500)
.attrTween("d", tweenPies)
.on('end', function(){
currentPie = newPie;
})
function tweenPies(d, i) {
console.log(i + ":start tween function \n current pie = " + currentPie + "\n new pie = "+newPie);
var currentAngleStart = d.slices[currentPie].startAngle;
var newAngleStart = d.slices[newPie].startAngle;
var currentAngleEnd = d.slices[currentPie].endAngle;
var newAngleEnd = d.slices[newPie].endAngle;
return t => {
var interpolateStartAngle = d3.interpolate(currentAngleStart, newAngleStart);
var interpolateEndAngle = d3.interpolate(currentAngleEnd, newAngleEnd);
d.startAngle = interpolateStartAngle(t);
d.endAngle = interpolateEndAngle(t);
return newArc(d);
};
};
};
d3.selectAll("button").on("click", transPie);
};
piechart(obj);
</script>
</body>
</html>
I am trying to animate the attached code without success. I want the axis and path smoothly to shift in between the addition of new data points. The transition time should be deltaT. I hope someone could help me! (I got it working but only as long as the graph was not responsive, see attached js snippet)
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Transfer setup rpi1 interface</title>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="jquery-1.11.3.min.js"></script>
<script src="jquery.flot.min.js"></script>
<script src="jquery.flot.time.js"></script>
<script src="//d3js.org/d3.v3.js" charset="utf-8"></script>
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<div class="content">
<header>
<h1>Transfer Setup Rpi1</h1>
</header>
<div class="main">
<h2>Control</h2>
<fieldset>
<legend>Temperature</legend>
<div id="widthReader">
<table id="cssTable">
<tr>
<td>
<table id="cssTable">
<td><label for="toggleFan">Fan</label></td>
<td>
<input id="toggleFan" class="cmn-toggle cmn-toggle-round" type="checkbox">
<label for="toggleFan"></label>
</td>
</table>
</td>
<td>
</td>
<td>
<label>Speed:</label> <input type="text"
id="fanSpeed" name="fanSpeed" value="">
</td>
</tr>
<tr>
<td>
<table id="cssTable">
<td><label for="toggleHeater">Heater</label></td>
<td>
<input id="toggleHeater" class="cmn-toggle cmn-toggle-round" type="checkbox">
<label for="toggleHeater"></label>
</td>
</table>
</td>
<td>
</td>
<td>
<label>Setpoint:</label> <input type="text"
id="heaterTemp" name="heaterTemp" value="">
</td>
</tr>
</table>
</div>
</fieldset>
<button id="buttonSave">Save Settings</button>
<h2>Dashboard</h2>
<fieldset>
<legend>Chart</legend>
<label>Current Temperature:</label> <label id="heater_temperature"> </label><label>°C</label>
<div id="chart"></div>
</fieldset>
</div>
</div>
</body>
</html>
main.js:
var deltaT = 2500; //temperature update intervall
var Chart = (function(window,d3) {
var svg, data, x, y, xAxis, yAxis, dim, chartWrapper, line, path, margin = {}, width, height, locator, textsize, xlabeloff, ylabeloff, xtickpadding, ytickpadding, xtickdistance, ytickdistance;
var breakPoint = 400; //for rendering smaller fonts on mobile devices wiht small screen widths
var n = 10,//(4)*60,//length of recording
duration = deltaT,//duration of deltat in msec
now = new Date(Date.now() - duration),
count = 0,
data = Array.apply(null, new Array(n)).map(Number.prototype.valueOf,20);
//called once the data is loaded
function init() {
//initialize scales
x = d3.time.scale()
.domain([now - (n+1) * duration, now ])
.range([0, width]);
y = d3.scale.linear()
.domain([0, (1.1*d3.max(data))])
.range([height, 0]);
line = d3.svg.line()
.interpolate("basis")
.x(function (d, i) { return x(now - ((n-1) - i) * duration); })
.y(function (d, i) { return y(d); });
//initialize axis
x.axis = d3.svg.axis().scale(x).orient("top");
y.axis = d3.svg.axis().scale(y).orient("right");
x.axisT = d3.svg.axis().scale(x).orient("bottom").tickFormat("");
y.axisR = d3.svg.axis().scale(y).orient("left").tickFormat("");
//initialize svg
svg = d3.select('#chart')
.append('svg')
.style('pointer-events', 'none');
chartWrapper = svg
.append('g')
.style('pointer-events', 'all');
//cliping mask needed because trace updates and thus moves
clipMask = chartWrapper.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect");
path = chartWrapper.append('g').attr("clip-path", "url(#clip)").append('path').datum(data).classed('line', true);
xAxis = chartWrapper.append('g')
.attr("class", "x axis")
.call(x.axis);
xAxisT = chartWrapper.append('g')
.attr("class", "x axis")
.call(x.axisT);
yAxis = chartWrapper.append('g')
.attr("class", "y axis")
.call(y.axis);
yAxisR = chartWrapper.append('g')
.attr("class", "y axis")
.call(y.axisR);
yLabel = chartWrapper.append("text")
.attr("class", "y label")
//.attr("transform", "rotate(-90)")
.style("text-anchor", "middle")
.text("Temperature (°C)");
xLabel = chartWrapper.append("text")
.attr("class", "x label")
.attr("text-anchor", "middle")
.text("Time (h:min)");
chartWrapper.on('mousemove', onMouseMove)
.on("mouseover", function() { locator.style("display", null); })
.on("mouseout", function() { locator.style("display", "none"); });
//add locator
locator = chartWrapper.append('g')
.attr("class", "focus");
//.style("display", "none");
locator.append('circle')
.attr('r', 4);
locator.append("text")
.attr("x", 0)
.style("text-anchor", "middle")
.attr("dy", "-8");
touchScale = d3.time.scale()
.domain([now - (n+1) * duration, now ]);
//render the chart
render();
}
function render() {
//get dimensions based on window size
updateDimensions();
clipMask.attr("width", width)
.attr("height", height);
//update x and y scales to new dimensions
x.range([0, width]);
y.range([height, 0]);
touchScale.range([0,width]).clamp(true);
//update svg elements to new dimensions
svg
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom);
chartWrapper
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
xLabel.attr("y", height + xlabeloff )
.attr("x", ((width/2) ) );
yLabel.attr("y", - ylabeloff )
.attr("x", (margin.top - height / 2) )
.attr("transform", "rotate(-90)");
//update the axis and line
x.axis.scale(x).tickPadding(xtickpadding);
x.axisT.scale(x);
y.axis.scale(y).tickPadding(ytickpadding);
y.axisR.scale(y);
x.axis.ticks(Math.max(width/xtickdistance, 2));
y.axis.ticks(Math.max(height/ytickdistance, 2));
x.axisT.ticks(Math.max(width/xtickdistance, 2));
y.axisR.ticks(Math.max(height/ytickdistance, 2));
xAxis.attr('transform', 'translate(0,' + height + ')').call(x.axis);
xAxisT.call(x.axisT);
yAxis.call(y.axis);
yAxisR.call(y.axisR).attr('transform', 'translate(' +width +' ,0)');
path.attr('d', line);
updateTextStyle();
}
function updateTextStyle() {
winWidth = document.getElementById("widthReader").offsetWidth;
textsize = winWidth < breakPoint ? "48%" : "80%";
//scale text size
chartWrapper.selectAll("text").style("font-size", textsize);
yAxis.call(y.axis).selectAll(".tick text").style("text-anchor", "end");
}
function updateDimensions(winWidth) {
winWidth = document.getElementById("widthReader").offsetWidth;
margin.top = winWidth < breakPoint ? 4 :10;
margin.right = winWidth < breakPoint ? 4 : 10;
margin.left = winWidth < breakPoint ? 26 : 48;
margin.bottom = winWidth < breakPoint ? 22: 40;
xlabeloff = winWidth < breakPoint ? 21 : 38;
ylabeloff = winWidth < breakPoint ? 20 : 36;
xtickpadding = winWidth < breakPoint ? "-17" : "-24";
ytickpadding = winWidth < breakPoint ? "-10" : "-12";
xtickdistance = winWidth < breakPoint ? 50 : 80;
ytickdistance = winWidth < breakPoint ? 15 : 35;
width = winWidth - margin.left - margin.right;
height = .54 * width;
}
function onMouseMove() {
var x0 = x.invert(d3.mouse(this)[0]),
y0 = y.invert(d3.mouse(this)[1]);
locator.attr("transform", "translate(" + d3.mouse(this)[0] + "," + d3.mouse(this)[1] + ")");
locator.select("text").text(y0);
}
function tick() {
// update the domains
now = new Date();
touchScale.domain([now - (n-1) * duration, now ]);
x.domain([now - (n-1) * duration, now ]);
y.domain([0, (1.1*d3.max(data))]);
// push a new data point onto the back
data.push(Number($('#heater_temperature').text()));
path.attr("d", line)
.attr("transform", null);
// slide the x-axis left, rescale the y-axis
xAxisT.call(x.axisT);
xAxis.call(x.axis);
yAxis.call(y.axis);
yAxisR.call(y.axisR);
// redraw the line and slide line left
path.attr("transform", "translate(" + (x(now - (n+0) * duration) ) + " ,0)");
// pop the old data point off the front
data.shift();
updateTextStyle();
};
return {
render : render,
tick : tick,
init : init
}
})(window,d3);
$(document).ready(function() {
// Start deltaT timer to call RESTful endpoint
setInterval(function() {
$('#heater_temperature').text(Math.random()*220);
Chart.tick();
}, deltaT);
});
$(window).load(function() { //is done after document ready
window.addEventListener('resize', Chart.render);
Chart.init();
Chart.render();
});
style.css
.x.label
{
fill: #555;
}
.y.label
{
fill: #555;
}
.axis path,
.axis line {
stroke-width: 1px;
fill: none;
stroke: #ccc;
}
.axis text {
fill: #555;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1px;
}
woking js:
var transition = d3.select({}).transition()
.duration(deltaT)
.ease("linear");
function tick() {
transition = transition.each(function () {
// update the domains
now = new Date();
x.domain([now - (n-1) * duration, now ]);
y.domain([0, (1.1*d3.max(data))]);
// push a new data point onto the back
data.push(Number($('#heater_temperature').text()));
// slide the x-axis left, rescale the y-axis
xAxisT.call(x.axisT);
xAxisB.call(x.axisB);
yAxisL.call(y.axisL);
yAxisR.call(y.axisR);
// redraw the line, and slide it to the left
path.attr("d", line)
.attr("transform", " translate( " + margin.left + " ," + margin.top+" )");
// slide the line left
path.transition()
.attr("transform", "translate(" + (x(now - (n+0) * duration) + margin.left) + " ," + margin.top+ ")");
// pop the old data point off the front
data.shift();
});
}
I got it working... The problem was that i started using d3.v3 instead of d3.v2. Here is what I changed:
function tick() {
d3.transition().ease("linear").duration(deltaT-100).each(function() {
// update the domains
now = new Date(Date.now());
markerScale.domain([now - (n-2) * duration, now ]);
xScale.domain([now - (n-2) * duration, now ]);
yScale.domain([0, (1.1*d3.max(data))]);
// slide the x-axis left, rescale the y-axis
xAxisContT.transition().call(xAxisT);
xAxisCont.transition().call(xAxis);
yAxisCont.transition().call(yAxis);
yAxisContR.transition().call(yAxisR);
plot.attr("transform", null);
// push a new data point onto the back
data.push( Number($('#heater_temperature').text()) );
path.attr("d", line);
marker.data(data)
.attr("cx", xValue)
.attr("cy", yValue)
.attr("transform", null);
// redraw the line and slide plot left
plot.transition()
.attr("transform", "translate(" + (xScale(now - (n-1) * duration) ) + " ,0)");
// pop the old data point off the front
data.shift();
updateTextStyle();
});
}