Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion content/contribute/sponsoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,21 @@ according to the level of support.
| Social media | 4 times | 3 times | 2 times | 1 time | --- |
{{</ table >}}


### GRASS GIS Developer Summit 2025 Sponsorship

#### Why sponsor the GRASS GIS Developer Summit?

The [GRASS GIS Developer Summit](https://grasswiki.osgeo.org/wiki/GRASS_Developer_Summit_Raleigh_2025) is an event that brings together the global community of GRASS GIS developers and users. It is an opportunity to meet face-to-face, discuss the latest developments, and plan the future of the project. The event is organized by the GRASS Project Steering Committee ([PSC](https://trac.osgeo.org/grass/wiki/PSC)) and is open to everyone.

The GRASS GIS Developer Summit is a unique opportunity to support the development of GRASS GIS and to help shape the future of the project. By sponsoring the event, you will be supporting the work of the GRASS GIS developers and helping to ensure the long-term sustainability of the project.

{{< fundraising-chart >}}


### Our Sponsors and supporters

Many thanks to all our supporters and sponsors throughout the years!

Please refer to the [List of Sponsors](https://grasswiki.osgeo.org/wiki/Sponsors)
in the GRASS GIS Wiki for details.
in the GRASS GIS Wiki for details.
30 changes: 16 additions & 14 deletions themes/grass/layouts/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,26 @@
<link rel="stylesheet" href="{{ .Site.BaseURL }}plugins/fontawesome/css/all.min.css">
<link rel="stylesheet" href="{{ .Site.BaseURL }}plugins/fontawesome/css/fontawesome.min.css">
<style>
:root{
--primary-color:{{ .Site.Params.primaryColor }};
--secondary-color:{{ .Site.Params.secondaryColor }};
--text-color:{{ .Site.Params.textColor }};
--text-color-dark:{{ .Site.Params.textColorDark }};
--grass-color:{{ .Site.Params.grassColor }};
--grass-color-alt:{{ .Site.Params.grassColorAlt }};
--grass-color-light:{{ .Site.Params.grassColorLight }};
--grass-color-dark:{{ .Site.Params.grassColorDark }};
--grey-color:{{ .Site.Params.greyColor }};
--grey-color-light:{{ .Site.Params.greyColorLight }};
--grey-color-dark:{{ .Site.Params.greyColorDark }};
--white-color:{{ .Site.Params.whiteColor }};
:root{
--primary-color:{{ .Site.Params.primaryColor }};
--secondary-color:{{ .Site.Params.secondaryColor }};
--text-color:{{ .Site.Params.textColor }};
--text-color-dark:{{ .Site.Params.textColorDark }};
--grass-color:{{ .Site.Params.grassColor }};
--grass-color-alt:{{ .Site.Params.grassColorAlt }};
--grass-color-light:{{ .Site.Params.grassColorLight }};
--grass-color-dark:{{ .Site.Params.grassColorDark }};
--grey-color:{{ .Site.Params.greyColor }};
--grey-color-light:{{ .Site.Params.greyColorLight }};
--grey-color-dark:{{ .Site.Params.greyColorDark }};
--white-color:{{ .Site.Params.whiteColor }};
}
</style>{{ $styles := resources.Get "css/style.css" | minify}}
<link rel="stylesheet" href="{{ $styles.Permalink }}" integrity="{{ $styles.Data.Integrity }}" media="screen">
<script src="{{ .Site.BaseURL }}plugins/jquery/jquery-1.12.4.js"></script>
<script src="{{ .Site.BaseURL }}plugins/chartjs/chart.umd.js"></script>
<script type="module" src="{{ .Site.BaseURL }}plugins/chartjs/chartjs-adapter-date-fns.bundle.min.js"></script>
<script type="module" src="{{ .Site.BaseURL }}plugins/chartjs/chartjs-plugin-annotation.min.js"></script>
<link rel="apple-touch-icon" sizes="180x180" href="{{ .Site.BaseURL }}images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ .Site.BaseURL }}images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ .Site.BaseURL }}images/favicon/favicon-16x16.png">
Expand All @@ -56,7 +59,6 @@
<meta name="msapplication-config" content="{{ .Site.BaseURL }}images/favicon/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<script src="{{ .Site.BaseURL }}plugins/tracking/matomo.js"></script>

</head>
<body>
<noscript>Please enable javascript :)</noscript>
209 changes: 209 additions & 0 deletions themes/grass/layouts/shortcodes/fundraising-chart.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<!-- layouts/shortcodes/fundraising-chart.html -->

<div class="fundraising-chart-container">
<canvas id="fundraisingChart" width="800" height="400"></canvas>
</div>

<script>
// Ensure the script runs after the page content is loaded
document.addEventListener("DOMContentLoaded", function() {
// Define the collective slug and API endpoint
const hostSlug = 'osgeo'; // Host collective
const projectSlug = 'grass'; // Project collective
const collectiveSlug = 'grass';
const apiUrl = 'https://api.opencollective.com/graphql/v2/';
// const API_TOKEN = "";

// Function to fetch donation transactions
async function fetchDonations() {
// GraphQL query to fetch contribution transactions
const query = `
query($slug: String!) {
project(slug: $slug) {
transactions(type: CREDIT, limit: 1000) {
nodes {
amount {
value
currency
valueInCents
}
createdAt
fromAccount {
name
}
}
}
}
}
`;

const variables = { slug: collectiveSlug };


try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 'Authorization': `Bearer ${API_TOKEN}`, // Uncomment if authentication is required
},
body: JSON.stringify({ query, variables }),
});

const result = await response.json();
return result.data.project.transactions.nodes;
} catch (error) {
console.error('Error fetching donations:', error);
return [];
}
}

// Function to process and aggregate donation data
function processDonations(transactions) {
const donationMap = {};

transactions.forEach(tx => {
// Convert timestamp to YYYY-MM-DD format
const date = new Date(tx.createdAt).toISOString().split('T')[0];
// Convert amount from cents to dollars (assuming USD)
const amount = tx.amount.valueInCents / 100;

if (donationMap[date]) {
donationMap[date] += amount;
} else {
donationMap[date] = amount;
}
});

// Sort the dates
const sortedDates = Object.keys(donationMap).sort();
const aggregatedAmounts = sortedDates.map(date => donationMap[date]);

return { dates: sortedDates, amounts: aggregatedAmounts };
}

// Function to calculate total donations
function calculateTotalDonations(transactions) {
let total = 0;
transactions.forEach(tx => {
// Convert amount from cents to dollars (assuming USD)
const amount = tx.amount.valueInCents / 100;
total += amount;
});
return total;
}

// Initialize the Chart.js chart
let fundraisingChart;

async function initializeChart() {
const transactions = await fetchDonations();
const ctx = document.getElementById('fundraisingChart').getContext('2d');
const totalDonations = calculateTotalDonations(transactions);
const fundraisingGoal = 10000;
console.log(totalDonations);
fundraisingChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Donations'],
datasets: [{
label: 'Total Donations (USD)',
data: [totalDonations],
backgroundColor: totalDonations >= fundraisingGoal ? 'rgba(75, 192, 192, 0.6)' : 'rgba(8, 139, 54, 0.6)',
borderColor: totalDonations >= fundraisingGoal ? 'rgba(75, 192, 192, 1)' : 'rgba(8, 139, 54, 1)',
borderRadius: 50,
borderWidth: 1
}]
},
options: {
indexAxis: 'x', // Makes the bar horizontal
responsive: true,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `$${context.parsed.y.toLocaleString()}`;
}
}
},
annotation: {
annotations: {
goalLine: {
type: 'line',
yMin: fundraisingGoal,
yMax: fundraisingGoal,
borderColor: 'rgba(230, 11, 11, 1)',
borderWidth: 2,
label: {
enabled: true,
content: 'Goal: $10,000',
// content: ['This is my text', 'This is my text, second line'],
position: 'end',
backgroundColor: 'rgba(230, 11, 11, 0.8)',
color: '#000',
xAdjust: -10
}
},
label1: {
type: 'label',
yValue: fundraisingGoal - 0,
xValue: 0,
rotation: 0,
backgroundColor: 'rgba(245,245,245)',
content: ['Goal'],
font: {
size: 18
}
}
}
}
},
scales: {
y: {
beginAtZero: true,
max: fundraisingGoal + 1500, // Set maximum value to $10,000
title: {
display: true,
text: 'Amount (USD)'
},
ticks: {
callback: function(value) {
return `$${value}`;
}
}
},
x: {
display: false
}
}
}
});
}

// Function to update the chart with new data
async function updateChart() {
const transactions = await fetchDonations();
const data = processDonations(transactions);

fundraisingChart.data.labels = data.dates;
fundraisingChart.data.datasets[0].data = data.amounts;
fundraisingChart.update();
}

// Initialize the chart
initializeChart();

// Update the chart every 5 minutes (300,000 milliseconds)
setInterval(updateChart, 300000);
});
</script>

<style>
.fundraising-chart-container {
max-width: 700px;
margin: 40px auto;
}
</style>
Loading