I generate graph depending on the value from my calculations. Problem is, I don't know how to set up 'margins', how to define where to put data labels.
Datalables definition:
datalabels: {
anchor: 'end',
align: 'start',
offset: 5,
Problem is, when a certain month value is 0, it's written over the labels on the bottom. Easy way to fix this would be to define a space about each column, so that it can that text can never go 'off screen' and define align: 'end'.
Case 2:
You could define some extra padding at the top of your chart using the option layout.padding.top.
Please take a look at below runnable code and see how it works:
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
plugins: [ChartDataLabels],
data: {
labels: ['A', 'B', 'C'],
datasets: [{
label: 'My Dataset',
data: [0, 0, 3],
backgroundColor: 'orange'
}
]
},
options: {
layout: {
padding: {
top: 30
}
},
plugins: {
legend: {
display: false
},
datalabels: {
align: 'end',
anchor: 'end'
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.2.0/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.0.0-rc.1/chartjs-plugin-datalabels.min.js"></script>
<canvas id="myChart" height="120"></canvas>
Related
There is a wonderful library of js - Highcharts. I'm trying to link it to Django, and everything actually works, but not when I'm trying to insert a variable with content into data. Here's the code.
This function returns what I substitute in data in Highcharts.
def get_series(context):
data_ser = []
for i in context:
if i in ['One', "Two", "Three", "Four", "Five"]:
data_ser.append({
'name': i,
'y': context[i],
'z': 22.2
})
data_ser = json.dumps(data_ser)
return data_ser
And this is the jquery code itself:
<script>
$(document).ready(function () {
var data_ser = '{{ data_ser|safe }}'
console.log(data_ser)
Highcharts.chart('container', {
chart: {
type: 'variablepie'
},
title: {
text: 'Stats'
},
series: [{
minPointSize: 10,
innerSize: '20%',
zMin: 0,
name: 'countries',
data: data_ser
}]
});
})
</script>
In series in data, I try to substitute data_ser, but the graph is not output. Although, if you write it manually, then everything will work.
Similar code works:
<script>
$(document).ready(function () {
var data_ser = '{{ data_ser|safe }}'
console.log(data_ser)
Highcharts.chart('container', {
chart: {
type: 'variablepie'
},
title: {
text: 'Stats'
},
series: [{
minPointSize: 10,
innerSize: '20%',
zMin: 0,
name: 'countries',
data: [
{
"name": "One",
"y": 50.0,
"z": 22.2
}]
}]
});
})
</script>
I really hope for help. Or give at least alternative js libraries with graphs where this will work.
It looks like the issue is that data_ser is a string that represents a JavaScript object, but it is being treated as a string in the data property of the series object.
Try it with:
<script>
...
var data_ser = JSON.parse('{{ data_ser|safe }}')
...
</script>
I'm using this for my chart application right now
<script>
var canvas = document.getElementById('myChart');
new Chart(document.getElementById("myCanvas"), {
type: 'line',
data: {
labels: mon_unique,
datasets: [{
data: values,
borderColor: "#3e95cd",
fill: false
},
]
},
options: {
title: {
display: true,
},
hover: {
mode: 'index',
intersect: true
},
}
});
</script>
values, what I called my data in my flask app, is a list of numbers. When I change data: [0,1,2,3,4] it graphs it, but it doesn't pass in my values at all.
data = remove_err_str
return render_template('graphing.html', values=data)
This displays only the first two points in values. Values is a list of about 50,000 items. It looks like ['1243.42','2`,...]
<body>
<canvas id="myChart" width="400" height="400"></canvas>
<script>
var canvas = document.getElementById('myChart');
new Chart(document.getElementById("myChart"), {
type: 'line',
data: {
datasets: [{
data: {{values | safe}},
borderColor: "#3e95cd",
fill: false
},
]
},
options: {
title: {
display: true,
test: "Chart for the sweep data"
},
hover: {
mode: 'index',
intersect: true
},
}
});
</script>
</body>
This is the solution I found
Graphing HTML page
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0/Chart.bundle.js"></script>
</head>
<body>
<canvas id="myChart" width="1600" height="800"></canvas>
<script>
var canvas = document.getElementById('myChart');
var chart = new Chart(canvas, {
type: 'line',
data: {
labels: {{ labels | safe }},
datasets: [{
label: "Line chart for sweep data",
data: {{ values | safe }},
fill: false
}]
},
options: {
responsive: false,
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}}}
);
</script>
Every item needs a label. If it gets passed in without a label it won't graph. Also I changed the 'beginAtZero' to be set to true, otherwise it starts the lowest y value in the list. To create labels for every value I did this in my flask app.py
for i in data: #turns it from a list of string values to float values
float_data.append(float(i))
count = count + 1
label_arr.append(count)
return render_template('graphing.html', values=float_data, labels=label_arr)
:*]
You are passing data from python-end to front-end so you have to use jinja template inside your code and for that double-brackets can be used
<script>
var canvas = document.getElementById('myChart');
new Chart(document.getElementById("myCanvas"), {
type: 'line',
data: {
labels: mon_unique,
datasets: [{
data: {{values | tojson}},
borderColor: "#3e95cd",
fill: false
},
]
},
options: {
title: {
display: true,
},
hover: {
mode: 'index',
intersect: true
},
}
});
</script>
I am developing an application which shows the data created in a chartjs graph, what I am doing it does well, since all the data is displayed in the way I want, but I have a problem and that is that now I am trying to do that according to the type of graph that a user wants this to be changed, the problem is that the graph is changed but in case of having multiple graphs only the first graph is changed, the others continue with the graph by default, this is my template:
<select name="chartType" id="chartType" onchange="updateChart()" data-role="select">
<option value="pie">pie</option>
<option value="bar">Bar</option>
</select>
<canvas id="{{ project.slug }}" width="400" height="400"></canvas>
This is my script:
var faltante = 100 - {{ project.porcent }};
var data = {
labels: ['{{ project.name }}', 'Falta'],
datasets: [{
data: [{{ project.porcent }}, faltante],
backgroundColor: ['#252850', '#f44611']
}],
};
var ctx = document.getElementById('{{ project.slug }}').getContext('2d');
var myChart = new Chart(ctx, {
type: 'pie',
data: data
});
function updateChart() {
myChart.destroy();
myChart = new Chart(ctx, {
type: document.getElementById("chartType").value,
data: data
});
}
You can invoke your updateChart function with the selected chart type as follows:
onchange="updateChart(this.value)"
Everything could then be done inside the updateChart function.
destroy the chart if it already exists
create the new chart with the specified type
To make this work, you'll also have to explicitly invoke updateChart once with the initial chart type.
updateChart('pie');
Please take a look at below runnable code snippet and see how it works.
let myChart;
function updateChart(type) {
if (myChart) {
myChart.destroy();
}
myChart = new Chart('chart', {
type: type,
data: {
labels: ['A', 'B'],
datasets: [{
data: [3, 6],
backgroundColor: ['#252850', '#f44611']
}]
},
options: {
scales: {
yAxes: [{
display: type == 'bar',
ticks: {
beginAtZero: true
}
}]
}
}
});
}
updateChart('pie');
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<select name="chartType" id="chartType" onchange="updateChart(this.value)" data-role="select">
<option value="pie">Pie</option>
<option value="bar">Bar</option>
</select>
<canvas id="chart" height="100"></canvas>
I have been trying to make sense out of TimestampedGeoJson plugin from folium.
I want to draw lines that change their colour over time. At the moment, what I do is to completely redraw a line every time I need to change the color, with the massive overhead that entails.
Another issue is how to specify the time in the features. At the moment, I have this example:
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=10
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'red'
},
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:20:00'
],
'color': 'blue'
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'style': {
'color': d['color'],
'weight': d['weight'] if 'weight' in d else 5
}
}
}
for d in data
]
TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT1M', add_last_point=True).add_to(m)
m.save('dynamic4.html')
To me, the first date does not make any sense, but apparently it is required because otherwise the browser will not draw anything.
So:
a) How can I change the style without redrawing the lines?
b) What does the time mean? How can I specify a consistent time sequence?
I will first try to address your questions individually and I'm putting a full solution of what I would do in the end. essentially:
change the TimestampedGeoJson _template variable to change the style_function and it will enable it to make style dinamic
make sure you have one timestep per coordinates in the TimestampedGeoJson data
For avoiding confusion try to not overlap features or have features missing data of in a certain timestep
I believe in your scenario you only have one feature but change colors in different timesteps
Addressing your questions:
a) How can I change the style without redrawing the lines?
I don't think it's possible from folium itself, it would be necessary to pass a style_function to TimestampedGeoJson which is not even a parameter on the class init at the moment. It seems to be hard to do that because you would need to translate a python style_function, to a javascript style_function.
There would be a simple work around. Inside the class definition of TimestampedGeoJson it uses a _template variable as a string template of the javascript code so you could potentially adapt this template however you want but using javascript.
class TimestampedGeoJson(MacroElement):
.... hidding lines to save space
_template = Template("""
{% macro script(this, kwargs) %}
.... hidding lines to save space
style: function (feature) {
return feature.properties.style;
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
{% endmacro %}
""") # noqa
..... hidding lines to save space
So for changing the line color at every time step you could change this part of the template:
style: function (feature) {
return feature.properties.style;
},
by this : loop through an array of colors
style: function(feature) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[0]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
change it so that you update the color inside properties.style every timestep.
b) What does the time mean? How can I specify a consistent time sequence?
TimestampedGeoJson is using the library Leaflet.TimeDimension, so TimestampedGeoJson corresponds to L.TimeDimension.Layer.GeoJSON.
from that documentation you get
"coordTimes, times or linestringTimestamps: array of times that can be associated with a geometry (datestrings or ms). In the case of a LineString, it must have as many items as coordinates in the LineString."
so essentially to be consistent just make sure
1. for each feature the times size is the same as coordinates and
2. use a valid datestrings or ms format
3. if your dates are increasing by a constant set your period to that value
putting all together I mainly changed in your previous example:
1) added a _template variable with the new style_function and change the TimestampedGeoJson default template
2) changed the two features coordinates a bit to show that the two features you set were overlapping and some timesteps and at some timesteps just the first feature was defined and later just the second feature was defined so it get's confusing what is happening in each time step.
3) added a list of colors to loop through for each feature
from jinja2 import Template
_template = Template("""
{% macro script(this, kwargs) %}
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
_getDisplayDateFormat: function(date){
var newdate = new moment(date);
console.log(newdate)
return newdate.format("{{this.date_options}}");
}
});
{{this._parent.get_name()}}.timeDimension = L.timeDimension(
{
period: {{ this.period|tojson }},
}
);
var timeDimensionControl = new L.Control.TimeDimensionCustom(
{{ this.options|tojson }}
);
{{this._parent.get_name()}}.addControl(this.timeDimensionControl);
var geoJsonLayer = L.geoJson({{this.data}}, {
pointToLayer: function (feature, latLng) {
if (feature.properties.icon == 'marker') {
if(feature.properties.iconstyle){
return new L.Marker(latLng, {
icon: L.icon(feature.properties.iconstyle)});
}
//else
return new L.Marker(latLng);
}
if (feature.properties.icon == 'circle') {
if (feature.properties.iconstyle) {
return new L.circleMarker(latLng, feature.properties.iconstyle)
};
//else
return new L.circleMarker(latLng);
}
//else
return new L.Marker(latLng);
},
style: function(feature) {
lastIdx=feature.properties.colors.length-1
currIdx=feature.properties.colors.indexOf(feature.properties.color);
if(currIdx==lastIdx){
feature.properties.color = feature.properties.colors[currIdx+1]
}
else{
feature.properties.color =feature.properties.colors[currIdx+1]
}
return {color: feature.properties.color}
},
onEachFeature: function(feature, layer) {
if (feature.properties.popup) {
layer.bindPopup(feature.properties.popup);
}
}
})
var {{this.get_name()}} = L.timeDimension.layer.geoJson(
geoJsonLayer,
{
updateTimeDimension: true,
addlastPoint: {{ this.add_last_point|tojson }},
duration: {{ this.duration }},
}
).addTo({{this._parent.get_name()}});
{% endmacro %}
""")
import folium
from folium.plugins import TimestampedGeoJson
m = folium.Map(
location=[42.80491692, -4.62577249],
zoom_start=9
)
data = [
{
'coordinates': [
[-4.018876661, 43.11843382],
[-4.856537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'brown',
'colors':["black","orange","pink"],
},
{
'coordinates': [
[-4.058876661, 43.11843382],
[-4.936537491, 42.82202193],
],
'dates': [
'2017-06-02T00:00:00',
'2017-06-02T00:10:00'
],
'color': 'blue',
'colors':["red","yellow","green"],
},
]
features = [
{
'type': 'Feature',
'geometry': {
'type': 'LineString',
'coordinates': d['coordinates'],
},
'properties': {
'times': d['dates'],
'color': d["color"],
'colors':d["colors"]
}
}
for d in data
]
t=TimestampedGeoJson({
'type': 'FeatureCollection',
'features': features,
}, period='PT10H', add_last_point=True)
t._template=_template
t.add_to(m)
m.save('original.html')
I have an excel where each row has latitude and longitude data associated with some integer values. I would like to visualize this as pie charts on a map.
What I tried so far:
Google Data Studio: The only drawback here is that we cannot zoom on the map and the map is zoomed at country level but all my data is about a district in a city.
Python with Folium: Folium is a wrapper for Leaflet.js which is excellent for geographical visualization. However it lacks pie chart feature. I looked at integrating with Vega but that is only good for popups on Markers. This is not good, I want the pie charts on the map directly.
Can you recommend any free tool, or Python solution for this?
I come from a Python background mainly, but I welcome JS based solutions as well.
I think Highcharts can help you with what you are looking for. They are based on javascript.
An example for precisely what you are looking for -
https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/maps/demo/map-pies/
{
title: {
text: 'USA 2016 Presidential Election Results'
},
chart: {
animation: false // Disable animation, especially for zooming
},
colorAxis: {
dataClasses: [{
from: -1,
to: 0,
color: 'rgba(244,91,91,0.5)',
name: 'Republican'
}, {
from: 0,
to: 1,
color: 'rgba(124,181,236,0.5)',
name: 'Democrat'
}, {
from: 2,
to: 3,
name: 'Libertarian',
color: libColor
}, {
from: 3,
to: 4,
name: 'Green',
color: grnColor
}]
},
mapNavigation: {
enabled: true
},
// Limit zoom range
yAxis: {
minRange: 2300
},
tooltip: {
useHTML: true
},
// Default options for the pies
plotOptions: {
mappie: {
borderColor: 'rgba(255,255,255,0.4)',
borderWidth: 1,
tooltip: {
headerFormat: ''
}
}
},
series: [{
mapData: Highcharts.maps['countries/us/us-all'],
data: data,
name: 'States',
borderColor: '#FFF',
showInLegend: false,
joinBy: ['name', 'id'],
keys: ['id', 'demVotes', 'repVotes', 'libVotes', 'grnVotes',
'sumVotes', 'value', 'pieOffset'],
tooltip: {
headerFormat: '',
pointFormatter: function () {
var hoverVotes = this.hoverVotes; // Used by pie only
return '<b>' + this.id + ' votes</b><br/>' +
Highcharts.map([
['Democrats', this.demVotes, demColor],
['Republicans', this.repVotes, repColor],
['Libertarians', this.libVotes, libColor],
['Green', this.grnVotes, grnColor]
].sort(function (a, b) {
return b[1] - a[1]; // Sort tooltip by most votes
}), function (line) {
return '<span style="color:' + line[2] +
// Colorized bullet
'">\u25CF</span> ' +
// Party and votes
(line[0] === hoverVotes ? '<b>' : '') +
line[0] + ': ' +
Highcharts.numberFormat(line[1], 0) +
(line[0] === hoverVotes ? '</b>' : '') +
'<br/>';
}).join('') +
'<hr/>Total: ' + Highcharts.numberFormat(this.sumVotes, 0);
}
}
}, {
name: 'Separators',
type: 'mapline',
data: Highcharts.geojson(Highcharts.maps['countries/us/us-all'], 'mapline'),
color: '#707070',
showInLegend: false,
enableMouseTracking: false
}, {
name: 'Connectors',
type: 'mapline',
color: 'rgba(130, 130, 130, 0.5)',
zIndex: 5,
showInLegend: false,
enableMouseTracking: false
}]
}