Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6ddc43b
Adapte la police et la hauteur des carrés à l'espace entre les feuilles
simonpenel Jan 18, 2024
8927488
Supprime la modif de police
simonpenel Jan 18, 2024
249b74a
Utilise la variable fitPoliceSize
simonpenel Jan 19, 2024
f0466f4
Mise à jour de nodeWidth lorsque le bouton 'logarithmic' est utilisé
simonpenel Jan 19, 2024
4c71457
Nettoyage
simonpenel Jan 19, 2024
3d20019
Remplace les fonctions par des valeurs
simonpenel Jan 19, 2024
f00609c
Generation des svg sous la condition du type de sequence
simonpenel Jan 19, 2024
78a765a
Fixe la position en x du texte à 0 au lieu de 1
simonpenel Jan 22, 2024
87642c0
Correction surlignage, correction taille de police pour les dna, adap…
simonpenel Jan 22, 2024
86bdf2b
Reduit la hauteur du soulignage pour les dna
simonpenel Jan 22, 2024
0457f82
Adapte la position verticale du soulignage
simonpenel Jan 22, 2024
a0eeb67
Ameliore la position verticale du soulignage
simonpenel Jan 22, 2024
eea16d7
Modif de la pos. verticale lors de l'action moreHeight
simonpenel Jan 22, 2024
98f1325
Le pointeur de lessHeight devient not-allowed lorsque la hauteur mini…
simonpenel Jan 22, 2024
95d0758
Nettoyage
simonpenel Jan 22, 2024
88e2321
Nettoyage
simonpenel Jan 22, 2024
21f16fd
Nettoyage
simonpenel Jan 22, 2024
8e53e86
Nettoyage
simonpenel Jan 31, 2024
31a0b1d
Change le nom de la variable rgb
simonpenel Jan 31, 2024
b9558d6
Mise sous forme de fonction: define_colordna
simonpenel Jan 31, 2024
60354c9
Nettoyage
simonpenel Jan 31, 2024
ad2441f
Deplace le code html de displaytree.ejs vers header.ejs
simonpenel Feb 1, 2024
0e8e7cc
Correction indentation
simonpenel Feb 1, 2024
7fe29f9
Separe header.ejs en header.ejs et buttons.ejs
simonpenel Feb 1, 2024
1704e0c
Nettoyage
simonpenel Feb 1, 2024
dec58b6
Nettoyage
simonpenel Feb 1, 2024
92b9cc7
Nettoyage
simonpenel Feb 1, 2024
d8b3d9c
add APOBEC3G example
lgueguen Jun 25, 2025
bf9e1ed
Merge branch 'main' of https://github.com/lgueguen/PositiveSelectionI…
lgueguen Jun 25, 2025
5c84a3b
functional buttons & scale mgmt
lgueguen Jul 22, 2025
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
390 changes: 390 additions & 0 deletions examples/nice_example/APOBEC3G_primates.counts_DnDs.txt.sged

Large diffs are not rendered by default.

546 changes: 26 additions & 520 deletions examples/nice_example/APOBEC3G_primates.fasta

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions examples/nice_example/APOBEC3G_primates_ml.dnd_1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
((ponPygAY639869:0.107494,(homSapCCDS13984:0.0312308,(gorGorAY639868:0.0361351,(panTroDQ185513:0.0140133,panPanXM_008975100:0.0253136)5:0.0167225)6:0.00381048)7:0.0448956)8:0.0899657,((colGueKC176189:0.0650861,(traFraKC176192:0.040492,(rhiRoxNM_001345905:0.0353039,nasLarKC176190:0.0381885)13:0.0118188)14:0.0415264)15:0.0923819,((allNigKC176173:0.0711026,((cerPetKC176193:0.019077,(cerCepKC176178:0.0204168,(cerWolKC176188:0.0167493,cerNegKC176175:0.0111318)21:0.0104289)22:0.00827238)23:0.0249896,((chlAetAB266486:0.0171719,chlSabXM_007975814:0.00535274)26:0.00712051,(eryPatKC176180:0.0232724,(chlTanJN662547:0.00451739,chlPygJN662545:0.00935603)30:0.00279884)31:0.00280891)32:0.0217719)33:0.0122203)34:0.0265378,((macMulKC176184:0.0216968,(macNemKX583654:0.00812479,macFasAB266488:0.0170656)38:0.00550161)39:0.0617103,(papAnuKC176174:0.0195709,(manLeuNM_001345916:0.0115229,(cerTorKC176182:0.00837242,cerAtyKF020491:0.0196226)44:0.00541445)45:0.0340216)46:0.0460357)47:0.011112)48:0.0433605)49:0.113498);
161 changes: 83 additions & 78 deletions genere_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,8 @@ def nucToAmino(nuc_seq:str):
return aa


def loadAlignment(alignmentFile):
'''Loads a FASTA alignment.'''

def loadAlignment(alignmentFile, sites):
'''Loads a FASTA alignment with sites (starting at position 0)'''
alignmentDict = {}
maxSeqIdLength = 0
with open(alignmentFile, 'r') as af:
Expand All @@ -168,105 +167,106 @@ def loadAlignment(alignmentFile):
alignmentDict[seq_id] = aligned_seq
else:
alignmentDict[seq_id] += aligned_seq
return alignmentDict, maxSeqIdLength

## Take only assigned sites
lenseq=len(alignmentDict[list(alignmentDict.keys())[0]])

## Guess is codon sequence or not
Msites = max(sites)
if Msites>=lenseq/3:
isCodon=False # Unsafe:perhaps to few sites to detect non-coding sequence
elif Msites<=lenseq:
isCodon=True
else:
sys.exit("Mismatch lengths between alignment (" + str(lenseq) + ") and sites (" + str(Msites) + ")")

def loadResultsSites(resultsFile, exprcol="'[1]'", nostat_value=-1.0, skipMissingSites=False):
'''Loads site results from formula exprcol on columns.'''
if not isCodon:
lsites = sites
else:
lsites = [y for x in sites for y in [3*x,3*x+1,3*x+2]]

alignmentDict2 = {}
for name, seq in alignmentDict.items():
alignmentDict2[name]="".join([seq[x] for x in lsites])

return alignmentDict2, maxSeqIdLength


def loadResultsSites(resultsFile, exprcol="'[1]'"):
'''Loads site results from formula exprcol on columns.
Returns list of results, list of sites (starting with 0)
'''

if len(exprcol)>30:
return
expr=exprcol[1:-1].replace("[","float(line[")
expr=expr.replace("]","])")
resultsDict = {}
resultsList = []
siteList =[]
with open(resultsFile, 'r') as f:
i = -1
for line in f:
line = line.strip().split()
if re.search('^[0-9]+$', line[0]): # results line
lp = re.findall('[0-9]+', line[0]) # results line
if len(lp)!=0:
try:
site = int(line[0])
site = int(lp[0])-1
res = float(eval(expr))
except ValueError:
print(f"Conversion failed in line {line}")
else:
if i == -1:
i = site
while i < site and i < MAX_SITE:
# in case there is no statistic for
# a site, give it a default value
if not skipMissingSites:
# print(f'Site {i} missing ({site})')
resultsDict[i] = nostat_value
i += 1
resultsDict[i] = res
i += 1
resultsList.append(res)
siteList.append(site)

resultsList = []
for key, item in resultsDict.items():
resultsList.append(item)
return resultsList
return resultsList, siteList


def loadResultsBranchSite(resultsFile, nostat_value=-1.0, skipMissingSites=False):
'''Loads branch-site results.'''
def loadResultsBranchSite(resultsFile):
'''Loads branch-site results.
Returns dict of lists of results, list of sites (starting with 0)
'''

col_lists = []

with open(resultsFile, 'r') as f:
col_headers=f.readline().strip().split()[1:]
nbcol=len(col_headers)
col_lists=[[] for i in range(nbcol)]

numl = -1
siteList=[]

for line in f:
line = line.strip().split()
site = int(line[0])

## line: ['0', '0.25866598', '0.66856214', '0.19517522', ...]
if numl==-1:
numl=site
if not skipMissingSites:
while numl < site and numl < MAX_SITE:
# in case there is no statistic for
# a site, give it a default value
lp = re.findall('[0-9]+', line[0]) # results line
if len(lp)!=0:
try:
siteList.append(int(lp[0])-1)
for i in range(nbcol):
col_lists[i].append(nostat_value)
numl += 1
else:
numl=site

for i in range(nbcol):
col_lists[i].append(line[i+1])

numl+=1
col_lists[i].append(float(line[i+1]))
except ValueError:
print(f"Conversion failed in line {line}")

d_cols = {}

for i in range(len(col_lists)):
d_cols[col_headers[i]] = col_lists[i]

## col_lists: [['0.00885554', '0.25866598', '0.03920189'], ...]
## d_cols {'38': ['0.00885554', '0.25866598', '0.03920189'], ...}

d_cols_2 = dict(d_cols)
nbs=len(d_cols['0'])
print(nbs)
for col_key in d_cols:
## For every branch
if re.search(r'^[0-9]+$', col_key):
col_text = ''
## Add each result to the text
for i in range(nbs):
col_text += f'{d_cols[col_key][i]}, '
## col_text: '0.1248, 0.12381, ...'
## Add brackets for JSON format
col_text = '['+col_text[:-2]+']' # [:-2] to remove last space and comma
d_cols_2[col_key] = col_text
## col_text: '0.1248, 0.12381, ...'
## Add brackets for JSON format
d_cols_2[col_key] = '[' + ",".join([str(d_cols[col_key][i]) for i in range(nbs)]) + "]"

## d_cols_2: {1: '[0.1248, 0.12381, ...]', ...}

nb_branches = len(col_lists)
print(nb_branches, 'branches found in results')

return d_cols_2
return d_cols_2, siteList


def getColnames(file):
Expand Down Expand Up @@ -326,7 +326,7 @@ def createPhyloXML(fam,newick,results):
## the first ID being 0.

# Write a sequence of Tree objects to the given file or handle
rd = str(random.randint(0,1000))
rd = str(random.randint(0,10000))
Phylo.write([trees], 'tmpfile-'+rd+'.xml', 'phyloxml')
file = open('tmpfile-'+rd+'.xml', 'r')
# Copies all objects in a variable and removes the created file
Expand Down Expand Up @@ -401,16 +401,18 @@ def count_repl(match):
leaf.set('speciesLocation', sp)
if 'seqdefdico' in globals():
if cds in seqdefdico:
leaf.set('defintiion', seqdefdico[cds])
leaf.set('definition', seqdefdico[cds])

## Add sequence to leaf
## Add sequence to 'leaf
if lenres==lenseq/3:
isCodon="true"
leaf.set('dnaAlign', seq_alg) # coding sequence
leaf.set('aaAlign', nucToAmino(seq_alg)) # translated sequence
else:
isCodon="false"
elif lenres<=lenseq:
isCodon="false"
leaf.set('aaAlign', seq_alg) # raw sequence (any type)
else:
sys.exit("Mismatch lengths between alignment (" + str(lenseq) + ") and sites (" + str(lenres) + ")")

evrec.append(leaf)
element.append(evrec)
Expand All @@ -423,14 +425,14 @@ def count_repl(match):
## Look for the column with header <branch_id> in the results
branch_info = etree.Element('branch_info')
branch_info.set('id', branch_id)
branch_info.set('results', str(dict_results[branch_id]))
branch_info.set('results', str(results[branch_id]))
element.append(branch_info)
except KeyError:
## If there is no matching column is results, add one with negative results
print(f'Branch ID {branch_id} not found, adding placeholder')
branch_info = etree.Element('branch_info')
branch_info.set('id', branch_id)
dummy_col = json.loads(dict_results['0'])
dummy_col = json.loads(results['0'])
dummy_col = [-1.0 for _ in range(len(dummy_col))]
col_str = json.dumps(dummy_col)
branch_info.set('results', col_str)
Expand Down Expand Up @@ -476,20 +478,20 @@ def count_repl(match):
MAX_SITE = 100000
sys.setrecursionlimit(15000)

print ("Loading alignment... ")
loadedAlignment = loadAlignment(args.alignmentFile)
alignmentDict, maxSeqIdLength = loadedAlignment[0], loadedAlignment[1]
print ("OK")

print ("Loading results... ")
sites=[]
if not args.isBranchsite:
results = loadResultsSites(args.resultsFile, args.exprcol, args.nostat, skipMissingSites=args.skipMissingSites)
results, sites = loadResultsSites(args.resultsFile, args.exprcol)
else:
results = loadResultsBranchSite(args.resultsFile)
results, sites = loadResultsBranchSite(args.resultsFile)

print("Results read")

print ("Loading alignment... ")
loadedAlignment = loadAlignment(args.alignmentFile, sites)
alignmentDict, maxSeqIdLength = loadedAlignment[0], loadedAlignment[1]
print ("OK")

if args.isBranchsite:
dict_results = loadResultsBranchSite(args.resultsFile)
print("OK")

# Creates empty phyloxml document
# project = Phyloxml() # uncomment to have a unique xml file
Expand Down Expand Up @@ -517,9 +519,12 @@ def count_repl(match):
# newick = tline[0]
# fam = ''
current_branch = -1
phyloxmltree = createPhyloXML("",line,results)
xmloutputfile.write(phyloxmltree)
print("Tree OK")
try:
phyloxmltree = createPhyloXML("",line,results)
xmloutputfile.write(phyloxmltree)
print("Tree OK")
except ValueError:
sys.exit("Mismatch lengths")

treefile.close()
xmloutputfile.close()
94 changes: 94 additions & 0 deletions partials/buttons.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!-- Bouton Action -->
<div id="action" class="dropdown">
<button class="btn dropdown-toggle btn-sm btn-outline-secondary" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Choose action on node">
On click
</button>
<ul class="dropdown-menu" aria-labelledby="action">
<li><a id="branch-res" class="dropdown-item" href="#">View Branch Results</a></li>
<li><a id="switch" class="dropdown-item" href="#">Switch children</a></li>
<li><a id="collapse" class="dropdown-item" href="#">Collapse/Expand</a></li>
<li><a id="subtree" class="dropdown-item" href="#">Sub Tree/Upper Tree</a></li>
</ul>
</div>
<!-- Bouton Expand -->
<div class="btn-group" role="group" aria-label="">
<button id="globalexpand" class="btn btn-sm btn-outline-secondary" type="button" aria-label="Expand" data-toggle="tooltip" data-placement="top" title="Expand the tree (takes time on big trees!)">
<span>Expand tree</span>
</button>
</div>
<!-- Boutons Width / Height -->
<div class="btn-group" role="group" aria-label="Tree size buttons">
<div class="btn-group" role="group" aria-label="Width buttons">
<!-- Bouton Reduce width -->
<button id="lessWidth" class="btn btn-sm btn-outline-secondary" type="button" aria-label="Less width" data-toggle="tooltip" data-placement="top" title="Reduce tree width">
<span>Width -</span>
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
<!-- Bouton Increase width -->
<button id="moreWidth" class="btn btn-sm btn-outline-secondary" type="button" aria-label="More width" data-toggle="tooltip" data-placement="top" title="Increase tree width">
<span>Width +</span>
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</button>
<!-- Bouton Increase width -->
<button id="loga" class="btn btn-sm btn-outline-secondary" type="button" aria-label="Logarithmic scale" data-toggle="tooltip" data-placement="top" title="Logarithmic scale">
<span>Logarithmic</span>
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</button>
</div>
<div class="btn-group" role="group" aria-label="Height buttons">
<!-- Bouton Reduce height -->
<button id="lessHeigth" class="btn btn-sm btn-outline-secondary" type="button" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="Reduce tree height">
<span>Height -</span>
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
<!-- Bouton Increase height -->
<button id="moreHeigth" class="btn btn-sm btn-outline-secondary" type="button" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="Increase tree height">
<span>Height +</span>
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</button>
</div>
</div>
<!-- Bouton Alignement -->
<div id="alignment-type" class="dropdown">
<button class="btn dropdown-toggle btn-sm btn-outline-secondary" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Choose alignment type">
Alignment type
</button>
<ul class="dropdown-menu" aria-labelledby="alignment-type">
<li><a id="aminoacid-display" class="dropdown-item" href="#">Amino Acids</a></li>
<li><a id="codons-display" class="dropdown-item" href="#">Codons</a></li>
</ul>
</div>
<div class="btn-group" role="group" aria-label="Redundance">
<!-- Bouton redondance -->
<button id="redondance" class="btn btn-sm btn-outline-secondary" type="button" aria-label="Redundancy" data-toggle="tooltip" data-placement="top" title="Reduce tree height">
<span>Redundancy</span>
</button>
</div>
<!-- Bouton Export -->
<div id="export" class="dropdown">
<button class="btn dropdown-toggle btn-sm btn-outline-secondary" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Choose Export">
Export
</button>
<ul class="dropdown-menu" aria-labelledby="export">
<li><a id="tree" class="dropdown-item" href="#">Tree</a></li>
<li><a id="selecp" class="dropdown-item" href="#">Alignment</a></li>
</ul>
</div>
<!-- Champs Seuils sup. et inf. -->
<div id="thresholds-group" class="btn-group" role="group" aria-label="">
<button class="btn btn-sm btn-outline-secondary">
<div class="input-group align-items-center">
<label for="ps-threshold-low">Thresholds</label>
<input id="ps-threshold-low" placeholder="PS Threshold" value="0.9" style="width: 5em;"/>
<input id="ps-threshold-high" placeholder="PS Threshold" value="1.0" style="width: 5em;"/>
</div>
</button>
<div>
<div class="input-group">
<input type="radio" class="btn-check" name="selectedThreshold" id="lowerSelected" value="lowerSelected">
<label class="btn btn-outline-secondary" for="lowerSelected" style="margin-left: 0.5rem;"><</label>
<input type="radio" class="btn-check" name="selectedThreshold" id="upperSelected" value="upperSelected" checked>
<label class="btn btn-outline-secondary" for="upperSelected" style="margin-left: -0.25rem;">></label>
</div>
</div>
</div>
5 changes: 1 addition & 4 deletions routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,10 @@ router.post("/upload_files", upload.fields([
var resultsType = req.body.resultsType;
var branchSite;
var logBranchLength = req.body.logBranchLength;
var skipMissingSites = req.body.skipMissingSites;
var isNuc = req.body.isNuc;

isNuc = (isNuc != undefined ? true : false);
logBranchLength = (logBranchLength != undefined ? true : false);
skipMissingSites = (skipMissingSites != undefined ? true : false);
branchSite = (resultsType == 'branchSiteMode' ? true : false);

// Generate XML file from data
Expand All @@ -85,8 +83,7 @@ router.post("/upload_files", upload.fields([
+' -o '+xml_dir+fname_xml
+' -e "\''+statcol+'\'"'
+' -n '+nostat
+(branchSite?' -b ':'')
+(skipMissingSites?' --skipmissing ':''),
+(branchSite?' -b ':''),
(error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
Expand Down
Loading