I am creating a D3.js plot of multiple years of data. I would like to use the month names as the the axis labels. I am currently using the number of days into the year corresponding with the first day of that month as the labels as shown in the screenshot of my x-axis:
I believe all the relevant code is shown below:
var margins = {'top' : 20, 'right' : 20, 'bottom' : 50, 'left' : 70};
var height = 500;
var width = 960;
var plotHeight = height - margins.top - margins.bottom;
var plotWidth = width - margins.right - margins.left;
var x = d3.scaleLinear()
.domain([0,366])
.range([margins.left, plotWidth]);
// I am not concerned with leap years
var monthDays = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
var monthAbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var svg = d3.select('.svg').attr('width',width).attr('height',height);
svg.append('g').attr('transform', 'translate(0,' + plotHeight + ')').call(d3.axisBottom(x).tickValues(monthDays));
Is there some way to map the position on the x-axis to the correct month (i.e.: 31 to Feb.)? I have tried using d3.timeMonth/d3.timeMonths and ordinal scales but haven't found something suitable yet.
You can use tickFormat:
.tickFormat(function(d,i){ return monthAbr[i]})
Here is a working demo based on your code:
var margins = {'top' : 20, 'right' : 20, 'bottom' : 50, 'left' : 20};
var height = 100;
var width = 600;
var plotHeight = height - margins.top - margins.bottom;
var plotWidth = width - margins.right - margins.left;
var x = d3.scaleLinear()
.domain([0,366])
.range([margins.left, plotWidth]);
// I am not concerned with leap years
var monthDays = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
var monthAbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var svg = d3.select('body').append("svg").attr('width',width).attr('height',height);
svg.append('g').attr('transform', 'translate(0,' + plotHeight + ')').call(d3.axisBottom(x).tickValues(monthDays).tickFormat(function(d,i){ return monthAbr[i]}));
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
Fair value gap coding in pinescript?
I have tried to write code but unable to add more functions on it.
//#version=5
indicator('Fair Value Gap Akash Mehto', overlay=true)
boxLength = input.int(title='boxLength', defval=6, group='General', inline='General')
boxTransparency = input.int(title='boxTransparency', defval=85, group='General', inline='General')
bullishFvg = low[0] > high[2]
bearishFvg = high[0] < low[2]
if bullishFvg
box.new(left=bar_index - 2, top=low[0], right=bar_index + boxLength, bottom=high[2], bgcolor=color.new(color.rgb(28, 202, 121), boxTransparency), text="FVG", text_size = "tiny", text_halign = text.align_right, text_color = color.green, border_color=color.new(color.green, boxTransparency))
if bearishFvg
box.new(left=bar_index - 2, top=low[2], right=bar_index + boxLength, bottom=high[0], bgcolor=color.new(color.rgb(240, 46, 46), boxTransparency), text="FVG", text_size = "tiny", text_halign = text.align_right, text_color = color.red, border_color=color.new(color.green, boxTransparency))
*/ write a function daysPastThisYear that takes the current date as the name of the current month and the current day, e.g., daysPastThisYear(month: "May", day: 12), and returns how many days have past since the beginning of the year. Use a "while" loop. Ignore leap years. You may user the following list and dictionary defined:
let monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
let monthDays = ["January": 31, "February": 29, "March": 31, "April": 30, "May": 31, "June": 30, "July": 31, "August": 31, "September": 30, "October": 31, "November": 30, "December": 31]
E.g.
print(daysPastThisYear(month: "January", day: 12))
// prints 12
*/
//My code so far:
func daysPastThisYear(monthNames: [String], monthDays: [String:Int]) -> Int {
let monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
let monthDays = ["January": 31, "February": 29, "March": 31, "April": 30, "May": 31, "June": 30, "July": 31, "August": 31, "September": 30, "October": 31, "November": 30, "December": 31]
var i = 0
while month != monthNames[i] {
i ++
}
totalDays = 0
while i > 0 {
totalDays = totalDays + monthDays[i - 1]
-= i
totalDays = totalDays + day
print(totalDays)
}
}
print(daysPastThisYear(month: "January", day: 12))
Im an new to coding
Tried to calculate the number of accumulated days implementing two while loops
Multiple errors when attemtong to run
Building on top of Leo Dabus's solution but using a while loop as requested
let monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
let monthDays = ["January": 31, "February": 29, "March": 31, "April": 30, "May": 31, "June": 30, "July": 31, "August": 31, "September": 30, "October": 31, "November": 30, "December": 31]
func daysPastThisYear(month: String, day: Int) -> Int {
var total = 0
var monthIndex = 0
// while loop though the monthNames array
while monthIndex <= (monthNames.count - 1) {
let monthName = monthNames[monthIndex]
if month == monthName {
total += day
break
} else {
total += monthDays[monthName] ?? 0
}
monthIndex += 1
}
return total
}
You can iterate the month names and each iteration just check if the month name is equal to the month, if not true add the number of days of the month to the total otherwise add the day to the total and break the loop. After the loop just return the total. Something like:
func daysPastThisYear(month: String, day: Int) -> Int {
var total = 0
for monthName in monthNames {
if month == monthName {
total += day
break
} else {
total += monthDays[monthName] ?? 0
}
}
return total
}
edit/update:
Using a while loop
func daysPastThisYear(month: String, day: Int) -> Int {
var total = 0
var index = 0
while monthNames.indices.contains(index) && month != monthNames[index] {
total += monthDays[monthNames[index]] ?? 0
index += 1
}
return total + day
}
I'm trying to display 36 hours array
labels = ["01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00"]
with data
data = [16.1, 15.55, 14.12, 11.81, 9.637, 8.022, 6.625, 5.105, 5.216, 8.398, 11.4, 10.86, 10.52, 11.14, 15.37, 13.26, 11.33, 8.572, 12.21, 16.98, 12.43, 10.4, 10.09, 10.19, 9.361, 9.068, 9.763, 12.06, 15.52, 17.32, 15.53, 14.46, 14.05, 24.24, 12.26, 11.01]
but displayed only 24 elements from 1:00 to 00:00. How can I configure D3 axis for displaying data with repeated time stamps?
Don't just use the hours, create the axis with full timestamps and then format the axis labels to only show the time:
var width = 600;
var height = 50;
var yPad = 50;
var xPad = 50;
var xTicks = 18;
var now = d3.utcHour(new Date());
var h36 = d3.utcHour.offset(now, 36);
var svg = d3
.select("#d3")
.append('svg')
.attr('width', width)
.attr('height', height);
var xScale = d3
.scaleTime()
.domain([now, h36])
.rangeRound([1 * xPad, width - xPad]);
var xAxis = d3
.axisBottom(xScale)
.ticks(xTicks)
.tickFormat(d3.timeFormat('%H00'));
svg
.append('g')
.attr('class', 'xaxis')
.attr('transform', 'translate(0, ' + (height - yPad) + ')')
.call(xAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="d3"></div>
How make a possible to display 2 values in 1 column (split red and yellow columns in one)?
I have plan of sold and value of sold ordered by months.
For ex:
Jan -
plan:100, sold:80
Feb -
plan:150, sold:150
So i want to see 2(4 columns in mind, second value should overlap first value) columns:
100 80
150 150
first column will be colored in two colors because sold value less then plan (100/80 )
second column will be colored in one color(yellow), because second column overlap first value 150/150
isStacked: true doesn't overlay first column
Thanks for advise.
Code that i use JsFiddle and what I need
google.load('visualization', '1', {packages: ['corechart', 'bar']});
google.setOnLoadCallback(drawStacked);
function drawStacked() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Date');
data.addColumn('number', 'Plan');
data.addColumn('number', 'Sold');
data.addRows([
['Jan, 2015', 100, 80],
['Feb, 2015', 150, 150],
]);
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(data, {
colors: ['red', 'yellow'],
width: 600,
height: 175,
title: 'Total',
legend: 'none',
});
}
I was just doing a pie sample so I set up a diff bar sample with your data. It has our PDF print code in it but shows what I think you want (except the color):
http://jsfiddle.net/1og99wL1/
function drawChart() {
var oldData = new google.visualization.DataTable();
oldData.addColumn('string', 'Date');
oldData.addColumn('number', 'Plan');
oldData.addRows([
['Jan, 2015', 100],
['Feb, 2015', 150],
]);
var newData = new google.visualization.DataTable();
newData.addColumn('string', 'Date');
newData.addColumn('number', 'Sold');
newData.addRows([
['Jan, 2015', 80],
['Feb, 2015', 150],
]);
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
var data = chart.computeDiff(oldData, newData);
var options = {
colors: ['yellow'],
diff: {
oldData: { opacity: 1, color: '#ff0000' },
newData: { opacity: 1, widthFactor: 1 }
},
legend: 'none',
width: 600
};
<!-- For the PDF print -->
google.visualization.events.addListener(chart, 'ready', AddNamespace);
chart.draw(data, options);
}
Result:
Comments:
You must use "oldData" and "newData" as the names for the datasets. You cannot just choose arbitrary names. If you do, the chart will draw but the "diff" option will not work (crazy, must be burned into the code). The color must be set like below with one color set in "colors" option and the other set in "diff" option.
I'm using Google Chart's stacked column chart, what i wanna achieve is to display the total on top of each column and i'm using annotation for this. As you look at the image, somehow only the annotation on the 5th column (1,307.20) is working as expected.
As i investigate , this seem like a bug of Google Chart , this bug can be explained like below
[[Date, Car, Motobike, {role: :annotation}],
[June 2015, 500, 0, 500],
[Feb 2015, 500, 600, 1100]]
[March 2015, 700, 0, 700],
With the above data, the annotation for Feb 2015 is the only which is displayed correctly , the other 2 do not since the last value of then is 0 , when I change the last value to 1 for June and March , the annotation is displayed correctly.
Then I think of a work around is to always display the "non-zero" data on top , and here's the result:
The annotations are moved on top properly , but as you can see, it's located within the column and what i want to achieve is to move it on top of the column .
I'm stuck with this for a while , Google Documentation doesn't help much with this case. Any help would be highly appreciated
I had the same problem, some of my series had 0 as my last value so the label would show on the X Axis instead of at the top. With dynamic data it would be a real challenge to ensure the last value was never 0. #dlaliberte gave me a hint where to start with this comment:
"As a workaround, you might consider using a ComboChart with an extra
series to draw a point at the top of each column stack. You'll have to
compute the total of the other series yourself to know where to put
each point."
I found a combo chart from google's gallery and opened jsfiddle to see what I could do. I left the data mostly, but changed the series name labels and made the numbers a little simpler. Don't get caught up on the purpose of the graph the data is regardless, I just wanted to figure out how to get my annotation to the top of the graph even when the last column was 0 (https://jsfiddle.net/L5wc8rcp/1/):
function drawVisualization() {
// Some raw data (not necessarily accurate)
var data = google.visualization.arrayToDataTable([
['Month', 'Bolivia', 'Ecuador', 'Madagascar', 'Papua New Guinea', 'Rwanda', 'Total', {type: 'number', role: 'annotation'}],
['Application', 5, 2, 2, 8, 0, 17, 17],
['Friend', 4, 3, 5, 6, 2, 20, 20],
['Newspaper', 6, 1, 0, 2, 0, 9, 9],
['Radio', 8, 0, 8, 1, 1, 18, 18],
['No Referral', 2, 2, 3, 0, 6, 13, 13]
]);
var options = {
isStacked: true,
title : 'Monthly Coffee Production by Country',
vAxis: {title: 'Cups'},
hAxis: {title: 'Month'},
seriesType: 'bars',
series: {5: {type: 'line'}},
};
var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
That produced this graph, which is a great start:
As you can see since series 5 (our Total of the other series) is a type: 'line', so it will always point to the top of the stack. Now, I didn't necessarily want the line in my chart, since it was not used to compare continuous horizontal totals, so I updated series 5 with lineWidth: 0, and then made the title of that category '' so that it wouldn't be included in the legend as a stack (https://jsfiddle.net/Lpgty7rq/):
function drawVisualization() {
// Some raw data (not necessarily accurate)
var data = google.visualization.arrayToDataTable([
['Month', 'Bolivia', 'Ecuador', 'Madagascar', 'Papua New Guinea', 'Rwanda', '', {type: 'number', role: 'annotation'}],
['Application', 5, 2, 2, 8, 0, 17, 17],
['Friend', 4, 3, 5, 6, 2, 20, 20],
['Newspaper', 6, 1, 0, 2, 0, 9, 9],
['Radio', 8, 0, 8, 1, 1, 18, 18],
['No Referral', 2, 2, 3, 0, 6, 13, 13]
]);
var options = {
isStacked: true,
title : 'Monthly Coffee Production by Country',
vAxis: {title: 'Cups'},
hAxis: {title: 'Month'},
seriesType: 'bars',
series: {5: {type: 'line', lineWidth: 0}},
};
var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
And Voila!
Use alwaysOutside: true.
annotations: {
textStyle: {
color: 'black',
fontSize: 11,
},
alwaysOutside: true
}
You will want to use the annotations.alwaysOutside option:
annotations.alwaysOutside -- In Bar and Column charts, if set to true,
draws all annotations outside of the Bar/Column.
See https://google-developers.appspot.com/chart/interactive/docs/gallery/columnchart
However, with a stacked chart, the annotations are currently always forced to be inside the columns. This will be fixed in the next major release.
As a workaround, you might consider using a ComboChart with an extra series to draw a point at the top of each column stack. You'll have to compute the total of the other series yourself to know where to put each point. Then make the pointSize 0, and add the annotation column after this series.