Author: sebb
Date: Thu Jun 12 11:51:39 2025
New Revision: 1926372
URL: http://svn.apache.org/viewvc?rev=1926372&view=rev
Log:
Draft ECharts versions of existing charts
Added:
comdev/projects.apache.org/trunk/site/index_echarts.html (with props)
comdev/projects.apache.org/trunk/site/js/projects_echarts.js (with props)
Added: comdev/projects.apache.org/trunk/site/index_echarts.html
URL:
http://svn.apache.org/viewvc/comdev/projects.apache.org/trunk/site/index_echarts.html?rev=1926372&view=auto
==============================================================================
--- comdev/projects.apache.org/trunk/site/index_echarts.html (added)
+++ comdev/projects.apache.org/trunk/site/index_echarts.html Thu Jun 12
11:51:39 2025
@@ -0,0 +1,74 @@
+<!doctype html>
+<html lang=''>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="stylesheet" href="styles.css">
+ <script src="js/jquery.js" type="text/javascript"></script>
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+ <script src="js/script.js" type="text/javascript"></script>
+ <script src="js/echarts.min.js"></script>
+ <script src="js/projects_echarts.js"></script>
+
+ <title>Apache Projects and Committees Directory</title>
+</head>
+<body>
+
+<div id="logo"><h1><div style="padding-top: 30px;">Projects and Committees
Directory</div></h1></div>
+<div id='cssmenu'>
+<ul>
+ <li class='active'><a href='/'><span>Home</span></a></li>
+ <li><a href='committees.html'><span>Committees</span></a></li>
+ <li><a href='projects.html'><span>Projects</span></a></li>
+ <li><a href='releases.html'><span>Releases</span></a></li>
+ <li class='last'><a href='timelines.html'><span>Timelines</span></a></li>
+ <li style="background: none !important"><input type="text"
style="margin-top: 20px;" onkeypress="checkKeyPress(event, this);"
placeholder="Search..."/></li>
+ <li style="float: right;"><a href='about.html'><span>About</span></a></li>
+ <!--li style="background: none; float: right;"><a href="edit/"><img
title="Edit project data" style="vertical-align: middle; margin-top: -5px;
height: 24px; width: 24px;" src="images/edit.png"/></a></li-->
+</ul>
+</div>
+
+<div id="contents">
+ <h2>Welcome to the Apache Projects and Committees Directory</h2>
+ <p>This site is a partial (*) catalog of Apache Software Foundation <a
href="projects.html">projects</a> and their <a
href="committees.html">management committees</a>.
+ It is designed to help you find specific projects that meet your
interests and to gain a broader understanding of
+ the wide variety of work currently underway in the Apache community.
+ <br>
+ (*) Please note that some committees have <a
href="projects.html?category#no-tlp-doap">not yet provided data</a> for the
projects that they manage.
+ </p>
+ <!-- N.B. This ids in this section must agree with the code in projects.js
-->
+ <div id="details">
+ <h3 style='text-align: center;'>There are hundreds of open source
initiatives at the ASF:</h3>
+ <ul style='width: 400px; margin: 0 auto; font-size: 18px; color: #269;
font-weight: bold;'>
+ <li><span id="committees">TBA</span> <a
href='committees.html'>committees</a> managing <span id="projects">TBA</span>
<a href='projects.html'>projects</a></li>
+ <li><span id="podlings">TBA</span> incubating podlings</li>
+ </ul>
+ <!-- This div is cleared when loading is complete -->
+ <div id="loading">
+ <p style="text-align: center;">
+ Loading data, please wait...<br/>
+ <img src="images/loader.gif"/>
+ </p>
+ <p id="progress" style="text-align: center;"></p>
+ </div>
+ <noscript>
+ <h2>Notice!</h2>
+ <p>
+ This site relies heavily on JavaScript.
+ Please enable it or get a browser that supports it.
+ </p>
+ </noscript>
+ </div>
+</div>
+<div id="footer">
+ Managed by the <a href="https://community.apache.org">Apache Community
Development Project</a>.<br/>
+ Copyright© the Apache Software Foundation. Licensed under the <a
href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version
2.0</a><br/>
+ Apache® and the Apache feather logo are trademarks of The Apache
Software Foundation.
+</div>
+<script type="text/javascript">
+ google.load("visualization", "1", {packages:["corechart"]});
+ google.setOnLoadCallback(function() { preloadEverything(buildFrontPage)});
+</script>
+</body>
+</html>
Propchange: comdev/projects.apache.org/trunk/site/index_echarts.html
------------------------------------------------------------------------------
svn:eol-style = native
Added: comdev/projects.apache.org/trunk/site/js/projects_echarts.js
URL:
http://svn.apache.org/viewvc/comdev/projects.apache.org/trunk/site/js/projects_echarts.js?rev=1926372&view=auto
==============================================================================
--- comdev/projects.apache.org/trunk/site/js/projects_echarts.js (added)
+++ comdev/projects.apache.org/trunk/site/js/projects_echarts.js Thu Jun 12
11:51:39 2025
@@ -0,0 +1,2000 @@
+/*
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+*/
+
+// ----- Global hashes used throughout the script ------ \\
+
+var people = {}; // committer -> name lookups
+var unixgroups = {}; // unix (ldap) groups (project -> committers lookup)
+var committees = {}; // id -> committee info (chair, established, group,
homepage, id, name, reporting, shortdesc) (current committees)
+var committeesByName = {}; // name -> committee info
+var retiredCommittees = {}; // retired committees information: id -> committee
info (established, retired, homepage, id, name)
+var projects = {}; // Projects
+var podlings = {}; // current podlings
+var podlingsHistory = {}; // Podlings history (now graduated or retired)
+var repositories = {}; // source repositories id -> url
+
+// --------- Global helpers ----------- \\
+
+function includeJs(jsFilePath) {
+ var js = document.createElement("script");
+
+ js.type = "text/javascript";
+ js.src = jsFilePath;
+
+ document.head.appendChild(js);
+}
+
+includeJs("js/underscore-min.js");
+
+function GetAsyncJSON(theUrl, xstate, callback) {
+ var xmlHttp = null;
+ if (window.XMLHttpRequest) {
+ xmlHttp = new XMLHttpRequest();
+ } else {
+ xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
+ }
+ xmlHttp.open("GET", theUrl, true);
+ xmlHttp.send(null);
+ xmlHttp.onreadystatechange = function(state) {
+ if (xmlHttp.readyState == 4 && xmlHttp.status == 200 || xmlHttp.status
== 404) {
+ if (callback) {
+ if (xmlHttp.status == 404) {
+ callback({}, xstate);
+ } else {
+ callback(JSON.parse(xmlHttp.responseText), xstate);
+ }
+ }
+ }
+ }
+}
+
+var urlErrors = []
+var fetchCount = 0;
+// Fetch an array of URLs, each with their description and own callback plus a
final callback
+// Used to fetch everything before rendering a page that relies on multiple
JSON sources.
+function GetAsyncJSONArray(urls, finalCallback) {
+ var obj = document.getElementById('progress');
+ if (fetchCount == 0 ) {
+ fetchCount = urls.length;
+ }
+
+ if (urls.length > 0) {
+ var a = urls.shift();
+ var URL = a[0];
+ var desc = a[1];
+ var cb = a[2];
+ var xmlHttp = null;
+ if (window.XMLHttpRequest) {
+ xmlHttp = new XMLHttpRequest();
+ } else {
+ xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
+ }
+
+ if (obj) { obj.innerHTML = "loading file #" + ( fetchCount -
urls.length ) + " / " + fetchCount + "<br>" + desc }
+
+ xmlHttp.open("GET", URL, true);
+ xmlHttp.onreadystatechange = function(state) {
+ if (xmlHttp.readyState == 4) {
+ if (cb) {
+ if (xmlHttp.status == 200) {
+ cb(JSON.parse(xmlHttp.responseText));
+ } else {
+ urlErrors.push(URL)
+ cb({});
+ }
+ }
+ GetAsyncJSONArray(urls, finalCallback);
+ }
+ }
+ xmlHttp.send(null);
+ }
+ else {
+ if (obj) { obj.innerHTML = "building page content..." }
+ finalCallback();
+ }
+}
+
+// See project_editor.js (not currently used)
+
+// ------------ Project information page ------------\\
+
+function linkCommitterIndex(cid) {
+ var fullname = people[cid];
+ var cl = isMember(cid) ? "member" : "committer";
+ return "<a class='" + cl + "' title='" + cid + "'
href='https://home.apache.org/phonebook.html?uid=" + cid + "' target='_blank'>"
+ fullname + "</a>";
+}
+
+function appendElementWithInnerHTML(obj,type,html) {
+ var child = document.createElement(type);
+ child.innerHTML = html;
+ obj.appendChild(child);
+ return child;
+}
+
+function appendLiInnerHTML(ul,html) {
+ return appendElementWithInnerHTML(ul,'li',html);
+}
+
+function projectIdToUnixGroup(projectId, pmcName) {
+ // Rerig the unix name and committee id
+ var unixgroup = projectId.split("-")[0];
+ /*
+ Temp hack for podling names. TODO need to sort out generated names
+ */
+ if (projectId.indexOf("incubator-") === 0) {
+ unixgroup = projectId.split("-")[1]
+ }
+ // special cases
+ if (unixgroup === "empire") unixgroup = "empire-db";
+ if (unixgroup === "community") unixgroup = "comdev";
+ if (pmcName === "attic") {
+ unixgroup = "attic";
+ }
+ return unixgroup;
+}
+
+function renderProjectPage(project, projectId) {
+ var obj = document.getElementById('contents');
+
+ if ((!project || !project.name) && projects[projectId]) {
+ // no DOAP file but known project: podling (loaded from podlings.json)
+ project = projects[projectId];
+ }
+ if (!project || !project.name) {
+ obj.innerHTML = "<h2>Sorry, I don't have any information available
about this project</h2>";
+ return;
+ }
+
+ fixProjectName(project);
+ var isIncubating = project && (project.podling || (project.pmc ==
'incubator'));
+
+ var unixgroup = projectIdToUnixGroup(projectId, project && project.pmc);
+
+ var committeeId = isIncubating ? 'incubator' : unixgroup;
+ if (!committees[unixgroup]) {
+ // at least one committee has a unix group that is different from
committee id: webservices (group=ws), see parsecommittees.py#group_ids
+ // search instead of hard-coding the currently known case
+ for (p in committees) {
+ if (committees[p].group == unixgroup) {
+ committeeId = p;
+ break;
+ }
+ }
+ }
+ var committee = committees[committeeId];
+ if (!committee) {
+ obj.innerHTML = "<h2>Cannot find the PMC '" + committeeId + "' for
this project. Check the DOAP is correct.</h2>";
+ return;
+ }
+
+ // Start by splitting the name, thus fetching the root name of the
project, and not the sub-project.
+ var description = "";
+ if (project) {
+ if (!_.isEmpty(project.description)) {
+ description = project.description;
+ } else if (!_.isEmpty(project.shortdesc)) {
+ description = project.shortdesc;
+ } else if (!_.isEmpty(committee.shortdesc)) {
+ description = committee.shortdesc;
+ } else {
+ description = "No description available";
+ }
+ }
+
+ // Title + description
+ obj.innerHTML = "<h1>" + project.name + " <font size='-1'>(a project
managed by the <a href='committee.html?" + committeeId + "'>" + committee.name
+ " Committee</a>)</font></h1>";
+
+ // project description
+
appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a)
{ return "<p>"+a+"</p>"}));
+
+ var ul = document.createElement('ul');
+
+ // Base data
+ appendElementWithInnerHTML(obj,'h4',"Project base data:");
+
+ if (project.description && project.shortdesc) {
+ appendLiInnerHTML(ul, "<b>Short description:</b> " +
project.shortdesc);
+ }
+
+ // Categories
+ if (project.category) {
+ var arr = project.category.split(/,\s*/);
+ var pls = "";
+ for (i in arr) {
+ var cat = arr[i];
+ // categories are downcased so fix up the anchor
+ pls += "<a href='projects.html?category#" + cat.toLowerCase() +
"'>" + cat + "</a> ";
+ }
+ appendLiInnerHTML(ul, "<b>Category:</b> " + pls);
+ }
+
+ // Website
+ if (project.homepage) {
+ appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + project.homepage +
"' target='_blank'>" + project.homepage + "</a>");
+ }
+ if (isIncubating) {
+ appendLiInnerHTML(ul, "<b>Project status:</b> <span
class='ppodling'>Incubating</span>");
+ } else if (committeeId != 'attic') {
+ appendLiInnerHTML(ul, "<b>Project status:</b> <span
class='pactive'>Active</span>");
+ } else {
+ appendLiInnerHTML(ul, "<b>Project status:</b> <span
class='pretired'>Retired</span>");
+ }
+
+ // Committers
+ if (isIncubating && unixgroups[unixgroup]) {
+ var commitl = [];
+ var commitgroup = unixgroups[unixgroup];
+ for (i in commitgroup) {
+ commitl.push(linkCommitterIndex(commitgroup[i]));
+ }
+ appendLiInnerHTML(ul, "<b>Committers (" + commitgroup.length + "):</b>
<blockquote>" + commitl.join(", ") + "</blockquote>");
+ }
+
+ if (project.implements) {
+ var stds = document.createElement('ul');
+ var impl;
+ for (impl in project.implements) {
+ impl = project.implements[impl];
+ var std = "";
+ if (impl.body) {
+ std += impl.body + ' ';
+ }
+ if (impl.id) {
+ std += "<a href='" + impl.url + "'>" + impl.id + "</a>: " +
impl.title;
+ } else {
+ std += "<a href='" + impl.url + "'>" + impl.title + "</a>";
+ }
+ appendLiInnerHTML(stds, std);
+ }
+ appendLiInnerHTML(ul, "<b>Implemented
standards</b>").appendChild(stds);
+ }
+
+ // doap/rdf
+ if (project.doap) {
+ appendLiInnerHTML(ul, "<b>Project data file:</b> <a href='" +
project.doap + "' target='_blank'>DOAP RDF Source</a> (<a href='json/projects/"
+ projectId + ".json'>generated json</a>)");
+ } else {
+ appendLiInnerHTML(ul, "<b>Project data file:</b> no <a
href='https://projects.apache.org/create.html'>DOAP file</a> available");
+ }
+ // maintainer
+ if (project.maintainer) {
+ var mt;
+ var maintainers = "";
+ for (mt in project.maintainer) {
+ mt = project.maintainer[mt];
+ if (mt.mbox) {
+ var id = mt.mbox;
+ id = id.substr(id.indexOf(':') + 1);
+ id = id.substr(0, id.indexOf('@'));
+ if (people[id]) {
+ maintainers += linkCommitterIndex(id) + " ";
+ } else {
+ maintainers += "<a href='" + mt.mbox + "'>" + mt.name +
"</a> ";
+ }
+ } else {
+ maintainers += mt.name + " ";
+ }
+ }
+ appendLiInnerHTML(ul, "<b>Project data maintainer(s):</b> " +
maintainers);
+ }
+
+ obj.appendChild(ul);
+
+ // Code data
+ appendElementWithInnerHTML(obj,'h4',"Development:");
+ ul = document.createElement('ul');
+
+ if (project['programming-language']) {
+ var pl = project['programming-language'];
+ var arr = pl.split(/,\s*/);
+ var pls = "";
+ for (i in arr) {
+ pls += "<a href='projects.html?language#" + arr[i] + "'>" + arr[i]
+ "</a> ";
+ }
+ appendLiInnerHTML(ul, "<b>Programming language:</b> " + pls);
+ }
+
+ if (project['bug-database']) {
+ var bd = project['bug-database'];
+ var arr = bd.split(/,\s*/);
+ var bds = "";
+ for (i in arr) {
+ bds += "<a href='" + arr[i] + "'>" + arr[i] + "</a> ";
+ }
+ appendLiInnerHTML(ul, "<b>Bug-tracking:</b> " + bds);
+ }
+
+ if (project['mailing-list']) {
+ var ml = project['mailing-list'];
+ var xml = ml;
+ // email instead of link?
+ if (ml.match(/@/)) {
+ xml = "mailto:" + ml;
+ }
+ appendLiInnerHTML(ul, "<b>Mailing list(s):</b> <a href='" + xml + "'>"
+ ml + "</a>");
+ }
+
+ // repositories
+ if (project.repository) {
+ var r;
+ for (r in project.repository) {
+ r = project.repository[r];
+ if (r.indexOf("svn") > 0) {
+ appendLiInnerHTML(ul, "<b>Subversion repository:</b> <a
target=*_blank' href='" + r + "'>" + r + "</a>");
+ } else if (r.indexOf("git") > 0) {
+ appendLiInnerHTML(ul, "<b>Git repository:</b> <a
target=*_blank' href='" + r + "'>" + r + "</a>");
+ } else {
+ appendLiInnerHTML(ul, "<b>Repository:</b> <a target=*_blank'
href='" + r + "'>" + r + "</a>");
+ }
+ }
+ }
+
+ obj.appendChild(ul);
+
+ // releases
+ appendElementWithInnerHTML(obj,'h4',"Releases <font size='-2'>(from
DOAP)</font>:");
+ ul = document.createElement('ul');
+ if (project['download-page']) {
+ appendLiInnerHTML(ul, "<b>Download:</b> <a href='" +
project['download-page'] + "' target='_blank'>" + project['download-page'] +
"</a>");
+ }
+ if (project.release) {
+ project.release.sort(function(a,b){// reverse date order (most recent
first)
+ var ac = a.created ? a.created : '1970-01-01';
+ var bc = b.created ? b.created : '1970-01-01';
+ if(ac < bc) return 1;
+ if(ac > bc) return -1;
+ return 0;});
+ var r;
+ for (r in project.release) {
+ r = project.release[r];
+ var html = "<b>" + (r.revision ? r.revision : r.version) + "</b>";
+ html += " (" + (r.created ? r.created : 'unknown') + ")";
+ appendLiInnerHTML(ul, html + ": " + r.name);
+ }
+ }
+ obj.appendChild(ul);
+}
+
+
+function buildProjectPage() {
+ var projectId = document.location.search.substr(1);
+ GetAsyncJSON("json/projects/" + projectId + ".json?" + Math.random(),
projectId, renderProjectPage)
+}
+
+// extract committee name from repo name
+function repoToCommittee(reponame) {
+ if (reponame.startsWith('empire-db')) {
+ return 'empire-db';
+ }
+ return reponame.split('-')[0];
+}
+
+function renderCommitteePage(committeeId) {
+ var obj = document.getElementById('contents');
+
+ if (!committees[committeeId]) {
+ obj.innerHTML = "<h2>Sorry, I don't have any information available
about this committee</h2>";
+ return;
+ }
+
+ var unixgroup = committeeId; // there are probably a few exceptions...
+ var committee = committees[committeeId];
+
+ obj.innerHTML = "<h1>" + committee.name + " Committee <font
size='-2'>(also called PMC or <a href='https://www.apache.org/#projects-list'
target='_blank'>Top Level Project</a>)</font></h1>";
+
+ var description;
+ if (!_.isEmpty(committee.shortdesc)) {
+ description = committee.shortdesc;
+ } else {
+ description = "Missing from https://whimsy.apache.org/public/
committee-info.json";
+ }
+
+ appendElementWithInnerHTML(obj, 'h4', "Description <font size='-2'>(from
<a href='https://whimsy.apache.org/public/'
target='_blank'>committee-info</a>)</a>:");
+
+
appendElementWithInnerHTML(obj,'p',description.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a)
{ return "<p>"+a+"</p>"}));
+
+ appendElementWithInnerHTML(obj, 'h4', "Charter <font size='-2'>(from PMC
data file)</a>:");
+
+ var charter;
+ if (!_.isEmpty(committee.charter)) {
+ charter = committee.charter;
+ } else {
+ charter = "Missing";
+ }
+
+
appendElementWithInnerHTML(obj,'p',charter.replace(/([^\r\n]+)\r?\n\r?\n/g,function(a)
{ return "<p>"+a+"</p>"}));
+
+ var ul = document.createElement('ul');
+
+ appendElementWithInnerHTML(obj, 'h4', "Committee data:");
+
+ appendLiInnerHTML(ul, "<b>Website:</b> <a href='" + committee.homepage +
"' target='_blank'>" + committee.homepage + "</a>");
+
+ appendLiInnerHTML(ul, "<b>Committee established:</b> " +
committee.established);
+
+ // VP
+ appendLiInnerHTML(ul, "<b>PMC Chair:</b> " +
linkCommitterIndex(committee.chair));
+
+ // Reporting cycle
+ var cycles = ["every month", "January, April, July, October", "February,
May, August, November", "March, June, September, December"];
+ var minutes = committee.name.substr("Apache ".length).replace(/ /g, '_');
+ // does not work for APR and Logging Services currently
+ if (committeeId == 'apr') {
+ minutes = 'Apr';
+ } else if (committeeId == 'logging') {
+ minutes = 'Logging';
+ }
+ appendLiInnerHTML(ul, "<b>Reporting cycle:</b> " +
cycles[committee.reporting] + ", see <a
href='https://whimsy.apache.org/board/minutes/" + minutes + ".html'
target='_blank'>minutes</a>");
+
+ // PMC
+ if (committee.roster) { // check we have the data
+ var pmcl = [];
+ for (i in committee.roster) {
+ pmcl.push(linkCommitterIndex(i));
+ }
+ if (pmcl.length > 0) {
+ appendLiInnerHTML(ul, "<b>PMC Roster <font size='-1'>(from <a
href='https://whimsy.apache.org/public/' target='_blank'>committee-info</a>;
updated daily)</font> (" + pmcl.length + "):</b> <blockquote>" +
pmcl.join(", ") + "</blockquote>");
+ } else {
+ appendLiInnerHTML(ul, "<b>PMC Roster not found in
committee-info.txt (check that Section 3 has been updated)</b>");
+ }
+ }
+
+ // Committers
+ if (unixgroups[unixgroup]) {
+ var commitl = [];
+ var commitgroup = unixgroups[unixgroup];
+ for (i in commitgroup) {
+ commitl.push(linkCommitterIndex(commitgroup[i]));
+ }
+ appendLiInnerHTML(ul, "<b>Committers; updated daily (" +
commitgroup.length + "):</b> <blockquote>" + commitl.join(", ") +
"</blockquote>");
+ }
+
+ // rdf
+ if (committee.rdf) {
+ appendLiInnerHTML(ul, "<b>PMC data file:</b> <a href='" +
committee.rdf + "' target='_blank'>RDF Source</a>");
+ }
+
+ obj.appendChild(ul);
+
+ var subprojects = [];
+ for (p in projects) {
+ if (projects[p].pmc == committeeId) {
+ subprojects.push(p);
+ }
+ }
+ if (subprojects.length == 0) {
+ if (committeeId != 'labs') {
+ // if a committee did not declare any project (DOAP), consider
there is a default one with the id of the committee
+ // only Labs doesn't manage projects
+ subprojects.push({ 'id': committeeId, 'name': committee.name,
'pmc': committeeId });
+ }
+ } else {
+ appendElementWithInnerHTML(obj, 'h4', "Projects managed by this
Committee:");
+
+ ul = document.createElement('ul');
+ for (var p in subprojects.sort()) {
+ p = subprojects[p];
+ appendLiInnerHTML(ul, projectLink(p));
+ }
+ obj.appendChild(ul);
+ }
+
+ var repos = [];
+ for (var r in repositories) {
+ if (committeeId == repoToCommittee(r)) {
+ repos.push(r);
+ }
+ }
+ if (repos.length > 0) {
+ appendElementWithInnerHTML(obj, 'h4',
+ "Source repositories managed by this Committee <font size='-2'>" +
+ "(from <a href='https://gitbox.apache.org/repositories.json'>ASF
Git repos</a>" +
+ " and <a href='https://svn.apache.org/repos/asf/'>ASF SVN
repos</a>)</font>:");
+
+ ul = document.createElement('ul');
+ for (var r in repos.sort()) {
+ r = repos[r];
+ var url = repositories[r];
+ appendLiInnerHTML(ul, r + ": <a href='" + url + "'>" + url +
"</a>");
+ }
+ obj.appendChild(ul);
+ }
+}
+
+function buildCommitteePage() {
+ var committeeId = document.location.search.substr(1);
+ renderCommitteePage(committeeId);
+}
+
+
+// ------------ Projects listing ------------\\
+
+function camelCase(str) {
+ return str.replace(/^([a-z])(.+)$/, function(c,a,b) { return
a.toUpperCase() + b.toLowerCase() } );
+}
+
+function committeeIcon() {
+ return "<img src='images/committee.png' title='Committee'
style='vertical-align: middle; padding: 2px;'/> ";
+}
+
+function projectIcon() {
+ return "<img src='images/project.png' title='Project'
style='vertical-align: middle; padding: 2px;'/> "
+}
+
+function committeeLink(id) {
+ var committee = committees[id];
+ return "<a href='committee.html?" + id + "'>" + committee.name + "</a> - "
+ committee.shortdesc;
+}
+
+function projectLink(id) {
+ var project = projects[id];
+ if (!project) {
+ // not project id: perhaps committee id
+ project = committees[id];
+ }
+ return "<a href='project.html?" + id + "'>" + project.name + "</a>";
+}
+
+function isMember(id) {
+ return _(unixgroups['member']).indexOf(id) >= 0;
+}
+
+function sortProjects() {
+ var projectsSortedX = [];
+ var projectsSorted = [];
+ for (i in projects) {
+ projectsSortedX.push([i, projects[i].name.toLowerCase()]);
+ }
+ // compare names (already lower-cased)
+ projectsSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] < b[1]
? -1 : 0 })
+ for (i in projectsSortedX) {
+ projectsSorted.push(projectsSortedX[i][0]);
+ }
+ return projectsSorted;
+}
+
+function renderProjectsByName() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+ var projectsSorted = sortProjects();
+
+ // Project list
+ var ul = document.createElement('ul');
+ for (var i in projectsSorted) {
+ var project = projectsSorted[i];
+ appendLiInnerHTML(ul, projectIcon(projects[project].name) +
projectLink(project));
+ }
+ obj.appendChild(ul);
+}
+
+function renderProjectsByLanguage() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+ var projectsSorted = sortProjects();
+
+ // Compile Language array
+ var lingos = [];
+ var lcount = {};
+ var i;
+ var x;
+ for (i in projects) {
+ if (projects[i]['programming-language']) {
+ var a = projects[i]['programming-language'].split(/,\s*/);
+ for (x in a) {
+ a[x] = camelCase(a[x]);
+ if (lingos.indexOf(a[x]) < 0) {
+ lingos.push(a[x]);
+ lcount[a[x]] = 0;
+ }
+ lcount[a[x]]++;
+ }
+ }
+ }
+
+ // Construct language list
+ lingos.sort();
+ var toc = document.createElement('p');
+ var toch = document.createElement('h3');
+ toch.textContent = 'TOC';
+ toc.appendChild(toch);
+ var ul = document.createElement('ul');
+
+ var l;
+ for (l in lingos) {
+ var lang = lingos[l];
+ var tocitem = document.createElement('a');
+ tocitem.href="#" + lang;
+ tocitem.innerHTML=lang;
+ if (l > 0) { // divider
+ toc.appendChild(document.createTextNode(', '));
+ }
+ toc.appendChild(tocitem);
+ var li = document.createElement('li');
+ li.innerHTML = "<h3><a id='" + lang + "'>" + lang + " (" +
lcount[lang] + ")</a>"+linkToHere(lang)+":</h3>";
+ var cul = document.createElement('ul');
+ for (i in projectsSorted) {
+ i = projectsSorted[i];
+ if (projects[i]['programming-language']) {
+ var a = projects[i]['programming-language'].split(/,\s*/);
+ for (x in a) {
+ // Use same capitalisation as the language list
+ if (camelCase(a[x]) == lang) {
+ appendLiInnerHTML(cul, projectIcon(projects[i].name) +
projectLink(i));
+ }
+ }
+ }
+ }
+ li.appendChild(cul);
+ ul.appendChild(li);
+ }
+
+ obj.appendChild(toc);
+ obj.appendChild(ul);
+
+ if (location.hash.length > 1) {
+ setTimeout(function() { location.href = location.href;}, 250);
+ }
+}
+
+function renderProjectsByCategory() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+ var projectsSorted = sortProjects();
+
+ var cats = [];
+ var ccount = {};
+ var i;
+ for (i in projects) {
+ if (projects[i].category) {
+ var a = projects[i].category.split(/,\s*/);
+ var x;
+ for (x in a) {
+ x = a[x].toLowerCase(); // must agree with downcase below
+ if (cats.indexOf(x) < 0) {
+ cats.push(x);
+ ccount[x] = 0;
+ }
+ ccount[x]++;
+ }
+ }
+ }
+ cats.sort();
+
+ // Construct category list
+ var toc = document.createElement('p');
+ var toch = document.createElement('h3');
+ toch.textContent = 'TOC';
+ toc.appendChild(toch);
+ var ul = document.createElement('ul');
+
+ var l;
+ for (l in cats) {
+ var cat = cats[l];
+ var tocitem = document.createElement('a');
+ tocitem.href="#" + cat;
+ tocitem.innerHTML=cat;
+ if (l > 0) { // divider
+ toc.appendChild(document.createTextNode(', '));
+ }
+ toc.appendChild(tocitem);
+ var li = document.createElement('li');
+ li.innerHTML = "<h3><a id='" + cat + "'>" + cat + " (" + ccount[cat] +
")</a>"+linkToHere(cat)+":</h3>";
+ var cul = document.createElement('ul');
+ for (i in projectsSorted) {
+ i = projectsSorted[i];
+ var project = projects[i];
+ if (project.category) {
+ var a = project.category.split(/,\s*/);
+ for (x in a) {
+ x = a[x].toLowerCase(); // must agree with downcase above
+ if (x == cat) {
+ appendLiInnerHTML(cul, projectIcon(project.name) +
projectLink(i));
+ }
+ }
+ }
+ }
+ li.appendChild(cul);
+ ul.appendChild(li);
+ }
+
+ obj.appendChild(toc);
+ obj.appendChild(ul);
+
+ if (location.hash.length > 1) {
+ setTimeout(function() { location.href = location.href;}, 250);
+ }
+}
+
+function renderProjectsByNumber() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+ var projectsSorted = sortProjects();
+
+ var lens = [];
+ var lcount = {};
+ for (projectId in projects) {
+ let unixGroup = projectIdToUnixGroup(projectId);
+ if (unixgroups[unixGroup] && projectId !== 'incubator') {
+ let len = unixgroups[unixGroup].length;
+ if (lens.indexOf(len) < 0) {
+ lens.push(len);
+ lcount[len] = 0;
+ }
+ lcount[len]++;
+ }
+ }
+ lens.sort(function(a,b) { return b - a });
+
+ // Construct date list
+ var ul = document.createElement('ul');
+
+ for (l in lens) {
+ var len = lens[l];
+ var projectId;
+ for (projectId in projectsSorted) {
+ projectId = projectsSorted[projectId];
+ let unixGroup = projectIdToUnixGroup(projectId);
+ if (unixgroups[unixGroup]) {
+ var xlen = unixgroups[unixGroup].length;
+ if (xlen == len) {
+ var html = projectIcon(projects[projectId].name) +
projectLink(projectId) + ": " + len + " committers";
+ if (unixgroups[unixGroup+'-pmc']) {
+ html += ", " + unixgroups[unixGroup+'-pmc'].length + "
PMC members";
+ }
+ appendLiInnerHTML(ul,html);
+ }
+ }
+ }
+ }
+
+ obj.appendChild(ul);
+}
+
+function renderProjectsByCommittee() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+ var projectsSorted = sortProjects();
+
+ var dcount = {};
+ for (var committee in committees) {
+ dcount[committee] = 0;
+ }
+ for (var project in projects) {
+ project = projects[project];
+ if (committees[project.pmc]) {
+ dcount[project.pmc]++;
+ }
+ }
+
+ // Construct pmc list
+ var ul = document.createElement('ul');
+
+ var lpmc;
+ for (lpmc in committees) {
+ var c = dcount[lpmc];
+ var li = document.createElement('li');
+ var cul = document.createElement('ul');
+ if (c == 0 && lpmc != 'labs') {
+ appendLiInnerHTML(cul, projectIcon(committees[lpmc].name) + "<a
href='project.html?" + lpmc + "'>" + committees[lpmc].name + "</a>");
+ c = 1;
+ } else {
+ var i;
+ for (i in projectsSorted) {
+ i = projectsSorted[i];
+ var project = projects[i];
+ if (committees[project.pmc]) {
+ var xlpmc = project.pmc;
+ if (xlpmc == lpmc) {
+ if (project.doap) {
+ appendLiInnerHTML(cul, projectIcon(project.name) +
projectLink(i));
+ } else {
+ c=0;
+ if (xlpmc == 'incubator') {
+ appendLiInnerHTML(cul, "<b>"+ project.name +
": please <a href='https://projects.apache.org/create.html'>create a DOAP</a>
file</b>");
+ } else {
+ appendLiInnerHTML(cul, "<b>Please <a
href='https://projects.apache.org/create.html'>create a DOAP</a> file</b>");
+ }
+ }
+ }
+ }
+ }
+ }
+ li.innerHTML = "<h3>" + committeeIcon() + "<a id='" + lpmc + "'
href='committee.html?"+ lpmc + "'>" + committees[lpmc].name + " Committee</a>"
+ (c!=1?(" (" + c + ")"):"") + (c>0?":": "") + "</h3>";
+ li.appendChild(cul);
+ ul.appendChild(li);
+ }
+
+ obj.appendChild(ul);
+
+ if (location.hash.length > 1) {
+ setTimeout(function() { location.href = location.href;}, 250);
+ }
+}
+
+function buildProjectsList() {
+ var cat = document.location.search.substr(1);
+ var types = {
+ 'name': [ 'name', renderProjectsByName ],
+ 'language': [ 'language', renderProjectsByLanguage ],
+ 'category': [ 'category', renderProjectsByCategory ],
+ 'number': [ 'number of committers', renderProjectsByNumber ],
+ 'pmc': [ 'Committee', renderProjectsByCommittee ],
+ 'committee': [ 'Committee', renderProjectsByCommittee ]
+ }
+ if ((cat.length == 0) || !(cat in types)) {
+ cat = "name";
+ }
+
+ var type = types[cat];
+ var obj = document.getElementById('title');
+ obj.innerHTML = "<h1>Projects by " + type[0] + ":</h1>"
+
+ preloadEverything(type[1]);
+}
+
+function sortCommittees() {
+ var committeesSortedX = [];
+ var committeesSorted = [];
+ for (i in committees) {
+ committeesSortedX.push([i, committees[i].name.toLowerCase()]);
+ }
+ // compare names (already lower-cased)
+ committeesSortedX.sort(function(a,b) { return a[1] > b[1] ? 1 : a[1] <
b[1] ? -1 : 0 })
+ for (i in committeesSortedX) {
+ committeesSorted.push(committeesSortedX[i][0]);
+ }
+ return committeesSorted;
+}
+
+function renderCommitteesByName() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+ var committeesSorted = sortCommittees();
+
+ // Committee list
+ var ul = document.createElement('ul');
+ var i;
+ for (i in committeesSorted) {
+ appendLiInnerHTML(ul, committeeIcon() +
committeeLink(committeesSorted[i]));
+ }
+ obj.appendChild(ul);
+}
+
+function renderCommitteesByDate() {
+ var obj = document.getElementById('list');
+ obj.innerHTML = "";
+
+ var dates = [];
+ var dcount = {};
+ var i;
+ for (i in committees) {
+ var date = committees[i].established;
+ if (dates.indexOf(date) < 0) {
+ dates.push(date);
+ dcount[date] = 0;
+ }
+ dcount[date]++;
+ }
+ dates.sort()
+
+ // Construct date list
+ var ul = document.createElement('ul');
+
+ var l;
+ for (l in dates) {
+ var date = dates[l];
+ var li = document.createElement('li');
+ li.innerHTML = "<h3><a id='" + date + "'>" + date + " (" +
dcount[date] + ")</a>:</h3>";
+ var cul = document.createElement('ul');
+ var i;
+ for (i in committeesByName) {
+ i = committeesByName[i];
+ if (i.established == date) {
+ appendLiInnerHTML(cul, committeeIcon() + committeeLink(i.id));
+ }
+ }
+ li.appendChild(cul);
+ ul.appendChild(li);
+ }
+
+ obj.appendChild(ul);
+
+ if (location.hash.length > 1) {
+ setTimeout(function() { location.href = location.href;}, 250);
+ }
+}
+
+function buildCommitteesList() {
+ var cat = document.location.search.substr(1);
+ var types = {
+ 'name': [ 'name', renderCommitteesByName ],
+ 'date': [ 'founding date', renderCommitteesByDate ]
+ }
+ if ((cat.length == 0) || !(cat in types)) {
+ cat = "name";
+ }
+
+ var type = types[cat];
+ var obj = document.getElementById('title');
+ obj.innerHTML = "<h1>Committees by " + type[0] + ":</h1>"
+
+ preloadEverything(type[1]);
+}
+
+
+// Rendering project list using DataTables instead of the usual stuff:
+function buildProjectListAsTable(json) {
+ var arr = [];
+ for (p in projects) {
+ var project = projects[p];
+
+ // Get name of PMC
+ var pmc = committees[project.pmc] ? committees[project.pmc].name :
"Unknown";
+
+ // Get project type
+ var type = "Sub-Project";
+ var shortp = p.split("-")[0];
+ if (unixgroups[shortp]) {
+ type = "TLP";
+ if ((!committeesByName[project.name] && committees[project.pmc])
|| project.name.match(/incubating/i)) {
+ type = "Sub-project";
+ }
+ } else {
+ type = "Retired";
+ }
+
+ if (project.podling || project.name.match(/incubating/i)) {
+ type = "Podling";
+ pmc = "Apache Incubator";
+ }
+
+ // Programming language
+ var pl = project['programming-language'] ?
project['programming-language'] : "Unknown";
+
+ // Shove the result into a row
+ arr.push([ p, project.name, type, pmc, pl, project.category])
+ }
+
+ // Construct the data table
+ $('#contents').html( '<table cellpadding="0" cellspacing="0" border="0"
class="display" id="projectlist"></table>' );
+
+ $('#projectlist').dataTable( {
+ "data": arr,
+ "columns": [
+ { "title": "ID", "visible": false },
+ { "title": "Name" },
+ { "title": "Type" },
+ { "title": "PMC" },
+ { "title": "Programming Language(s)" },
+ { "title": "Category" }
+ ],
+ "fnRowCallback": function( nRow, aData, iDisplayIndex,
iDisplayIndexFull) {
+ jQuery(nRow).attr('id', aData[0]);
+ jQuery(nRow).css("cursor", "pointer");
+ return nRow;
+ }
+ } );
+
+ $('#projectlist tbody').on('click', 'tr', function () {
+ var name = $(this).attr('id');
+ location.href = "project.html?" + name
+ } );
+}
+
+
+function isCommittee(name) {
+ return committeesByName[name];
+}
+
+// ------------ Front page rendering ------------\\
+
+function update_id(id, val) {
+ var obj = document.getElementById(id);
+ if (obj) {
+ obj.innerHTML = val;
+ }
+}
+function renderFrontPage() {
+ var numcommittees = 0;
+ var i;
+ for (i in committees) numcommittees++;
+ var curPodlings = 0;
+ for (i in podlings) curPodlings++;
+
+ // The projects list contains 1 entry for each podling, as well as 1 entry
for each DOAP.
+ // Each podling relates to a single project, but a PMC may have one or
more projects.
+ // However not all projects may have registered DOAPs.
+ // In order to find these missing projects, we need to find projects that
have not registered DOAPs
+
+ var projectsWithDoaps = {}; // ids of projects which have registered DOAPS
+ var numProjects = 0; // total projects run by active PMCs
+ for (j in projects) {
+ i = projects[j];
+ projectsWithDoaps[i.pmc] = 1; // which projects have got DOAPs
+ if (i.pmc != 'attic' && i.pmc != 'incubator') {
+ numProjects++; // found a project run by an active PMC (not
podling or retired)
+ }
+ }
+ var numprojectsWithDoaps = 0; // how many projects have registered DOAPs
+ for (i in projectsWithDoaps) numprojectsWithDoaps++;
+ numProjects += (numcommittees - numprojectsWithDoaps); // Add in projects
without DOAPs
+ var obj = document.getElementById('details');
+ if (urlErrors.length > 0) {
+ obj.innerHTML += "<p><span style='color: red'><b>Warning: could not
load: "+urlErrors.join(', ')+"</b></span></p>"
+ }
+ update_id('loading', ''); // clear the loading messages
+ // N.B. These ids must agree with the 'details' div in index.html
+ update_id('committees', numcommittees);
+ update_id('projects', numProjects);
+ update_id('podlings', curPodlings);
+ renderCommitteeEvolution();
+ renderPodlingsEvolution();
+ renderLanguageChart();
+}
+
+
+// ------------ Chart functions ------------\\
+
+function htmlListTooltip(date,name,values) {
+ return '<div style="padding:8px 8px 8px 8px;"><b>' + date + '</b>'
+ + '<br/><b>' + values.length + '</b> ' + name + ((values.length > 1) ?
's:':':')
+ + ((values.length > 0) ? '<br/>- '+values.join('<br/>- '):'')
+ + '</div>';
+}
+
+function renderCommitteeEvolution() {
+ var evo = {}; // 'year-month' -> { established: [], retired: [] }
+ // init evo with empty content for the whole period
+ var maxYear = new Date().getFullYear();
+ for (var year = 1995; year <= maxYear; year++) {
+ var maxMonth = (year < maxYear) ? 12 : (new Date().getMonth() + 1);
+ for (var month = 1; month <= maxMonth; month++) {
+ var key = year + '-' + (month < 10 ? '0':'') + month;
+ evo[key] = { 'established': [], 'retired': [] };
+ }
+ }
+ // add current committees
+ var c;
+ for (c in committees) {
+ c = committees[c];
+ if (evo[c.established]) {
+ evo[c.established]['established'].push(c);
+ } else {
+ console.log(c.id + ": " + c.established + " is off(?!)");
+ }
+ }
+ // add retired committees
+ for (c in retiredCommittees) {
+ c = retiredCommittees[c];
+ if (evo[c.established] && evo[c.retired]) {
+ evo[c.established]['established'].push(c);
+ evo[c.retired]['retired'].push(c);
+
+ } else {
+ console.log(c.id + ": " + c.established + " or " + c.retired + "
is off(?!)");
+ }
+ }
+ // compute data
+ var data = [];
+ var cur = 0;
+ var d;
+ for (d in evo) {
+ var established = evo[d]['established'];
+ var retired = evo[d]['retired'];
+ var estDisplay = [];
+ for (c in established) {
+ c = established[c];
+ estDisplay.push(c.name + ((c.id in retiredCommittees) ? ' (retired
' + retiredCommittees[c.id]['retired'] + ')':''));
+ }
+ var retDisplay = [];
+ for (c in retired) {
+ c = retired[c];
+ retDisplay.push(c.name + ' (established ' + c['established'] +
')');
+ }
+ cur += established.length - retired.length;
+ data.push([d, established.length, htmlListTooltip(d, 'new committee',
estDisplay), -1*retired.length, htmlListTooltip(d, 'retired committee',
retDisplay), cur]);
+ }
+ //narr.sort(function(a,b) { return (b[1] - a[1]) });
+ var dataTable = new google.visualization.DataTable();
+ dataTable.addColumn('string', 'Month');
+ dataTable.addColumn('number', "New committees");
+ dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html':
true}});
+ dataTable.addColumn('number', "Retired committees");
+ dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html':
true}});
+ dataTable.addColumn('number', 'Current committees');
+
+ dataTable.addRows(data);
+
+ var options = {
+ title: "Evolution of Committees (also called PMCs or Top Level
Projects)",
+ isStacked: true,
+ height: 320,
+ width: 1160,
+ seriesType: "bars",
+ backgroundColor: 'transparent',
+ series: {2: {type: "line", targetAxisIndex: 1}},
+ tooltip: {isHtml: true},
+ vAxes:[
+ {title: 'Change in states', ticks: [-3,0,3,6,9]},
+ {title: 'Current number of committees'}
+ ]
+ };
+ var div = document.createElement('div');
+ document.getElementById('details').appendChild(div);
+ var chart = new google.visualization.ComboChart(div);
+ chart.draw(dataTable, options);
+
+ // ================= echarts start ==================
+
+ var chartDom = document.createElement('div');
+
+ document.getElementById('details').appendChild(chartDom);
+ var myChart = echarts.init(chartDom, null, {width: 1160, height: 320});
+
+ var months = []; // x-axis
+ var newPMCs = [];
+ var retired = [];
+ var current = [];
+
+ // pre-generated tooltips
+ var tips = [[],[],[]]; // indexed by series index and data index
+
+ // extract the data for echarts
+ for (d in data) {
+ var e = data[d];
+ months.push(e[0]);
+ newPMCs.push(e[1]);
+ tips[0].push(e[2]);
+ retired.push(e[3]);
+ tips[1].push(e[4]);
+ current.push(e[5]);
+ }
+ var option = {
+ title: {
+ text: "Evolution of Committees (also called PMCs or Top Level
Projects) [ECharts]",
+ left: 125, // should agree with left below
+ },
+ legend: {
+ top: 25, // so does not overwrite title
+ left: 125, // should agree with left above
+ },
+ tooltip: {
+ type: 'item',
+ formatter: function(obj) {
+ if (obj.seriesName == 'Current committees') {
+ return `<b>${obj.name}</b><br/><br/>Current committees:
<b>${obj.value}</b>`;
+ }
+ var idx = parseInt(obj.dataIndex);
+ var sidx = parseInt(obj.seriesIndex);
+ var value = tips[sidx][idx];
+ return value;
+ }
+ },
+ xAxis: [
+ {
+ type: 'category',
+ axisTick: {
+ alignWithLabel: true
+ },
+ axisLabel: {
+ rotate: 30
+ },
+ data: months
+ }
+ ],
+ yAxis: [
+ {
+ type: 'value',
+ name: 'Change in states',
+ nameTextStyle: {
+ fontStyle: 'italic',
+ fontSize: 15,
+ },
+ axisLabel: {
+ customValues: [-3, 0, 3, 6, 9],
+ },
+ nameLocation: 'middle',
+ nameGap: 50,
+ min: -3,
+ max: 9,
+ },
+ {
+ type: 'value',
+ name: 'Current number of committees',
+ nameTextStyle: {
+ fontStyle: 'italic',
+ fontSize: 15,
+ },
+ nameLocation: 'middle',
+ nameGap: 50,
+ min: 0,
+ max: 300,
+ position: 'right',
+ }
+ ],
+ series: [
+ {
+ name: 'New committees',
+ type: 'bar',
+ stack: 'Total',
+ yAxisIndex: 0,
+ data: newPMCs
+ },
+ {
+ name: 'Retired commitees',
+ type: 'bar',
+ barWidth: 1, // must be on last bar series
+ stack: 'Total',
+ yAxisIndex: 0,
+ data: retired
+ },
+ {
+ name: 'Current committees',
+ type: 'line',
+ itemStyle: {
+ opacity: 0 // don't want circles showing
+ },
+ yAxisIndex: 1,
+ data: current
+ }
+ ],
+
+ // Same colours as Google charts
+ color: ['#3366CC', '#DC3912', '#FF9900']
+ };
+ myChart.setOption(option);
+
+ // ================= echarts end ==================
+
+}
+
+function renderPodlingsEvolution(obj) {
+ var evo = {}; // 'year-month' -> { started: [], graduated: [], retired: []
}
+ // init evo with empty content for the whole period
+ var maxYear = new Date().getFullYear();
+ for (var year = 2003; year <= maxYear; year++) {
+ var maxMonth = (year < maxYear) ? 12 : (new Date().getMonth() + 1);
+ for (var month = 1; month <= maxMonth; month++) {
+ var key = year + '-' + (month < 10 ? '0':'') + month;
+ evo[key] = { 'started': [], 'graduated': [], 'retired': [] };
+ }
+ }
+ // add current podlings
+ var p;
+ for (p in podlings) {
+ p = podlings[p];
+ if (p['podling']) {
+ evo[p.started]['started'].push(p);
+ }
+ }
+ // add podlings history
+ for (p in podlingsHistory) {
+ p = podlingsHistory[p];
+ evo[p.started]['started'].push(p);
+ evo[p.ended][p.status].push(p);
+ }
+ // compute data
+ var data = [];
+ var cur = 0;
+ var d;
+ for (d in evo) {
+ var started = evo[d]['started'];
+ var graduated = evo[d]['graduated'];
+ var retired = evo[d]['retired'];
+ var startedDisplay = [];
+ for (p in started) {
+ p = started[p];
+ startedDisplay.push(p.name + (p['ended'] ? ' (' + p.status + ' ' +
p.ended + ')':''));
+ }
+ var graduatedDisplay = [];
+ for (p in graduated) {
+ p = graduated[p];
+ graduatedDisplay.push(p.name + ' (started ' + p.started + ')');
+ }
+ var retiredDisplay = [];
+ for (p in retired) {
+ p = retired[p];
+ retiredDisplay.push(p.name + ' (started ' + p.started + ')');
+ }
+ cur += started.length - graduated.length - retired.length;
+ data.push([d, started.length, htmlListTooltip(d, 'new podling',
startedDisplay),
+ -1*graduated.length, htmlListTooltip(d, 'graduated podling',
graduatedDisplay),
+ -1*retired.length, htmlListTooltip(d, 'retired podling',
retiredDisplay), cur]);
+ }
+ //narr.sort(function(a,b) { return (b[1] - a[1]) });
+ var dataTable = new google.visualization.DataTable();
+ dataTable.addColumn('string', 'Month');
+ dataTable.addColumn('number', "New podlings");
+ dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html':
true}});
+ dataTable.addColumn('number', "Graduated podlings");
+ dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html':
true}});
+ dataTable.addColumn('number', "Retired podlings");
+ dataTable.addColumn({type: 'string', role: 'tooltip', 'p': {'html':
true}});
+ dataTable.addColumn('number', 'Current podlings');
+
+ dataTable.addRows(data);
+
+ var coptions = {
+ title: "Evolution of Incubating projects ('podlings')",
+ isStacked: true,
+ height: 320,
+ width: 1160,
+ seriesType: "bars",
+ backgroundColor: 'transparent',
+ colors: ['#3366cc', '#109618', '#dc3912', '#ff9900'],
+ series: {3: {type: "line", targetAxisIndex: 1}},
+ tooltip: {isHtml: true},
+ vAxes: [
+ {title: 'Change in states', ticks: [-6,-3,0,3,6]},
+ {title: 'Current number of podlings'}
+ ]
+ };
+ var div = document.createElement('div');
+ document.getElementById('details').appendChild(div);
+ var chart = new google.visualization.ComboChart(div);
+ chart.draw(dataTable, coptions);
+
+ // ================= echarts start ==================
+
+ var chartDom = document.createElement('div');
+
+ document.getElementById('details').appendChild(chartDom);
+ var myChart = echarts.init(chartDom, null, {width: 1160, height: 320});
+
+ var months = []; // x-axis
+ var newPods = [];
+ var retired = [];
+ var graduated = [];
+ var current = [];
+
+ // pre-generated tooltips
+ var tips = [[],[],[]]; // indexed by series index and data index
+
+ // extract the data for echarts
+ for (d in data) {
+ var e = data[d];
+ months.push(e[0]);
+ newPods.push(e[1]);
+ tips[0].push(e[2]);
+ graduated.push(e[3]);
+ tips[1].push(e[4]);
+ retired.push(e[5]);
+ tips[2].push(e[6]);
+ current.push(e[7]);
+ }
+ var option = {
+ title: {
+ text: "Evolution of Incubating projects ('podlings') [ECharts]",
+ left: 125, // should agree with left below
+ },
+ legend: {
+ top: 25, // so does not overwrite title
+ left: 125, // should agree with left above
+ },
+ tooltip: {
+ type: 'item',
+ formatter: function(obj) {
+ if (obj.seriesName == 'Current podlings') {
+ return `<b>${obj.name}</b><br/><br/>Current podlings:
<b>${obj.value}</b>`;
+ }
+ var idx = parseInt(obj.dataIndex);
+ var sidx = parseInt(obj.seriesIndex);
+ var value = tips[sidx][idx];
+ return value;
+ }
+ },
+ xAxis: [
+ {
+ type: 'category',
+ axisTick: {
+ alignWithLabel: true
+ },
+ axisLabel: {
+ rotate: 30
+ },
+ data: months
+ }
+ ],
+ yAxis: [
+ {
+ type: 'value',
+ name: 'Change in states',
+ nameTextStyle: {
+ fontStyle: 'italic',
+ fontSize: 15,
+ },
+ axisLabel: {
+ customValues: [-6,-3,0,3,6],
+ },
+ nameLocation: 'middle',
+ nameGap: 50,
+ min: -9,
+ max: 6,
+ },
+ {
+ type: 'value',
+ name: 'Current number of podlings',
+ nameTextStyle: {
+ fontStyle: 'italic',
+ fontSize: 15,
+ },
+ nameLocation: 'middle',
+ nameGap: 50,
+ min: 0,
+ max: 80,
+ position: 'right',
+ }
+ ],
+ series: [
+ {
+ name: 'New podlings',
+ type: 'bar',
+ stack: 'Total',
+ yAxisIndex: 0,
+ data: newPods
+ },
+ {
+ name: 'Graduated podlings',
+ type: 'bar',
+ stack: 'Total',
+ yAxisIndex: 0,
+ data: graduated
+ },
+ {
+ name: 'Retired podlings',
+ type: 'bar',
+ stack: 'Total',
+ yAxisIndex: 0,
+ data: retired
+ },
+ {
+ name: 'Current podlings',
+ type: 'line',
+ itemStyle: {
+ opacity: 0 // don't want circles showing
+ },
+ yAxisIndex: 1,
+ data: current
+ }
+ ],
+
+ // Same colours as Google charts
+ color: ['#3366CC', '#109618', '#DC3912', '#FF9900']
+ };
+ myChart.setOption(option);
+
+ // ================= echarts end ==================
+
+}
+
+function renderLanguageChart() {
+ var obj = document.getElementById('details');
+
+ // Languages
+ var lingos = [];
+ var lcount = {};
+ for (var i in projects) {
+ i = projects[i];
+ if (i['programming-language']) {
+ var a = i['programming-language'].split(/,\s*/);
+ for (var x in a) {
+ x = a[x];
+ if (lingos.indexOf(x) < 0) {
+ lingos.push(x);
+ lcount[x] = 0;
+ }
+ lcount[x]++;
+ }
+ }
+ }
+
+
+ var narr = [];
+ for (i in lingos) {
+ var lang = lingos[i];
+ narr.push([lang, lcount[lang], 'Click here to view declared projects
using ' + lang]);
+ }
+ narr.sort(function(a,b) { return (b[1] - a[1]) });
+
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', 'Language');
+ data.addColumn('number', "Projects using it");
+ data.addColumn({type: 'string', role: 'tooltip'});
+ data.addRows(narr);
+
+ var options = {
+ title: 'Language distribution (click on a language to view declared
projects using it)',
+ height: 400,
+ backgroundColor: 'transparent'
+ };
+
+ var chartDiv = document.createElement('div');
+ var chart = new google.visualization.PieChart(chartDiv);
+ obj.appendChild(chartDiv);
+
+ function selectHandlerLanguage() {
+ var selectedItem = chart.getSelection()[0];
+ if (selectedItem) {
+ var value = data.getValue(selectedItem.row, 0);
+ location.href = "projects.html?language#" + value;
+ }
+ }
+ google.visualization.events.addListener(chart, 'select',
selectHandlerLanguage);
+ chart.draw(data, options);
+
+ // ================= echarts start ==================
+
+ var chartDom1 = document.createElement('div');
+
+ document.getElementById('details').appendChild(chartDom1);
+ var myChart1 = echarts.init(chartDom1, null, {width: 800, height: 400});
+
+ const leg1 = [];
+ const num1 = [];
+ for (i in narr) {
+ leg1.push(narr[i][0]);
+ num1.push({name: narr[i][0], value: narr[i][1]});
+ }
+
+ var option = {
+ title: {
+ text: 'Language distribution (click on a language to view declared
projects using it) [ECharts]',
+ textStyle: {
+ fontSize: 15
+ },
+ left: 130
+ },
+ tooltip: {
+ trigger: 'item',
+ // formatter: '{a} {b} ({d}%)'
+ },
+ legend: {
+ type: 'scroll',
+ orient: 'vertical',
+ right: 10,
+ top: 30,
+ bottom: 300,
+ data: leg1
+ },
+ series: [
+ {
+ name: 'Click here to view declared projects using ',
+ type: 'pie',
+ radius: '65%',
+ center: ['50%', '50%'],
+ data: num1,
+ label: {
+ position: 'inner',
+ textStyle: {
+ fontSize: 15
+ },
+ formatter: function(obj) {
+ if (obj.percent > 8) {
+ return Math.round(obj.percent*10)/10 + '%'; // round
to one decimal point
+ } else {
+ return '';
+ }
+ }
+ }
+ }
+ ]
+ };
+
+ myChart1.setOption(option);
+ myChart1.on('click', function(params) {
+ var name = params.data.name;
+ window.location.href = 'projects.html?language#'+ name;
+ });
+
+ // ================= echarts end ====================
+
+ // Categories
+ var cats = [];
+ var ccount = {};
+ for (i in projects) {
+ i = projects[i];
+ if (i.category) {
+ var a = i.category.split(/,\s*/);
+ for (x in a) {
+ if (cats.indexOf(a[x]) < 0) {
+ cats.push(a[x]);
+ ccount[a[x]] = 0;
+ }
+ ccount[a[x]]++;
+ }
+ }
+ }
+
+
+ var carr = [];
+ for (i in cats) {
+ var cat = cats[i];
+ carr.push([cat, ccount[cat], 'Click here to view declared projects in
the ' + cat + ' category'])
+ }
+ carr.sort(function(a,b) { return (b[1] - a[1]) });
+
+
+ var data2 = new google.visualization.DataTable();
+ data2.addColumn('string', 'Category');
+ data2.addColumn('number', "Projects");
+ data2.addColumn({type: 'string', role: 'tooltip'});
+ data2.addRows(carr);
+
+ var options2 = {
+ title: 'Categories (click on a category to view declared projects within
it)',
+ height: 400,
+ backgroundColor: 'transparent'
+ };
+
+ var chartDiv2 = document.createElement('div');
+ var chart2 = new google.visualization.PieChart(chartDiv2);
+ obj.appendChild(chartDiv2);
+
+
+ function selectHandlerCategory() {
+ var selectedItem = chart2.getSelection()[0];
+ if (selectedItem) {
+ var value = data2.getValue(selectedItem.row, 0);
+ location.href = "projects.html?category#" + value;
+ }
+ }
+ google.visualization.events.addListener(chart2, 'select',
selectHandlerCategory);
+ chart2.draw(data2, options2);
+
+ // ================= echarts start ==================
+
+ var chartDom2 = document.createElement('div');
+
+ document.getElementById('details').appendChild(chartDom2);
+ var myChart2 = echarts.init(chartDom2, null, {width: 800, height: 400});
+
+ const leg2 = [];
+ const num2 = [];
+ for (i in carr) {
+ leg2.push(carr[i][0]);
+ num2.push({name: carr[i][0], value: carr[i][1]});
+ }
+
+ var option = {
+ title: {
+ text: 'Categories (click on a category to view declared projects
within it) [ECharts]',
+ textStyle: {
+ fontSize: 15
+ },
+ left: 130
+ },
+ tooltip: {
+ trigger: 'item',
+ },
+ legend: {
+ type: 'scroll',
+ orient: 'vertical',
+ right: 10,
+ top: 30,
+ bottom: 300,
+ data: leg2
+ },
+ series: [
+ {
+ name: 'Click here to view declared projects in category ',
+ type: 'pie',
+ radius: '65%',
+ center: ['50%', '50%'],
+ data: num2,
+ label: {
+ position: 'inner',
+ textStyle: {
+ fontSize: 15
+ },
+ formatter: function(obj) {
+ if (obj.percent > 8) {
+ return Math.round(obj.percent*10)/10 + '%'; // round
to one decimal point
+ } else {
+ return '';
+ }
+ }
+ }
+ }
+ ]
+ };
+
+ myChart2.setOption(option);
+ myChart2.on('click', function(params) {
+ var name = params.data.name;
+ window.location.href = 'projects.html?category#'+ name;
+ });
+
+ // ================= echarts end ====================
+
+
+}
+
+// This is the entry point from index.html and about.html
+
+function buildFrontPage() {
+ renderFrontPage({}, null)
+}
+
+
+
+// ------- Account creation chart function -------- \\
+
+function drawAccountCreation(json) {
+ var i;
+ var j;
+ var narr = [];
+ var cdata = new google.visualization.DataTable();
+ cdata.addColumn('string', 'Date');
+ cdata.addColumn('number', 'New committers');
+ cdata.addColumn('number', 'Total number of committers');
+ var max = 0;
+ var jsort = [];
+ for (j in json) {
+ jsort.push(j);
+ }
+
+ jsort.sort();
+ var c = 0;
+ for (i in jsort) {
+ i = jsort[i];
+ var entry = json[i];
+ var arr = i.split("-");
+ var d = new Date(parseInt(arr[0]), parseInt(arr[1]), 1);
+ c += entry;
+ narr.push([i, entry, c]);
+ max = (max < entry) ? entry : max;
+ }
+ cdata.addRows(narr);
+
+ var options = {
+ title: ('Account creation timeline'),
+ backgroundColor: 'transparent',
+ height: 320,
+ width: 1160,
+ vAxes:[
+
+ {title: 'New accounts', titleTextStyle: {color: '#0000DD'}, maxValue:
Math.max(200,max)},
+ {title: 'Total number of accounts', titleTextStyle: {color: '#DD0000'}},
+
+ ],
+ series: {
+ 1: {type: "line", pointSize:3, lineWidth: 3, targetAxisIndex: 1},
+ 0: {type: "bars", targetAxisIndex: 0}
+ },
+ seriesType: "bars",
+ tooltip: {isHtml: true}
+ };
+
+ var obj = document.createElement('div');
+ obj.style = "float: left; width: 1160px; height: 450px;";
+ obj.setAttribute("id", 'accountchart');
+ var contents = document.getElementById('contents');
+ contents.innerHTML = "<h1>Timelines</h1>";
+ contents.appendChild(obj);
+
+ var chart = new google.visualization.ComboChart(obj);
+ chart.draw(cdata, options);
+}
+
+// called by timelines.html
+
+function buildTimelines() {
+ GetAsyncJSON("json/foundation/accounts-evolution.json", null,
drawAccountCreation);
+}
+
+
+// called by timelines2.html
+
+function buildTimelines2() {
+ GetAsyncJSON("json/foundation/accounts-evolution2.json", null,
drawAccountCreation);
+}
+
+
+// ------------ Search feature for the site ------------\\
+
+function searchProjects(str) {
+ var obj = document.getElementById('contents');
+
+ str = str.toLowerCase();
+ var hits = {};
+ var hitssorted = [];
+
+ // Search committees
+ for (p in projects) {
+ var project = projects[p];
+ for (key in project) {
+ if (typeof project[key] == "string") {
+ var val = project[key].toLowerCase();
+ if (val.indexOf(str) >= 0 && val.substr(0,1) != "{") {
+ if (!hits[p]) {
+ hits[p] = [];
+ }
+ var estr = new RegExp(str, "i");
+ hits[p].push({
+ 'key': key,
+ 'val': project[key].replace(estr, function(a) { return "<u
style='color: #963;'>"+a+"</u>"}, "img")
+ });
+ if (hitssorted.indexOf(p) < 0) {
+ hitssorted.push(p);
+ }
+ }
+ }
+ }
+ }
+
+ var title = "Search results for '" + str + "' (" + hitssorted.length +
"):";
+ obj.innerHTML = "";
+ var h2 = document.createElement('h2');
+ h2.appendChild(document.createTextNode(title));
+ obj.appendChild(h2);
+ hitssorted.sort(function(a,b) { return hits[b].length - hits[a].length });
+ var ul = document.createElement('ul');
+
+ var h;
+ for (h in hitssorted) {
+ h = hitssorted[h];
+ var project = hits[h];
+ var html = "<h4><a href='project.html?" + h + "'>" + projects[h].name
+ "</a> (" + project.length + " hit(s)):</h4>";
+ for (x in project) {
+ html += "<blockquote><b>" + project[x].key + ":</b> " +
project[x].val + "</blockquote>";
+ }
+ appendLiInnerHTML(ul,html);
+ }
+ if (hitssorted.length == 0) {
+ obj.innerHTML += "No search results found";
+ }
+ obj.appendChild(ul);
+}
+
+
+
+// Key press monitoring for search field
+function checkKeyPress(e, txt) {
+ if (!e) e = window.event;
+ var keyCode = e.keyCode || e.which;
+ if (keyCode == '13'){
+ searchProjects(txt.value);
+ }
+}
+
+
+// ------------ Weave functions ------------\\
+
+function weaveById(list,mapById) {
+ for (var i in list) {
+ var o = list[i];
+ mapById[o.id] = o;
+ }
+}
+
+function fixProjectName(project) {
+ // fix attic and incubator project names if necessary
+ if (project.pmc == "attic") {
+ project.name += " (in the Attic)";
+ } else if ((project.pmc == "incubator") &&
!project.name.match(/incubating/i)) {
+ project.name += " (Incubating)";
+ }
+ return project;
+}
+
+// Add content by id to projects
+function weaveInProjects(json, pfx) {
+ if (!pfx) { pfx='' }
+ for (p in json) {
+ // Since podlings are loaded first, DOAPs take precedence
+ projects[pfx+p] = fixProjectName(json[p]);
+ }
+}
+
+function weaveInRetiredCommittees(json) {
+ weaveById(json, retiredCommittees);
+ var p;
+ var projectsPmcs = {};
+ for (p in projects) {
+ projectsPmcs[projects[p].pmc] = p;
+ }
+}
+
+function setCommittees(json, state) {
+ weaveById(json, committees);
+ var c;
+ for (c in json) {
+ c = json[c];
+ // committeesByName = { name -> committee }
+ committeesByName[c.name] = c;
+ }
+ if (state) {
+ state();
+ }
+}
+
+// Render releases using datatables
+function renderReleases(releases) {
+ var arr = [];
+ for (p in releases) {
+ var releasedata = releases[p];
+
+ for (filename in releasedata) {
+ var date = releasedata[filename];
+ // Shove the result into a row
+ arr.push([ p, p, date, filename]);
+ }
+ }
+
+ // Construct the data table
+ $('#contents2').html( '<table cellpadding="0" cellspacing="0" border="0"
class="display" id="releases"></table>' );
+
+ $('#releases').dataTable( {
+ "data": arr,
+ "columns": [
+ { "title": "ID", "visible": false },
+ { "title": "Name" },
+ { "title": "Date" },
+ { "title": "Release name" }
+ ],
+ "fnRowCallback": function( nRow, aData, iDisplayIndex,
iDisplayIndexFull) {
+ jQuery(nRow).attr('id', aData[0]);
+ jQuery(nRow).css("cursor", "pointer");
+ return nRow;
+ }
+ } );
+
+ $('#releases tbody').on('click', 'tr', function () {
+ var name = $(this).attr('id').replace("incubator-","incubator/");
+ location.href = "https://www.apache.org/dist/" + name;
+ } );
+}
+
+// Generate a 'Link to here' pop-up marker
+function linkToHere(id) {
+ return "<a class='sectionlink' href='#"+id+"' title='Link to
here'>¶</a>"
+}
+
+// Called by releases.html
+
+function buildReleases() {
+ GetAsyncJSON("json/foundation/releases.json?" + Math.random(), null,
renderReleases);
+}
+
+// ------------ Async data fetching ------------\\
+// This function is the starter of every page, and preloads the needed files
+// before running the final page renderer. This is roughly 1 mb of JSON, but as
+// it gets cached after first run, it's not really a major issue.
+
+function preloadEverything(callback) {
+ //
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
+ if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function (searchString, position) {
+ position = position || 0;
+ return this.substr(position, searchString.length) === searchString;
+ };
+ }
+ GetAsyncJSONArray([
+ ["json/foundation/committees.json", "committees", setCommittees],
+ ["json/foundation/groups.json", "groups", function(json) { unixgroups
= json; }],
+ ["json/foundation/people_name.json", "people", function(json) { people
= json; }],
+ ["json/foundation/podlings.json", "podlings", function(json) {
podlings = json; weaveInProjects(json,'incubator-')}], // do this first
+ ["json/foundation/projects.json", "projects", weaveInProjects], // so
can replace with DOAP data where it exists
+ ["json/foundation/committees-retired.json", "retired committees",
weaveInRetiredCommittees],
+ ["json/foundation/podlings-history.json", "podlings history",
function(json) { podlingsHistory = json; }],
+ ["json/foundation/repositories.json", "repositories", function(json) {
repositories = json; }]
+ ],
+ callback);
+}
Propchange: comdev/projects.apache.org/trunk/site/js/projects_echarts.js
------------------------------------------------------------------------------
svn:eol-style = native