Merge branch 'mgcore_T156_lintfix' into dev
This commit is contained in:
commit
92f6004507
@ -86,6 +86,7 @@ set(fmt_source_dir ${libs_dir}/fmt)
|
|||||||
set(fmt_static_lib ${fmt_source_dir}/fmt/libfmt.a)
|
set(fmt_static_lib ${fmt_source_dir}/fmt/libfmt.a)
|
||||||
# yaml-cpp
|
# yaml-cpp
|
||||||
set(yaml_source_dir ${libs_dir}/yaml-cpp)
|
set(yaml_source_dir ${libs_dir}/yaml-cpp)
|
||||||
|
set(yaml_include_dir ${yaml_source_dir}/include)
|
||||||
set(yaml_static_lib ${yaml_source_dir}/libyaml-cpp.a)
|
set(yaml_static_lib ${yaml_source_dir}/libyaml-cpp.a)
|
||||||
# Catch (C++ Automated Test Cases in Headers)
|
# Catch (C++ Automated Test Cases in Headers)
|
||||||
set(catch_source_dir "${libs_dir}/Catch")
|
set(catch_source_dir "${libs_dir}/Catch")
|
||||||
@ -156,7 +157,7 @@ if(CLANG_TIDY)
|
|||||||
-config=''
|
-config=''
|
||||||
--
|
--
|
||||||
-std=c++1y
|
-std=c++1y
|
||||||
-I${CMAKE_SOURCE_DIR}/include -I${fmt_source_dir}
|
-I${CMAKE_SOURCE_DIR}/include -I${fmt_source_dir} -I${yaml_include_dir}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
# linter setup
|
# linter setup
|
||||||
@ -263,7 +264,7 @@ include_directories(${CMAKE_SOURCE_DIR}/include)
|
|||||||
include_directories(${src_dir})
|
include_directories(${src_dir})
|
||||||
include_directories(${build_include_dir})
|
include_directories(${build_include_dir})
|
||||||
include_directories(${fmt_source_dir})
|
include_directories(${fmt_source_dir})
|
||||||
include_directories(${yaml_source_dir}/include)
|
include_directories(${yaml_include_dir})
|
||||||
include_directories(${http_parser_source_dir})
|
include_directories(${http_parser_source_dir})
|
||||||
include_directories(${lexertl_dir})
|
include_directories(${lexertl_dir})
|
||||||
include_directories(${libuv_source_dir}/include)
|
include_directories(${libuv_source_dir}/include)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
compile_cpu_path: "./compiled/cpu/"
|
compile_cpu_path: "./compiled/cpu/"
|
||||||
|
|
||||||
# path to the template (cpp) for codes generation
|
# path to the template (cpp) for codes generation
|
||||||
template_cpu_cpp_path: "./template/template_code_cpu.cpp"
|
template_cpu_cpp_path: "./template/template_code_cpu_cpp"
|
||||||
|
|
||||||
# path to the template (hpp) for codes generation
|
# path to the template (hpp) for codes generation
|
||||||
template_cpu_hpp_path: "./template/template_code_cpu.hpp"
|
template_cpu_hpp_path: "./template/template_code_cpu.hpp"
|
||||||
|
1
example/.gitignore
vendored
Normal file
1
example/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.out
|
0
src/examples/timestamp.cpp → example/timestamp.cpp
Executable file → Normal file
0
src/examples/timestamp.cpp → example/timestamp.cpp
Executable file → Normal file
@ -5,6 +5,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -47,6 +48,7 @@ int main(void)
|
|||||||
|
|
||||||
auto max = std::accumulate(buckets.begin(), buckets.end(), 0u,
|
auto max = std::accumulate(buckets.begin(), buckets.end(), 0u,
|
||||||
[](auto& acc, auto& x) { return std::max(acc, x.load()); });
|
[](auto& acc, auto& x) { return std::max(acc, x.load()); });
|
||||||
|
assert(max != 0u);
|
||||||
|
|
||||||
std::cout << std::fixed;
|
std::cout << std::fixed;
|
||||||
|
|
@ -90,7 +90,7 @@ std::string BoltDecoder::read_string()
|
|||||||
size = marker & 0x0F;
|
size = marker & 0x0F;
|
||||||
}
|
}
|
||||||
// if the marker is 0xD0, size is an 8-bit unsigned integer
|
// if the marker is 0xD0, size is an 8-bit unsigned integer
|
||||||
if (marker == pack::String8) {
|
else if (marker == pack::String8) {
|
||||||
size = read_byte();
|
size = read_byte();
|
||||||
}
|
}
|
||||||
// if the marker is 0xD1, size is a 16-bit big-endian unsigned integer
|
// if the marker is 0xD1, size is a 16-bit big-endian unsigned integer
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include "data_structures/queue/mpsc_queue.hpp"
|
|
||||||
#include "utils/bash_colors.hpp"
|
|
||||||
|
|
||||||
class Log
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum class Level : std::uint_fast8_t { Debug, Info, Warn, Error };
|
|
||||||
|
|
||||||
struct Message
|
|
||||||
{
|
|
||||||
std::chrono::system_clock::time_point time;
|
|
||||||
Level level;
|
|
||||||
std::string text;
|
|
||||||
std::string file;
|
|
||||||
std::string function;
|
|
||||||
size_t line;
|
|
||||||
|
|
||||||
friend std::ostream&
|
|
||||||
operator<<(std::ostream& stream, const Message& message)
|
|
||||||
{
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
auto t = system_clock::to_time_t(message.time);
|
|
||||||
auto time_string = std::string(std::ctime(&t));
|
|
||||||
|
|
||||||
return stream << bash_color::green
|
|
||||||
<< "[" << to_string(message.level) << "] " << bash_color::end
|
|
||||||
<< message.text;
|
|
||||||
/* << bash_color::yellow << " on " << bash_color::end */
|
|
||||||
/* << time_string.substr(0, time_string.size() - 1) */
|
|
||||||
/* << bash_color::yellow << " in file " << bash_color::end */
|
|
||||||
/* << message.file */
|
|
||||||
/* << bash_color::yellow << " in function " << bash_color::end */
|
|
||||||
/* << message.function */
|
|
||||||
/* << bash_color::yellow << " at line " << bash_color::end */
|
|
||||||
/* << message.line; */
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::array<std::string, 4> level_strings;
|
|
||||||
|
|
||||||
static std::string to_string(Log::Level level)
|
|
||||||
{
|
|
||||||
return level_strings[static_cast<std::size_t>(level)];
|
|
||||||
}
|
|
||||||
|
|
||||||
Log() : alive(true), worker([this]() { work(); }) {}
|
|
||||||
|
|
||||||
~Log()
|
|
||||||
{
|
|
||||||
alive.store(false, std::memory_order_seq_cst);
|
|
||||||
worker.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Log& instance()
|
|
||||||
{
|
|
||||||
static Log log;
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Log(Log&) = delete;
|
|
||||||
Log(Log&&) = delete;
|
|
||||||
|
|
||||||
static void log(Level level, const std::string& text,
|
|
||||||
const char* file, const char* function, size_t line)
|
|
||||||
{
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
auto& log = Log::instance();
|
|
||||||
|
|
||||||
log.messages.push(std::unique_ptr<Message>(new Message {
|
|
||||||
system_clock::now(), level, text, file, function, line
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
lockfree::MpscQueue<Message> messages;
|
|
||||||
std::atomic<bool> alive;
|
|
||||||
std::thread worker;
|
|
||||||
|
|
||||||
void work()
|
|
||||||
{
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
auto message = messages.pop();
|
|
||||||
|
|
||||||
if(message != nullptr)
|
|
||||||
{
|
|
||||||
std::cerr << *message << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!alive)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(10ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::array<std::string, 4> Log::level_strings {{
|
|
||||||
"DEBUG", "INFO", "WARN", "ERROR"
|
|
||||||
}};
|
|
||||||
|
|
||||||
#define LOG(_LEVEL_, _MESSAGE_) \
|
|
||||||
Log::log( \
|
|
||||||
_LEVEL_, \
|
|
||||||
static_cast<std::ostringstream&>( \
|
|
||||||
std::ostringstream().flush() << _MESSAGE_ \
|
|
||||||
).str(), \
|
|
||||||
__FILE__, \
|
|
||||||
__PRETTY_FUNCTION__, \
|
|
||||||
__LINE__ \
|
|
||||||
);
|
|
||||||
|
|
||||||
#ifdef NDEBUG
|
|
||||||
# define LOG_DEBUG(_) do {} while(0);
|
|
||||||
#else
|
|
||||||
# define LOG_DEBUG(_MESSAGE_) LOG(Log::Level::Debug, _MESSAGE_)
|
|
||||||
#endif
|
|
1
src/demo/.gitignore
vendored
1
src/demo/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
ve/
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"esnext": true,
|
|
||||||
"browser": true,
|
|
||||||
"globals": {
|
|
||||||
"sigma": true,
|
|
||||||
"console": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import json
|
|
||||||
from util import get_env, set_modul_attrs
|
|
||||||
|
|
||||||
host = "0.0.0.0"
|
|
||||||
|
|
||||||
port = 5000
|
|
||||||
|
|
||||||
log_level = 'INFO'
|
|
||||||
|
|
||||||
try:
|
|
||||||
set_modul_attrs(__name__, json.loads(get_env('CONFIG')))
|
|
||||||
except:
|
|
||||||
pass
|
|
@ -1,20 +0,0 @@
|
|||||||
FROM ubuntu:14.04
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install -y python3.4 python3.4-dev python3-pip python3-setuptools
|
|
||||||
# RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
RUN pip3 install flask
|
|
||||||
|
|
||||||
COPY simulation /app/simulation
|
|
||||||
COPY demo_server_init.py /app/demo_server_init.py
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV MEMGRAPH_DEMO prod
|
|
||||||
|
|
||||||
# uwsgi --http-socket 0.0.0.0:8080 --module demo_server_init:app \
|
|
||||||
# --master --enable-threads
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
CMD ["python3", "demo_server_init.py"]
|
|
@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
The demo server init script. Environment could be configured
|
|
||||||
via the MEMGRAPH_DEMO environtment variable. Available environments
|
|
||||||
are: debug, prod.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from util import get_env
|
|
||||||
from simulation.web_server import SimulationWebServer
|
|
||||||
|
|
||||||
environment = get_env('MEMGRAPH_DEMO', 'debug')
|
|
||||||
wsgi = get_env('MEMGRAPH_DEMO_WSGI', 'werkzeug')
|
|
||||||
|
|
||||||
|
|
||||||
def _init():
|
|
||||||
'''
|
|
||||||
Initialzies logging level and server.
|
|
||||||
'''
|
|
||||||
if environment == 'prod':
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
elif environment == 'test':
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
else:
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
return SimulationWebServer().server
|
|
||||||
|
|
||||||
|
|
||||||
app = _init()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if wsgi == 'gevent':
|
|
||||||
from gevent.wsgi import WSGIServer
|
|
||||||
http_server = WSGIServer(('', 8080), app)
|
|
||||||
http_server.serve_forever()
|
|
||||||
else:
|
|
||||||
app.run(host="0.0.0.0", port=8080)
|
|
@ -1,10 +0,0 @@
|
|||||||
FROM ubuntu:16.04
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install -y git
|
|
||||||
|
|
||||||
RUN mkdir /app
|
|
||||||
WORKDIR /app
|
|
||||||
RUN git clone --recursive https://${MEMGRAPH_PULL_USER}:${MEMGRAPH_PULL_PASS}@phabricator.tomicevic.com/diffusion/MG/memgraph.git
|
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
|
@ -1,19 +0,0 @@
|
|||||||
Cython==0.23.4
|
|
||||||
Flask==0.10.1
|
|
||||||
Jinja2==2.8
|
|
||||||
MarkupSafe==0.23
|
|
||||||
Werkzeug==0.11.4
|
|
||||||
decorator==4.0.9
|
|
||||||
gevent==1.1rc5
|
|
||||||
greenlet==0.4.9
|
|
||||||
ipython==4.1.1
|
|
||||||
ipython-genutils==0.1.0
|
|
||||||
itsdangerous==0.24
|
|
||||||
path.py==8.1.2
|
|
||||||
pexpect==4.0.1
|
|
||||||
pickleshare==0.6
|
|
||||||
ptyprocess==0.5.1
|
|
||||||
requests==2.9.1
|
|
||||||
simplegeneric==0.8.1
|
|
||||||
traitlets==4.1.0
|
|
||||||
uWSGI==2.0.12
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
docker stop memgraph_demo
|
|
||||||
docker rm memgraph_demo
|
|
||||||
docker run -it --rm memgraph_demo
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
docker stop neo4j_server
|
|
||||||
docker rm neo4j_server
|
|
||||||
docker run -d --name neo4j_server --net=host -p 7474:7474 neo4j
|
|
@ -1,7 +0,0 @@
|
|||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
# TODO init object by module name
|
|
||||||
|
|
||||||
# something like
|
|
||||||
# module_name, class_name = module_class_string.rsplit(".", 1)
|
|
||||||
# return getattr(importlib.import_module(module_name), class_name)()
|
|
@ -1,26 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationEpochResult(object):
|
|
||||||
'''
|
|
||||||
Encapsulates single epoch result.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, per_query, for_all):
|
|
||||||
'''
|
|
||||||
Sets per_query and for_all results.
|
|
||||||
|
|
||||||
:param per_query: list of SimulationGroupResult objects
|
|
||||||
:param for_all: float, queries per second
|
|
||||||
'''
|
|
||||||
self.per_query = per_query
|
|
||||||
self.for_all = for_all
|
|
||||||
|
|
||||||
def json_data(self):
|
|
||||||
'''
|
|
||||||
:returns: dict, epoch results
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
"per_query": [item.json_data() for item in self.per_query],
|
|
||||||
"for_all": self.for_all
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import itertools
|
|
||||||
import http
|
|
||||||
from concurrent.futures import ProcessPoolExecutor
|
|
||||||
|
|
||||||
from .substitutor import substitute
|
|
||||||
from .epoch_result import SimulationEpochResult
|
|
||||||
from .group_result import SimulationGroupResult
|
|
||||||
from .iteration_result import SimulationIterationResult
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_qps(results, delta_t=None):
|
|
||||||
'''
|
|
||||||
Calculates queris per second for the results list. The main idea
|
|
||||||
is to scale up results to the result with the biggest execution time.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
Let say that 2 workers execute the same query. First worker
|
|
||||||
executes 100 queries in 1s, second worker executes 10 queries in 10s.
|
|
||||||
In that case first query result has to be scaled up.
|
|
||||||
Here we are goint to make aproximation, if first worker
|
|
||||||
execution time was 10s, it would execute 1000 queries.
|
|
||||||
So, the total speed is (1000q + 10q) / 10s = 101 qps. In that case
|
|
||||||
101 would be returned from this function.
|
|
||||||
|
|
||||||
:param results: list of SimulationIterationResult objects
|
|
||||||
:returns: queries per second result calculated on the input list
|
|
||||||
'''
|
|
||||||
min_start_time = min([result.start_time for result in results])
|
|
||||||
max_end_time = max([result.end_time for result in results])
|
|
||||||
delta_t = max_end_time - min_start_time
|
|
||||||
qps = sum([result.count for result in results]) / delta_t
|
|
||||||
return qps
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationExecutor(object):
|
|
||||||
'''
|
|
||||||
The main executor object. Every period the instance of this class
|
|
||||||
will execute all queries, collect the results and calculate speed of
|
|
||||||
queries execution.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def setup(self, params):
|
|
||||||
'''
|
|
||||||
Setup params and initialize the workers pool.
|
|
||||||
|
|
||||||
:param params: SimulationParams object
|
|
||||||
'''
|
|
||||||
self.params = params
|
|
||||||
self.pool = ProcessPoolExecutor
|
|
||||||
return self
|
|
||||||
|
|
||||||
def send(self, connection, query):
|
|
||||||
'''
|
|
||||||
Sends the query to the graph database.
|
|
||||||
|
|
||||||
:param connection: http.client.HTTPConnection
|
|
||||||
:param query: str, query string
|
|
||||||
'''
|
|
||||||
body = json.dumps({'statements': [{'statement': substitute(query)}]})
|
|
||||||
headers = {
|
|
||||||
'Authorization': self.params.authorization,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
connection.request('POST', '/db/data/transaction/commit',
|
|
||||||
body=body, headers=headers)
|
|
||||||
response = connection.getresponse()
|
|
||||||
log.debug('New response: %s' % response.read())
|
|
||||||
|
|
||||||
def iteration(self, task):
|
|
||||||
'''
|
|
||||||
Executes the task. Task encapsulates the informations about query.
|
|
||||||
The task is smallest piece of work and this method will try to execute
|
|
||||||
queries (one query, more times) from the task as fastest as possible.
|
|
||||||
Execution time of this method is constrained with the period_time time.
|
|
||||||
|
|
||||||
:param task: instance of SimulationTask class.
|
|
||||||
:returns: SimulationIterationResult
|
|
||||||
'''
|
|
||||||
count = 0
|
|
||||||
delta_t = 0
|
|
||||||
|
|
||||||
log.debug("New worker with PID: %s" % os.getpid())
|
|
||||||
|
|
||||||
connection = http.client.HTTPConnection(
|
|
||||||
self.params.host, self.params.port)
|
|
||||||
connection.connect()
|
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
for i in range(self.params.queries_per_period):
|
|
||||||
|
|
||||||
# send the query on execution
|
|
||||||
self.send(connection, task.query)
|
|
||||||
|
|
||||||
# calculate delta time
|
|
||||||
end_time = time.time()
|
|
||||||
delta_t = end_time - start_time
|
|
||||||
|
|
||||||
count = count + 1
|
|
||||||
|
|
||||||
if delta_t > self.params.period_time:
|
|
||||||
break
|
|
||||||
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
return SimulationIterationResult(task.id, count, start_time, end_time)
|
|
||||||
|
|
||||||
def epoch(self):
|
|
||||||
'''
|
|
||||||
Single simulation epoc. All workers are going to execute
|
|
||||||
their queries in the period that is period_time seconds length.
|
|
||||||
'''
|
|
||||||
log.debug('epoch')
|
|
||||||
|
|
||||||
max_workers = self.params.workers_per_query * len(self.params.tasks)
|
|
||||||
|
|
||||||
with self.pool(max_workers=max_workers) as executor:
|
|
||||||
|
|
||||||
log.debug('pool iter')
|
|
||||||
|
|
||||||
# execute all tasks
|
|
||||||
start_time = time.time()
|
|
||||||
futures = [executor.submit(self.iteration, task)
|
|
||||||
for task in self.params.tasks
|
|
||||||
for i in range(self.params.workers_per_query)]
|
|
||||||
results = [future.result() for future in futures]
|
|
||||||
end_time = time.time()
|
|
||||||
epoch_time = end_time - start_time
|
|
||||||
log.info("Total epoch time: %s" % epoch_time)
|
|
||||||
|
|
||||||
# per query calculation
|
|
||||||
grouped = itertools.groupby(results, lambda x: x.id)
|
|
||||||
per_query = [SimulationGroupResult(id, calculate_qps(list(tasks)))
|
|
||||||
for id, tasks in grouped]
|
|
||||||
|
|
||||||
# for all calculation
|
|
||||||
for_all = calculate_qps(results)
|
|
||||||
|
|
||||||
log.info('Queries per period: %s' % sum([r.count
|
|
||||||
for r in results]))
|
|
||||||
|
|
||||||
return SimulationEpochResult(per_query, for_all)
|
|
@ -1,25 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationGroupResult(object):
|
|
||||||
'''
|
|
||||||
Encapsulates query per seconds information for qroup of workers
|
|
||||||
(all workers that execute the same query).
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, id, queries_per_second):
|
|
||||||
'''
|
|
||||||
:param id: str, query id
|
|
||||||
:param queries_per_second: float, queries per second
|
|
||||||
'''
|
|
||||||
self.id = id
|
|
||||||
self.queries_per_second = queries_per_second
|
|
||||||
|
|
||||||
def json_data(self):
|
|
||||||
'''
|
|
||||||
:returns: dict, {query_id(str):queries_per_second(float)}
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
'id': self.id,
|
|
||||||
'queries_per_second': self.queries_per_second
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationIterationResult(object):
|
|
||||||
'''
|
|
||||||
Encapsulates single worker result.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, id, count, start_time, end_time):
|
|
||||||
'''
|
|
||||||
:param id: str, query id
|
|
||||||
:param count: int, number of the query exection
|
|
||||||
:param delta_t: time of execution
|
|
||||||
'''
|
|
||||||
self.id = id
|
|
||||||
self.count = count
|
|
||||||
self.start_time = start_time
|
|
||||||
self.end_time = end_time
|
|
||||||
self.delta_t = end_time - start_time
|
|
||||||
self.queries_per_second = self.count / self.delta_t
|
|
||||||
|
|
||||||
def json_data(self):
|
|
||||||
'''
|
|
||||||
:returns: dict {query_id(str):queries_per_second(float)}
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"queries_per_second": self.queries_per_second
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import logging
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationParams(object):
|
|
||||||
'''
|
|
||||||
Encapsulates the simulation params.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
'''
|
|
||||||
Setup default params values.
|
|
||||||
'''
|
|
||||||
self.protocol = 'http'
|
|
||||||
self.host = 'localhost'
|
|
||||||
self.port = 7474
|
|
||||||
self.username = ''
|
|
||||||
self.password = ''
|
|
||||||
|
|
||||||
self.period_time = 0.5
|
|
||||||
self.workers_per_query = 1
|
|
||||||
self.queries_per_second = 15000
|
|
||||||
self.recalculate_qpp()
|
|
||||||
|
|
||||||
self.tasks = []
|
|
||||||
|
|
||||||
def json_data(self):
|
|
||||||
'''
|
|
||||||
:returns: dict with all param values
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
"protocol": self.protocol,
|
|
||||||
"host": self.host,
|
|
||||||
"port": self.port,
|
|
||||||
"username": self.username,
|
|
||||||
"password": self.password,
|
|
||||||
"period_time": self.period_time,
|
|
||||||
"workers_per_query": self.workers_per_query,
|
|
||||||
"queries_per_period": self.queries_per_period,
|
|
||||||
"queries_per_second": self.queries_per_second
|
|
||||||
}
|
|
||||||
|
|
||||||
# protocol
|
|
||||||
@property
|
|
||||||
def protocol(self):
|
|
||||||
return self._protocol
|
|
||||||
|
|
||||||
@protocol.setter
|
|
||||||
def protocol(self, value):
|
|
||||||
self._protocol = value
|
|
||||||
|
|
||||||
# host
|
|
||||||
@property
|
|
||||||
def host(self):
|
|
||||||
return self._host
|
|
||||||
|
|
||||||
@host.setter
|
|
||||||
def host(self, value):
|
|
||||||
self._host = value
|
|
||||||
|
|
||||||
# port
|
|
||||||
@property
|
|
||||||
def port(self):
|
|
||||||
return self._port
|
|
||||||
|
|
||||||
@port.setter
|
|
||||||
def port(self, value):
|
|
||||||
self._port = value
|
|
||||||
|
|
||||||
# username
|
|
||||||
@property
|
|
||||||
def username(self):
|
|
||||||
return self._username
|
|
||||||
|
|
||||||
@username.setter
|
|
||||||
def username(self, value):
|
|
||||||
self._username = value
|
|
||||||
log.info("Username is now: %s" % self._username)
|
|
||||||
self.http_basic()
|
|
||||||
|
|
||||||
# password
|
|
||||||
@property
|
|
||||||
def password(self):
|
|
||||||
return self._password
|
|
||||||
|
|
||||||
@password.setter
|
|
||||||
def password(self, value):
|
|
||||||
self._password = value
|
|
||||||
log.info("Password is now: %s" % self._password)
|
|
||||||
self.http_basic()
|
|
||||||
|
|
||||||
def http_basic(self):
|
|
||||||
'''
|
|
||||||
Recalculates http authorization header.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
encoded = base64.b64encode(
|
|
||||||
str.encode(self.username + ":" + self.password))
|
|
||||||
self.authorization = "Basic " + encoded.decode()
|
|
||||||
log.info("Authorization is now: %s" % self.authorization)
|
|
||||||
except AttributeError:
|
|
||||||
log.debug("Username or password isn't defined.")
|
|
||||||
except Exception as e:
|
|
||||||
log.exception(e)
|
|
||||||
|
|
||||||
# authorization
|
|
||||||
@property
|
|
||||||
def authorization(self):
|
|
||||||
return self._authorization
|
|
||||||
|
|
||||||
@authorization.setter
|
|
||||||
def authorization(self, value):
|
|
||||||
self._authorization = value
|
|
||||||
|
|
||||||
# workers per query
|
|
||||||
@property
|
|
||||||
def workers_per_query(self):
|
|
||||||
return self._workers_per_query
|
|
||||||
|
|
||||||
@workers_per_query.setter
|
|
||||||
def workers_per_query(self, value):
|
|
||||||
self._workers_per_query = value
|
|
||||||
|
|
||||||
# queries per second
|
|
||||||
@property
|
|
||||||
def queries_per_second(self):
|
|
||||||
return self._queries_per_second
|
|
||||||
|
|
||||||
@queries_per_second.setter
|
|
||||||
def queries_per_second(self, value):
|
|
||||||
self._queries_per_second = value
|
|
||||||
self.recalculate_qpp()
|
|
||||||
|
|
||||||
def recalculate_qpp(self):
|
|
||||||
try:
|
|
||||||
self.queries_per_period = \
|
|
||||||
int(self.queries_per_second * self.period_time)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# queries per period
|
|
||||||
@property
|
|
||||||
def queries_per_period(self):
|
|
||||||
return self._queries_per_period
|
|
||||||
|
|
||||||
@queries_per_period.setter
|
|
||||||
def queries_per_period(self, value):
|
|
||||||
self._queries_per_period = value
|
|
||||||
|
|
||||||
# period time
|
|
||||||
@property
|
|
||||||
def period_time(self):
|
|
||||||
return self._period_time
|
|
||||||
|
|
||||||
@period_time.setter
|
|
||||||
def period_time(self, value):
|
|
||||||
self._period_time = value
|
|
||||||
self.recalculate_qpp()
|
|
@ -1,77 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
|
|
||||||
char_options = string.ascii_lowercase + string.digits
|
|
||||||
|
|
||||||
|
|
||||||
def random_integer(start=0, end=sys.maxsize):
|
|
||||||
'''
|
|
||||||
:returns: random integer between start and end
|
|
||||||
'''
|
|
||||||
return random.randint(start, end)
|
|
||||||
|
|
||||||
|
|
||||||
def random_float(start=0, end=1):
|
|
||||||
'''
|
|
||||||
:returns: random float between start and end (uniform distribution)
|
|
||||||
'''
|
|
||||||
return random.uniform(start, end)
|
|
||||||
|
|
||||||
|
|
||||||
def random_bool():
|
|
||||||
'''
|
|
||||||
:returns: random bool value
|
|
||||||
'''
|
|
||||||
return bool(random.getrandbits(1))
|
|
||||||
|
|
||||||
|
|
||||||
def random_string(size=5):
|
|
||||||
'''
|
|
||||||
:param size: int, string size
|
|
||||||
|
|
||||||
:returns: random string of specific size build from ascii_lowercase chars
|
|
||||||
and digits
|
|
||||||
'''
|
|
||||||
return ''.join([random.choice(char_options)
|
|
||||||
for _ in range(size)])
|
|
||||||
|
|
||||||
|
|
||||||
placeholders = {
|
|
||||||
'#': random_integer,
|
|
||||||
'@': random_float,
|
|
||||||
'*': random_bool,
|
|
||||||
'^': random_string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def substitute(text='', placeholders=placeholders):
|
|
||||||
'''
|
|
||||||
Substitutes chars in text with values generated from functions placed
|
|
||||||
in placeholders dict.
|
|
||||||
|
|
||||||
:param text: str, substitutable text
|
|
||||||
:param placeholders: dict, key is char that will be substituted, value
|
|
||||||
is function that is going to be used to generate
|
|
||||||
a new value
|
|
||||||
'''
|
|
||||||
return ''.join((c if c not in placeholders else str(placeholders[c]())
|
|
||||||
for c in iter(text)))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
def test_1():
|
|
||||||
print([f() for f in [random_integer, random_float,
|
|
||||||
random_bool, random_string]])
|
|
||||||
|
|
||||||
def test_2():
|
|
||||||
return substitute('int # float @ bool * string ^')
|
|
||||||
|
|
||||||
def test_3():
|
|
||||||
print(test_2())
|
|
||||||
|
|
||||||
test_3()
|
|
@ -1,24 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationTask(object):
|
|
||||||
'''
|
|
||||||
Encapsulates query data.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, id, query):
|
|
||||||
'''
|
|
||||||
:param id: query id
|
|
||||||
:param query: str, query string
|
|
||||||
'''
|
|
||||||
self.id = id
|
|
||||||
self.query = query
|
|
||||||
|
|
||||||
def json_data(self):
|
|
||||||
'''
|
|
||||||
:returns: dict with all elements
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"query": self.query
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
from flask import Flask, request, jsonify
|
|
||||||
|
|
||||||
from .executor import SimulationExecutor
|
|
||||||
from .params import SimulationParams
|
|
||||||
from .task import SimulationTask
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationWebServer(object):
|
|
||||||
'''
|
|
||||||
Memgraph demo fontend server. For now it wraps the flask server.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
'''
|
|
||||||
Instantiates the flask web server.
|
|
||||||
'''
|
|
||||||
self.is_simulation_running = False
|
|
||||||
self.simulation_stats = None
|
|
||||||
self.simulation_params = SimulationParams()
|
|
||||||
self.simulation_executor = SimulationExecutor()
|
|
||||||
self.server = Flask(__name__)
|
|
||||||
self.setup_routes()
|
|
||||||
self.server.before_first_request(self.before_first_request)
|
|
||||||
|
|
||||||
def setup_routes(self):
|
|
||||||
'''
|
|
||||||
Setup all routes.
|
|
||||||
'''
|
|
||||||
self.add_route('/ping', self.ping, 'GET')
|
|
||||||
self.add_route('/', self.index, 'GET')
|
|
||||||
self.add_route('/<path:path>', self.static, 'GET')
|
|
||||||
self.add_route('/tasks', self.tasks_get, 'GET')
|
|
||||||
self.add_route('/tasks', self.tasks_set, 'POST')
|
|
||||||
self.add_route('/start', self.start, 'POST')
|
|
||||||
self.add_route('/stop', self.stop, 'POST')
|
|
||||||
self.add_route('/stats', self.stats, 'GET')
|
|
||||||
self.add_route('/params', self.params_get, 'GET')
|
|
||||||
self.add_route('/params', self.params_set, 'POST')
|
|
||||||
|
|
||||||
def before_first_request(self):
|
|
||||||
'''
|
|
||||||
Initializes simulation executor before first request.
|
|
||||||
'''
|
|
||||||
log.info('before first request')
|
|
||||||
self.simulation_executor.setup(self.simulation_params)
|
|
||||||
|
|
||||||
def add_route(self, route, code_method, http_method):
|
|
||||||
'''
|
|
||||||
Registers URL rule
|
|
||||||
|
|
||||||
:param route: str, route string
|
|
||||||
:param object_method: object method responsible for the
|
|
||||||
request handling
|
|
||||||
:param http_method: name of http method
|
|
||||||
'''
|
|
||||||
self.server.add_url_rule(route, '%s_%s' % (route, http_method),
|
|
||||||
code_method, methods=[http_method])
|
|
||||||
|
|
||||||
def ping(self):
|
|
||||||
'''
|
|
||||||
Ping endpoint. Returns 204 HTTP status code.
|
|
||||||
'''
|
|
||||||
return ('', 204)
|
|
||||||
|
|
||||||
def index(self):
|
|
||||||
'''
|
|
||||||
Serves demo.html on the index path.
|
|
||||||
'''
|
|
||||||
print('index')
|
|
||||||
return self.server.send_static_file('demo.html')
|
|
||||||
|
|
||||||
def static(self, path):
|
|
||||||
'''
|
|
||||||
Serves other static files.
|
|
||||||
'''
|
|
||||||
return self.server.send_static_file(path)
|
|
||||||
|
|
||||||
def tasks_get(self):
|
|
||||||
'''
|
|
||||||
Retutns all defined tasks.
|
|
||||||
'''
|
|
||||||
return json.dumps(
|
|
||||||
[task.json_data() for task in self.simulation_params.tasks]
|
|
||||||
)
|
|
||||||
|
|
||||||
def tasks_set(self):
|
|
||||||
'''
|
|
||||||
Register tasks. Task is object that encapsulates single query data.
|
|
||||||
'''
|
|
||||||
data = request.get_json()['data']
|
|
||||||
|
|
||||||
self.simulation_params.tasks = \
|
|
||||||
[SimulationTask(item['id'], item['query'])
|
|
||||||
for item in data]
|
|
||||||
|
|
||||||
return ('', 200)
|
|
||||||
|
|
||||||
def run_simulation(self):
|
|
||||||
'''
|
|
||||||
If flag is_simulation_running flag is up (True) the executor
|
|
||||||
epoch will be executed. Epochs will be executed until somebody
|
|
||||||
set is_simulation_running flag to Flase.
|
|
||||||
'''
|
|
||||||
log.info('new simulation run')
|
|
||||||
|
|
||||||
while self.is_simulation_running:
|
|
||||||
self.simulation_stats = self.simulation_executor.epoch()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
'''
|
|
||||||
Starts new executor epoch in separate thread.
|
|
||||||
'''
|
|
||||||
self.is_simulation_running = True
|
|
||||||
t = threading.Thread(target=self.run_simulation, daemon=True)
|
|
||||||
t.start()
|
|
||||||
return ('', 204)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
'''
|
|
||||||
On POST /stop, stops the executor. The is not immediately, first
|
|
||||||
the is_simulation_running flag is set to False value, and next
|
|
||||||
epoc of executor won't be executed.
|
|
||||||
'''
|
|
||||||
self.is_simulation_running = False
|
|
||||||
return ('', 204)
|
|
||||||
|
|
||||||
def stats(self):
|
|
||||||
'''
|
|
||||||
Returns the simulation stats. Queries per second.
|
|
||||||
'''
|
|
||||||
if not self.simulation_stats:
|
|
||||||
return ('', 204)
|
|
||||||
|
|
||||||
return jsonify(self.simulation_stats.json_data())
|
|
||||||
|
|
||||||
def params_get(self):
|
|
||||||
'''
|
|
||||||
Returns simulation parameters.
|
|
||||||
'''
|
|
||||||
return jsonify(self.simulation_params.json_data())
|
|
||||||
|
|
||||||
def params_set(self):
|
|
||||||
'''
|
|
||||||
Sets simulation parameters.
|
|
||||||
'''
|
|
||||||
data = request.get_json()
|
|
||||||
|
|
||||||
param_names = ['protocol', 'host', 'port', 'username', 'password',
|
|
||||||
'period_time', 'queries_per_second',
|
|
||||||
'workers_per_query']
|
|
||||||
|
|
||||||
for param in param_names:
|
|
||||||
if param in data:
|
|
||||||
setattr(self.simulation_params, param, data[param])
|
|
||||||
|
|
||||||
return self.params_get()
|
|
8
src/demo/static/cypher/.gitattributes
vendored
8
src/demo/static/cypher/.gitattributes
vendored
@ -1,8 +0,0 @@
|
|||||||
*.txt text
|
|
||||||
*.js text
|
|
||||||
*.html text
|
|
||||||
*.md text
|
|
||||||
*.json text
|
|
||||||
*.yml text
|
|
||||||
*.css text
|
|
||||||
*.svg text
|
|
8
src/demo/static/cypher/.gitignore
vendored
8
src/demo/static/cypher/.gitignore
vendored
@ -1,8 +0,0 @@
|
|||||||
/node_modules
|
|
||||||
/npm-debug.log
|
|
||||||
/test*.html
|
|
||||||
.tern-*
|
|
||||||
*~
|
|
||||||
*.swp
|
|
||||||
.idea
|
|
||||||
*.iml
|
|
@ -1,10 +0,0 @@
|
|||||||
/node_modules
|
|
||||||
/demo
|
|
||||||
/doc
|
|
||||||
/test
|
|
||||||
/test*.html
|
|
||||||
/index.html
|
|
||||||
/mode/*/*test.js
|
|
||||||
/mode/*/*.html
|
|
||||||
/mode/index.html
|
|
||||||
.*
|
|
@ -1,4 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- stable
|
|
||||||
sudo: false
|
|
@ -1,335 +0,0 @@
|
|||||||
/* BASICS */
|
|
||||||
|
|
||||||
.CodeMirror {
|
|
||||||
/* Set height, width, borders, and global font properties here */
|
|
||||||
font-family: monospace;
|
|
||||||
height: 300px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PADDING */
|
|
||||||
|
|
||||||
.CodeMirror-lines {
|
|
||||||
padding: 4px 0; /* Vertical padding around content */
|
|
||||||
}
|
|
||||||
.CodeMirror pre {
|
|
||||||
padding: 0 4px; /* Horizontal padding of content */
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
|
||||||
background-color: white; /* The little square between H and V scrollbars */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* GUTTER */
|
|
||||||
|
|
||||||
.CodeMirror-gutters {
|
|
||||||
border-right: 1px solid #ddd;
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.CodeMirror-linenumbers {}
|
|
||||||
.CodeMirror-linenumber {
|
|
||||||
padding: 0 3px 0 5px;
|
|
||||||
min-width: 20px;
|
|
||||||
text-align: right;
|
|
||||||
color: #999;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-guttermarker { color: black; }
|
|
||||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
|
||||||
|
|
||||||
/* CURSOR */
|
|
||||||
|
|
||||||
.CodeMirror-cursor {
|
|
||||||
border-left: 1px solid black;
|
|
||||||
border-right: none;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
/* Shown when moving in bi-directional text */
|
|
||||||
.CodeMirror div.CodeMirror-secondarycursor {
|
|
||||||
border-left: 1px solid silver;
|
|
||||||
}
|
|
||||||
.cm-fat-cursor .CodeMirror-cursor {
|
|
||||||
width: auto;
|
|
||||||
border: 0;
|
|
||||||
background: #7e7;
|
|
||||||
}
|
|
||||||
.cm-fat-cursor div.CodeMirror-cursors {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-animate-fat-cursor {
|
|
||||||
width: auto;
|
|
||||||
border: 0;
|
|
||||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
|
||||||
-moz-animation: blink 1.06s steps(1) infinite;
|
|
||||||
animation: blink 1.06s steps(1) infinite;
|
|
||||||
background-color: #7e7;
|
|
||||||
}
|
|
||||||
@-moz-keyframes blink {
|
|
||||||
0% {}
|
|
||||||
50% { background-color: transparent; }
|
|
||||||
100% {}
|
|
||||||
}
|
|
||||||
@-webkit-keyframes blink {
|
|
||||||
0% {}
|
|
||||||
50% { background-color: transparent; }
|
|
||||||
100% {}
|
|
||||||
}
|
|
||||||
@keyframes blink {
|
|
||||||
0% {}
|
|
||||||
50% { background-color: transparent; }
|
|
||||||
100% {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Can style cursor different in overwrite (non-insert) mode */
|
|
||||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
|
||||||
|
|
||||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
|
||||||
|
|
||||||
.CodeMirror-ruler {
|
|
||||||
border-left: 1px solid #ccc;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DEFAULT THEME */
|
|
||||||
|
|
||||||
.cm-s-default .cm-header {color: blue;}
|
|
||||||
.cm-s-default .cm-quote {color: #090;}
|
|
||||||
.cm-negative {color: #d44;}
|
|
||||||
.cm-positive {color: #292;}
|
|
||||||
.cm-header, .cm-strong {font-weight: bold;}
|
|
||||||
.cm-em {font-style: italic;}
|
|
||||||
.cm-link {text-decoration: underline;}
|
|
||||||
.cm-strikethrough {text-decoration: line-through;}
|
|
||||||
|
|
||||||
.cm-s-default .cm-keyword {color: #708;}
|
|
||||||
.cm-s-default .cm-atom {color: #219;}
|
|
||||||
.cm-s-default .cm-number {color: #164;}
|
|
||||||
.cm-s-default .cm-def {color: #00f;}
|
|
||||||
.cm-s-default .cm-variable,
|
|
||||||
.cm-s-default .cm-punctuation,
|
|
||||||
.cm-s-default .cm-property,
|
|
||||||
.cm-s-default .cm-operator {}
|
|
||||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
|
||||||
.cm-s-default .cm-variable-3 {color: #085;}
|
|
||||||
.cm-s-default .cm-comment {color: #a50;}
|
|
||||||
.cm-s-default .cm-string {color: #a11;}
|
|
||||||
.cm-s-default .cm-string-2 {color: #f50;}
|
|
||||||
.cm-s-default .cm-meta {color: #555;}
|
|
||||||
.cm-s-default .cm-qualifier {color: #555;}
|
|
||||||
.cm-s-default .cm-builtin {color: #30a;}
|
|
||||||
.cm-s-default .cm-bracket {color: #997;}
|
|
||||||
.cm-s-default .cm-tag {color: #170;}
|
|
||||||
.cm-s-default .cm-attribute {color: #00c;}
|
|
||||||
.cm-s-default .cm-hr {color: #999;}
|
|
||||||
.cm-s-default .cm-link {color: #00c;}
|
|
||||||
|
|
||||||
.cm-s-default .cm-error {color: #f00;}
|
|
||||||
.cm-invalidchar {color: #f00;}
|
|
||||||
|
|
||||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
|
||||||
|
|
||||||
/* Default styles for common addons */
|
|
||||||
|
|
||||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
|
||||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
|
||||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
|
||||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
|
||||||
|
|
||||||
/* STOP */
|
|
||||||
|
|
||||||
/* The rest of this file contains styles related to the mechanics of
|
|
||||||
the editor. You probably shouldn't touch them. */
|
|
||||||
|
|
||||||
.CodeMirror {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
|
||||||
overflow: scroll !important; /* Things will break if this is overridden */
|
|
||||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
|
||||||
/* See overflow: hidden in .CodeMirror */
|
|
||||||
margin-bottom: -30px; margin-right: -30px;
|
|
||||||
padding-bottom: 30px;
|
|
||||||
height: 100%;
|
|
||||||
outline: none; /* Prevent dragging from highlighting the element */
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.CodeMirror-sizer {
|
|
||||||
position: relative;
|
|
||||||
border-right: 30px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
|
||||||
before actual scrolling happens, thus preventing shaking and
|
|
||||||
flickering artifacts. */
|
|
||||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 6;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.CodeMirror-vscrollbar {
|
|
||||||
right: 0; top: 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
.CodeMirror-hscrollbar {
|
|
||||||
bottom: 0; left: 0;
|
|
||||||
overflow-y: hidden;
|
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
|
||||||
.CodeMirror-scrollbar-filler {
|
|
||||||
right: 0; bottom: 0;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-filler {
|
|
||||||
left: 0; bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-gutters {
|
|
||||||
position: absolute; left: 0; top: 0;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter {
|
|
||||||
white-space: normal;
|
|
||||||
height: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
margin-bottom: -30px;
|
|
||||||
/* Hack to make IE7 behave */
|
|
||||||
*zoom:1;
|
|
||||||
*display:inline;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 4;
|
|
||||||
background: none !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0; bottom: 0;
|
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-elt {
|
|
||||||
position: absolute;
|
|
||||||
cursor: default;
|
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutter-wrapper {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-lines {
|
|
||||||
cursor: text;
|
|
||||||
min-height: 1px; /* prevents collapsing before first draw */
|
|
||||||
}
|
|
||||||
.CodeMirror pre {
|
|
||||||
/* Reset some styles that the rest of the page might have set */
|
|
||||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
|
||||||
border-width: 0;
|
|
||||||
background: transparent;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre;
|
|
||||||
word-wrap: normal;
|
|
||||||
line-height: inherit;
|
|
||||||
color: inherit;
|
|
||||||
z-index: 2;
|
|
||||||
position: relative;
|
|
||||||
overflow: visible;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
.CodeMirror-wrap pre {
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-linebackground {
|
|
||||||
position: absolute;
|
|
||||||
left: 0; right: 0; top: 0; bottom: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-linewidget {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-widget {}
|
|
||||||
|
|
||||||
.CodeMirror-code {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Force content-box sizing for the elements where we expect it */
|
|
||||||
.CodeMirror-scroll,
|
|
||||||
.CodeMirror-sizer,
|
|
||||||
.CodeMirror-gutter,
|
|
||||||
.CodeMirror-gutters,
|
|
||||||
.CodeMirror-linenumber {
|
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-measure {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-cursor { position: absolute; }
|
|
||||||
.CodeMirror-measure pre { position: static; }
|
|
||||||
|
|
||||||
div.CodeMirror-cursors {
|
|
||||||
visibility: hidden;
|
|
||||||
position: relative;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
div.CodeMirror-dragcursors {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-focused div.CodeMirror-cursors {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-selected { background: #d9d9d9; }
|
|
||||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
|
||||||
.CodeMirror-crosshair { cursor: crosshair; }
|
|
||||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
|
||||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
|
||||||
|
|
||||||
.cm-searching {
|
|
||||||
background: #ffa;
|
|
||||||
background: rgba(255, 255, 0, .4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
|
||||||
.CodeMirror span { *vertical-align: text-bottom; }
|
|
||||||
|
|
||||||
/* Used to force a border model for a node */
|
|
||||||
.cm-force-border { padding-right: .1px; }
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
/* Hide the cursor when printing */
|
|
||||||
.CodeMirror div.CodeMirror-cursors {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See issue #2901 */
|
|
||||||
.cm-tab-wrap-hack:after { content: ''; }
|
|
||||||
|
|
||||||
/* Help users use markselection to safely style text background */
|
|
||||||
span.CodeMirror-selectedtext { background: none; }
|
|
File diff suppressed because it is too large
Load Diff
@ -1,146 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
// By the Neo4j Team and contributors.
|
|
||||||
// https://github.com/neo4j-contrib/CodeMirror
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
var wordRegexp = function(words) {
|
|
||||||
return new RegExp("^(?:" + words.join("|") + ")$", "i");
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.defineMode("cypher", function(config) {
|
|
||||||
var tokenBase = function(stream/*, state*/) {
|
|
||||||
var ch = stream.next();
|
|
||||||
if (ch === "\"" || ch === "'") {
|
|
||||||
stream.match(/.+?["']/);
|
|
||||||
return "string";
|
|
||||||
}
|
|
||||||
if (/[{}\(\),\.;\[\]]/.test(ch)) {
|
|
||||||
curPunc = ch;
|
|
||||||
return "node";
|
|
||||||
} else if (ch === "/" && stream.eat("/")) {
|
|
||||||
stream.skipToEnd();
|
|
||||||
return "comment";
|
|
||||||
} else if (operatorChars.test(ch)) {
|
|
||||||
stream.eatWhile(operatorChars);
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
stream.eatWhile(/[_\w\d]/);
|
|
||||||
if (stream.eat(":")) {
|
|
||||||
stream.eatWhile(/[\w\d_\-]/);
|
|
||||||
return "atom";
|
|
||||||
}
|
|
||||||
var word = stream.current();
|
|
||||||
if (funcs.test(word)) return "builtin";
|
|
||||||
if (preds.test(word)) return "def";
|
|
||||||
if (keywords.test(word)) return "keyword";
|
|
||||||
return "variable";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var pushContext = function(state, type, col) {
|
|
||||||
return state.context = {
|
|
||||||
prev: state.context,
|
|
||||||
indent: state.indent,
|
|
||||||
col: col,
|
|
||||||
type: type
|
|
||||||
};
|
|
||||||
};
|
|
||||||
var popContext = function(state) {
|
|
||||||
state.indent = state.context.indent;
|
|
||||||
return state.context = state.context.prev;
|
|
||||||
};
|
|
||||||
var indentUnit = config.indentUnit;
|
|
||||||
var curPunc;
|
|
||||||
var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "reverse", "right", "round", "rtrim", "shortestPath", "sign", "sin", "size", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "toString", "trim", "type", "upper"]);
|
|
||||||
var preds = wordRegexp(["all", "and", "any", "contains", "exists", "has", "in", "none", "not", "or", "single", "xor"]);
|
|
||||||
var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "detach", "distinct", "drop", "else", "end", "ends", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "starts", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]);
|
|
||||||
var operatorChars = /[*+\-<>=&|~%^]/;
|
|
||||||
|
|
||||||
return {
|
|
||||||
startState: function(/*base*/) {
|
|
||||||
return {
|
|
||||||
tokenize: tokenBase,
|
|
||||||
context: null,
|
|
||||||
indent: 0,
|
|
||||||
col: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
token: function(stream, state) {
|
|
||||||
if (stream.sol()) {
|
|
||||||
if (state.context && (state.context.align == null)) {
|
|
||||||
state.context.align = false;
|
|
||||||
}
|
|
||||||
state.indent = stream.indentation();
|
|
||||||
}
|
|
||||||
if (stream.eatSpace()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var style = state.tokenize(stream, state);
|
|
||||||
if (style !== "comment" && state.context && (state.context.align == null) && state.context.type !== "pattern") {
|
|
||||||
state.context.align = true;
|
|
||||||
}
|
|
||||||
if (curPunc === "(") {
|
|
||||||
pushContext(state, ")", stream.column());
|
|
||||||
} else if (curPunc === "[") {
|
|
||||||
pushContext(state, "]", stream.column());
|
|
||||||
} else if (curPunc === "{") {
|
|
||||||
pushContext(state, "}", stream.column());
|
|
||||||
} else if (/[\]\}\)]/.test(curPunc)) {
|
|
||||||
while (state.context && state.context.type === "pattern") {
|
|
||||||
popContext(state);
|
|
||||||
}
|
|
||||||
if (state.context && curPunc === state.context.type) {
|
|
||||||
popContext(state);
|
|
||||||
}
|
|
||||||
} else if (curPunc === "." && state.context && state.context.type === "pattern") {
|
|
||||||
popContext(state);
|
|
||||||
} else if (/atom|string|variable/.test(style) && state.context) {
|
|
||||||
if (/[\}\]]/.test(state.context.type)) {
|
|
||||||
pushContext(state, "pattern", stream.column());
|
|
||||||
} else if (state.context.type === "pattern" && !state.context.align) {
|
|
||||||
state.context.align = true;
|
|
||||||
state.context.col = stream.column();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
},
|
|
||||||
indent: function(state, textAfter) {
|
|
||||||
var firstChar = textAfter && textAfter.charAt(0);
|
|
||||||
var context = state.context;
|
|
||||||
if (/[\]\}]/.test(firstChar)) {
|
|
||||||
while (context && context.type === "pattern") {
|
|
||||||
context = context.prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var closing = context && firstChar === context.type;
|
|
||||||
if (!context) return 0;
|
|
||||||
if (context.type === "keywords") return CodeMirror.commands.newlineAndIndent;
|
|
||||||
if (context.align) return context.col + (closing ? 0 : 1);
|
|
||||||
return context.indent + (closing ? 0 : indentUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.modeExtensions["cypher"] = {
|
|
||||||
autoFormatLineBreaks: function(text) {
|
|
||||||
var i, lines, reProcessedPortion;
|
|
||||||
var lines = text.split("\n");
|
|
||||||
var reProcessedPortion = /\s+\b(return|where|order by|match|with|skip|limit|create|delete|set)\b\s/g;
|
|
||||||
for (var i = 0; i < lines.length; i++)
|
|
||||||
lines[i] = lines[i].replace(reProcessedPortion, " \n$1 ").trim();
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.defineMIME("application/x-cypher-query", "cypher");
|
|
||||||
|
|
||||||
});
|
|
@ -1,43 +0,0 @@
|
|||||||
/* neo theme for codemirror */
|
|
||||||
|
|
||||||
/* Color scheme */
|
|
||||||
|
|
||||||
.cm-s-neo.CodeMirror {
|
|
||||||
background-color:#ffffff;
|
|
||||||
color:#2e383c;
|
|
||||||
line-height:1.4375;
|
|
||||||
}
|
|
||||||
.cm-s-neo .cm-comment { color:#75787b; }
|
|
||||||
.cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#1d75b3; }
|
|
||||||
.cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; }
|
|
||||||
.cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; }
|
|
||||||
.cm-s-neo .cm-string { color:#b35e14; }
|
|
||||||
.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#047d65; }
|
|
||||||
|
|
||||||
|
|
||||||
/* Editor styling */
|
|
||||||
|
|
||||||
.cm-s-neo pre {
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-neo .CodeMirror-gutters {
|
|
||||||
border:none;
|
|
||||||
border-right:10px solid transparent;
|
|
||||||
background-color:transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-neo .CodeMirror-linenumber {
|
|
||||||
padding:0;
|
|
||||||
color:#e0e2e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; }
|
|
||||||
.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; }
|
|
||||||
|
|
||||||
.cm-s-neo .CodeMirror-cursor {
|
|
||||||
width: auto;
|
|
||||||
border: 0;
|
|
||||||
background: rgba(155,157,162,0.37);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: GillSans, Calibri, Trebuchet, sans-serif;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror pre {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* label color inactive */
|
|
||||||
.input-field label {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* label color active */
|
|
||||||
.input-field input[type=text]:focus + label {
|
|
||||||
color: #007FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* label bottom border color */
|
|
||||||
.input-field input[type=text]:focus {
|
|
||||||
border-bottom: 1px solid #007FFF;
|
|
||||||
box-shadow: 0 1px 0 0 #007FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-field input[type=text] {
|
|
||||||
border-bottom: 1px solid #007FFF;
|
|
||||||
box-shadow: 0 1px 0 0 #007FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
label#hostname_port {
|
|
||||||
font: normal 15px GillSans, Calibri, Trebuchet, sans-serif !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hostname_port {
|
|
||||||
font-size: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w100 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
width: 80%;
|
|
||||||
color: #007FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror {
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg, text {
|
|
||||||
font-size: 28;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background-color: #007FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background-color: #007FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:focus {
|
|
||||||
background-color: #007FFF;
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Memgraph Demo</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="demo.css">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
|
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="cypher/codemirror.css">
|
|
||||||
<link rel="stylesheet" href="cypher/neo.css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.rawgit.com/novus/nvd3/v1.8.1/build/nv.d3.css">
|
|
||||||
<link rel="stylesheet" href="demo.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<!-- title row -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="valign-wrapper">
|
|
||||||
<div class="input-field col s2">
|
|
||||||
<input value="localhost:7474" id="neo4j_url" type="text" class="validate">
|
|
||||||
<label id="neo4j_url" for="neo4j_url">hostname:port</label>
|
|
||||||
</div>
|
|
||||||
<div class="col s2">
|
|
||||||
<button id="run-neo4j"
|
|
||||||
class="btn waves-effect waves-light"
|
|
||||||
type="submit" name="action">
|
|
||||||
Start Neo4j
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<h1 class="valign center col s4">Graph Benchmark</h1>
|
|
||||||
<div class="col s2">
|
|
||||||
<button id="run-memgraph" style="float:right;"
|
|
||||||
class="btn waves-effect waves-light right-align"
|
|
||||||
type="submit" name="action">
|
|
||||||
Start Memgraph
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="input-field col s2">
|
|
||||||
<input value="localhost:7475" id="memgraph_url" type="text" class="validate">
|
|
||||||
<label id="memgraph_url" for="memgraph_url">hostname:port</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- neo4j cards -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s2">
|
|
||||||
<div id="q-0-0" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-0-1" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-0-2" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-0-3" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-0-4" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- the graph -->
|
|
||||||
<div class="col s8">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
|
||||||
<div id="chart" style="height: 750px;">
|
|
||||||
<svg></svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- memgraph cards -->
|
|
||||||
<div class="col s2">
|
|
||||||
<div id="q-1-0" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-1-1" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-1-2" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-1-3" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
<div id="q-1-4" class="card">
|
|
||||||
<div class="qps valign-wrapper">
|
|
||||||
<div class="col s6">
|
|
||||||
<h3 id="text" class="valign center w100">0</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col s6">
|
|
||||||
<canvas id="gauge" width="150" height="60"
|
|
||||||
class="valign center"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea id="query">MATCH (n:Person) RETURN n</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
|
|
||||||
<script type="text/javascript" src="http://bernii.github.io/gauge.js/dist/gauge.min.js"></script>
|
|
||||||
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
|
||||||
<script src="https://cdn.rawgit.com/novus/nvd3/v1.8.1/build/nv.d3.min.js"></script>
|
|
||||||
<script src="cypher/codemirror.js"></script>
|
|
||||||
<script src="cypher/cypher.js"></script>
|
|
||||||
<script src="demo.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
@ -1,308 +0,0 @@
|
|||||||
(() => {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class QpsGauge {
|
|
||||||
constructor(canvas) {
|
|
||||||
this.gauge = new Gauge(canvas).setOptions({
|
|
||||||
lines: 12, // The number of lines to draw
|
|
||||||
angle: 0.07, // The length of each line
|
|
||||||
lineWidth: 0.27, // The line thickness
|
|
||||||
pointer: {
|
|
||||||
length: 0.82, // The radius of the inner circle
|
|
||||||
strokeWidth: 0.073, // The rotation offset
|
|
||||||
color: '#000' // Fill color
|
|
||||||
},
|
|
||||||
// If true, the pointer will not go past the end of the gauge
|
|
||||||
limitMax: 'true',
|
|
||||||
strokeColor: '#e0e0e0', // to see which ones work best for you
|
|
||||||
generateGradient: true,
|
|
||||||
percentColors: [[0.0, '#d32f2f'], [0.5, '#ffee58'], [1.0, '#388e3c']],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.gauge.animationSpeed = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
set(value) {
|
|
||||||
this.gauge.set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
setMax(value) {
|
|
||||||
this.gauge.maxValue = this.maxValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMax() {
|
|
||||||
return this.maxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CypherQuery {
|
|
||||||
constructor(textarea) {
|
|
||||||
this.textarea = textarea;
|
|
||||||
this.editor = CodeMirror.fromTextArea(this.textarea, {
|
|
||||||
height: 0,
|
|
||||||
lineNumbers: true,
|
|
||||||
mode: 'application/x-cypher-query',
|
|
||||||
indentWithTabs: false,
|
|
||||||
smartIndent: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
theme: 'neo',
|
|
||||||
viewportMargin: Infinity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
set(value) {
|
|
||||||
this.editor.getDoc().setValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(separator) {
|
|
||||||
separator = separator ? separator : ' ';
|
|
||||||
this.editor.getDoc().getValue(separator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QpsText {
|
|
||||||
constructor(element) {
|
|
||||||
this.element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
$(this.element).text();
|
|
||||||
}
|
|
||||||
|
|
||||||
set(text) {
|
|
||||||
$(this.element).text(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QueryCard {
|
|
||||||
constructor(card, maxQps, value) {
|
|
||||||
this.card = card;
|
|
||||||
this.maxQps = maxQps;
|
|
||||||
|
|
||||||
value = value ? value : 1;
|
|
||||||
|
|
||||||
this.text = new QpsText($(card).find('#text')[0]);
|
|
||||||
this.gauge = new QpsGauge($(card).find('#gauge')[0]);
|
|
||||||
this.query = new CypherQuery($(card).find('#query')[0]);
|
|
||||||
|
|
||||||
this.gauge.setMax(maxQps + 1);
|
|
||||||
this.gauge.animationSpeed = 1;
|
|
||||||
this.gauge.set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_value(value) {
|
|
||||||
this.text.set(value);
|
|
||||||
this.gauge.set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_query(query) {
|
|
||||||
this.query.set(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// put \n on module 2 space positions
|
|
||||||
var put_new_line_mod_2 = function(array) {
|
|
||||||
let join_array = array.map(function(o, i) {
|
|
||||||
if (i % 2 === 0)
|
|
||||||
return ' ';
|
|
||||||
else
|
|
||||||
return '\n';
|
|
||||||
});
|
|
||||||
join_array.pop();
|
|
||||||
return array.map(function(v,i) {
|
|
||||||
return [v, join_array[i]];
|
|
||||||
}).reduce(function(a,b) {
|
|
||||||
return a.concat(b);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// -- variable part ----
|
|
||||||
let running = false;
|
|
||||||
let value = 0;
|
|
||||||
let maxQps = 10000;
|
|
||||||
let memgraphCards = [];
|
|
||||||
let neo4jCards = [];
|
|
||||||
let memgraphSec = 0;
|
|
||||||
let neo4jSec = 0;
|
|
||||||
let memgraphLine = [];
|
|
||||||
let neo4jLine = [];
|
|
||||||
var queries = [
|
|
||||||
"CREATE (n:Item{id:@}) RETURN n",
|
|
||||||
"MATCH (n:Item{id:#}),(m:Item{id:#}) CREATE (n)-[r:test]->(m) RETURN r",
|
|
||||||
"MATCH (n:Item{id:#}) SET n.prop=# RETURN n",
|
|
||||||
"MATCH (n:Item{id:#}) RETURN n",
|
|
||||||
"MATCH (n:Item{id:#})-[r]->(m) RETURN count(r)"
|
|
||||||
];
|
|
||||||
var params = {
|
|
||||||
host: "localhost",
|
|
||||||
port: "7474",
|
|
||||||
connections: 2,
|
|
||||||
duration: 1,
|
|
||||||
queries: queries
|
|
||||||
};
|
|
||||||
// -----------------------
|
|
||||||
|
|
||||||
// server control functions
|
|
||||||
var start = function() {
|
|
||||||
$.ajax({url:'/start', type:"POST", success: function(data){}});
|
|
||||||
};
|
|
||||||
var stop = function() {
|
|
||||||
$.ajax({url:'/stop', type:"POST", success: function(data){}});
|
|
||||||
};
|
|
||||||
var registerParams = function(f) {
|
|
||||||
$.ajax({
|
|
||||||
url:'/params', type:"POST", data:JSON.stringify(params),
|
|
||||||
contentType:"application/json; charset=utf-8",
|
|
||||||
success: function(data){
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
registerParams();
|
|
||||||
|
|
||||||
// setup cards
|
|
||||||
queries.forEach(function(query, i) {
|
|
||||||
query = put_new_line_mod_2(query.split(" ")).join('');
|
|
||||||
neo4jCards.push(new QueryCard($('#q-0-' + i.toString())[0], maxQps));
|
|
||||||
memgraphCards.push(new QueryCard($('#q-1-' + i.toString())[0], maxQps));
|
|
||||||
neo4jCards[i].set_query(query);
|
|
||||||
memgraphCards[i].set_query(query);
|
|
||||||
});
|
|
||||||
|
|
||||||
// start stop button
|
|
||||||
$("#run-neo4j").click(function() {
|
|
||||||
running = !running;
|
|
||||||
if (running) {
|
|
||||||
$(this).text('Stop Neo4j');
|
|
||||||
let hostname_port = $("#neo4j_url").val().split(":");
|
|
||||||
params.host = hostname_port[0];
|
|
||||||
params.port = hostname_port[1];
|
|
||||||
params.connections = 2;
|
|
||||||
registerParams(function() {
|
|
||||||
start();
|
|
||||||
updateNeo4j();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!running) {
|
|
||||||
$(this).text('Start Neo4j');
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// start stop button
|
|
||||||
$("#run-memgraph").click(function() {
|
|
||||||
running = !running;
|
|
||||||
if (running) {
|
|
||||||
$(this).text('Stop Memgraph');
|
|
||||||
let hostname_port = $("#memgraph_url").val().split(":");
|
|
||||||
params.host = hostname_port[0];
|
|
||||||
params.port = hostname_port[1];
|
|
||||||
params.connections = 4;
|
|
||||||
registerParams(function() {
|
|
||||||
start();
|
|
||||||
updateMemgraph();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!running) {
|
|
||||||
$(this).text('Start Memgraph');
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// update only line on the graph
|
|
||||||
var updateGraph = function() {
|
|
||||||
let newData = [{
|
|
||||||
values: memgraphLine,
|
|
||||||
key: 'Memgraph',
|
|
||||||
color: '#ff0000',
|
|
||||||
strokeWidth: 3
|
|
||||||
}, {
|
|
||||||
values: neo4jLine,
|
|
||||||
key: 'Neo4j',
|
|
||||||
color: '#0000ff',
|
|
||||||
strokeWidth: 3
|
|
||||||
}];
|
|
||||||
chartData.datum(newData).transition().duration(500).call(chart);
|
|
||||||
};
|
|
||||||
|
|
||||||
// update
|
|
||||||
function updateNeo4j() {
|
|
||||||
if (!running) {
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
$.ajax({
|
|
||||||
url:'/stats', type:"GET",
|
|
||||||
success: function(data){
|
|
||||||
if (!data || !data.total || !data.per_query)
|
|
||||||
return;
|
|
||||||
|
|
||||||
neo4jSec = neo4jSec + 1;
|
|
||||||
neo4jLine.push({x: neo4jSec, y: data.total});
|
|
||||||
data.per_query.forEach(function(speed, i) {
|
|
||||||
neo4jCards[i].set_value(Math.round(speed));
|
|
||||||
});
|
|
||||||
updateGraph();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
updateNeo4j();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
function updateMemgraph() {
|
|
||||||
if (!running) {
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
$.ajax({
|
|
||||||
url:'/stats', type:"GET",
|
|
||||||
success: function(data){
|
|
||||||
if (!data || !data.total || !data.per_query)
|
|
||||||
return;
|
|
||||||
|
|
||||||
memgraphSec = memgraphSec + 1;
|
|
||||||
memgraphLine.push({x: memgraphSec, y: data.total});
|
|
||||||
data.per_query.forEach(function(speed, i) {
|
|
||||||
memgraphCards[i].set_value(Math.round(speed));
|
|
||||||
});
|
|
||||||
updateGraph();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
updateMemgraph();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// graph init
|
|
||||||
var chart;
|
|
||||||
var chartData;
|
|
||||||
nv.addGraph(function() {
|
|
||||||
chart = nv.models.lineChart()
|
|
||||||
.interpolate('basis')
|
|
||||||
.useInteractiveGuideline(true)
|
|
||||||
.showLegend(true)
|
|
||||||
.showYAxis(true)
|
|
||||||
.showXAxis(true);
|
|
||||||
|
|
||||||
chart.xAxis
|
|
||||||
.axisLabel('Time (s)')
|
|
||||||
.tickFormat(d3.format(',r'));
|
|
||||||
|
|
||||||
chart.yAxis
|
|
||||||
.axisLabel('QPS')
|
|
||||||
.tickFormat(d3.format('f'));
|
|
||||||
|
|
||||||
chart.forceY([0, 50000]);
|
|
||||||
|
|
||||||
chartData = d3.select('#chart svg')
|
|
||||||
.datum([]);
|
|
||||||
chartData.call(chart);
|
|
||||||
|
|
||||||
chart.update();
|
|
||||||
nv.utils.windowResize(function() { chart.update(); });
|
|
||||||
return chart;
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
@ -1,395 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.captors');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user inputs default captor. It deals with mouse events, keyboards
|
|
||||||
* events and touch events.
|
|
||||||
*
|
|
||||||
* @param {DOMElement} target The DOM element where the listeners will be
|
|
||||||
* bound.
|
|
||||||
* @param {camera} camera The camera related to the target.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
* @return {sigma.captor} The fresh new captor instance.
|
|
||||||
*/
|
|
||||||
sigma.captors.mouse = function(target, camera, settings) {
|
|
||||||
var _self = this,
|
|
||||||
_target = target,
|
|
||||||
_camera = camera,
|
|
||||||
_settings = settings,
|
|
||||||
|
|
||||||
// CAMERA MANAGEMENT:
|
|
||||||
// ******************
|
|
||||||
// The camera position when the user starts dragging:
|
|
||||||
_startCameraX,
|
|
||||||
_startCameraY,
|
|
||||||
_startCameraAngle,
|
|
||||||
|
|
||||||
// The latest stage position:
|
|
||||||
_lastCameraX,
|
|
||||||
_lastCameraY,
|
|
||||||
_lastCameraAngle,
|
|
||||||
_lastCameraRatio,
|
|
||||||
|
|
||||||
// MOUSE MANAGEMENT:
|
|
||||||
// *****************
|
|
||||||
// The mouse position when the user starts dragging:
|
|
||||||
_startMouseX,
|
|
||||||
_startMouseY,
|
|
||||||
|
|
||||||
_isMouseDown,
|
|
||||||
_isMoving,
|
|
||||||
_hasDragged,
|
|
||||||
_downStartTime,
|
|
||||||
_movingTimeoutId;
|
|
||||||
|
|
||||||
sigma.classes.dispatcher.extend(this);
|
|
||||||
|
|
||||||
sigma.utils.doubleClick(_target, 'click', _doubleClickHandler);
|
|
||||||
_target.addEventListener('DOMMouseScroll', _wheelHandler, false);
|
|
||||||
_target.addEventListener('mousewheel', _wheelHandler, false);
|
|
||||||
_target.addEventListener('mousemove', _moveHandler, false);
|
|
||||||
_target.addEventListener('mousedown', _downHandler, false);
|
|
||||||
_target.addEventListener('click', _clickHandler, false);
|
|
||||||
_target.addEventListener('mouseout', _outHandler, false);
|
|
||||||
document.addEventListener('mouseup', _upHandler, false);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method unbinds every handlers that makes the captor work.
|
|
||||||
*/
|
|
||||||
this.kill = function() {
|
|
||||||
sigma.utils.unbindDoubleClick(_target, 'click');
|
|
||||||
_target.removeEventListener('DOMMouseScroll', _wheelHandler);
|
|
||||||
_target.removeEventListener('mousewheel', _wheelHandler);
|
|
||||||
_target.removeEventListener('mousemove', _moveHandler);
|
|
||||||
_target.removeEventListener('mousedown', _downHandler);
|
|
||||||
_target.removeEventListener('click', _clickHandler);
|
|
||||||
_target.removeEventListener('mouseout', _outHandler);
|
|
||||||
document.removeEventListener('mouseup', _upHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MOUSE EVENTS:
|
|
||||||
// *************
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'move' mouse event. It will effectively
|
|
||||||
* drag the graph.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _moveHandler(e) {
|
|
||||||
var x,
|
|
||||||
y,
|
|
||||||
pos;
|
|
||||||
|
|
||||||
// Dispatch event:
|
|
||||||
if (_settings('mouseEnabled'))
|
|
||||||
_self.dispatchEvent('mousemove', {
|
|
||||||
x: sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_settings('mouseEnabled') && _isMouseDown) {
|
|
||||||
_isMoving = true;
|
|
||||||
_hasDragged = true;
|
|
||||||
|
|
||||||
if (_movingTimeoutId)
|
|
||||||
clearTimeout(_movingTimeoutId);
|
|
||||||
|
|
||||||
_movingTimeoutId = setTimeout(function() {
|
|
||||||
_isMoving = false;
|
|
||||||
}, _settings('dragTimeout'));
|
|
||||||
|
|
||||||
sigma.misc.animation.killAll(_camera);
|
|
||||||
|
|
||||||
_camera.isMoving = true;
|
|
||||||
pos = _camera.cameraPosition(
|
|
||||||
sigma.utils.getX(e) - _startMouseX,
|
|
||||||
sigma.utils.getY(e) - _startMouseY,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
x = _startCameraX - pos.x;
|
|
||||||
y = _startCameraY - pos.y;
|
|
||||||
|
|
||||||
if (x !== _camera.x || y !== _camera.y) {
|
|
||||||
_lastCameraX = _camera.x;
|
|
||||||
_lastCameraY = _camera.y;
|
|
||||||
|
|
||||||
_camera.goTo({
|
|
||||||
x: x,
|
|
||||||
y: y
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.preventDefault)
|
|
||||||
e.preventDefault();
|
|
||||||
else
|
|
||||||
e.returnValue = false;
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'up' mouse event. It will stop dragging the
|
|
||||||
* graph.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _upHandler(e) {
|
|
||||||
if (_settings('mouseEnabled') && _isMouseDown) {
|
|
||||||
_isMouseDown = false;
|
|
||||||
if (_movingTimeoutId)
|
|
||||||
clearTimeout(_movingTimeoutId);
|
|
||||||
|
|
||||||
_camera.isMoving = false;
|
|
||||||
|
|
||||||
var x = sigma.utils.getX(e),
|
|
||||||
y = sigma.utils.getY(e);
|
|
||||||
|
|
||||||
if (_isMoving) {
|
|
||||||
sigma.misc.animation.killAll(_camera);
|
|
||||||
sigma.misc.animation.camera(
|
|
||||||
_camera,
|
|
||||||
{
|
|
||||||
x: _camera.x +
|
|
||||||
_settings('mouseInertiaRatio') * (_camera.x - _lastCameraX),
|
|
||||||
y: _camera.y +
|
|
||||||
_settings('mouseInertiaRatio') * (_camera.y - _lastCameraY)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
easing: 'quadraticOut',
|
|
||||||
duration: _settings('mouseInertiaDuration')
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
_startMouseX !== x ||
|
|
||||||
_startMouseY !== y
|
|
||||||
)
|
|
||||||
_camera.goTo({
|
|
||||||
x: _camera.x,
|
|
||||||
y: _camera.y
|
|
||||||
});
|
|
||||||
|
|
||||||
_self.dispatchEvent('mouseup', {
|
|
||||||
x: x - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: y - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update _isMoving flag:
|
|
||||||
_isMoving = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'down' mouse event. It will start observing
|
|
||||||
* the mouse position for dragging the graph.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _downHandler(e) {
|
|
||||||
if (_settings('mouseEnabled')) {
|
|
||||||
_startCameraX = _camera.x;
|
|
||||||
_startCameraY = _camera.y;
|
|
||||||
|
|
||||||
_lastCameraX = _camera.x;
|
|
||||||
_lastCameraY = _camera.y;
|
|
||||||
|
|
||||||
_startMouseX = sigma.utils.getX(e);
|
|
||||||
_startMouseY = sigma.utils.getY(e);
|
|
||||||
|
|
||||||
_hasDragged = false;
|
|
||||||
_downStartTime = (new Date()).getTime();
|
|
||||||
|
|
||||||
switch (e.which) {
|
|
||||||
case 2:
|
|
||||||
// Middle mouse button pressed
|
|
||||||
// Do nothing.
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
// Right mouse button pressed
|
|
||||||
_self.dispatchEvent('rightclick', {
|
|
||||||
x: _startMouseX - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: _startMouseY - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
// case 1:
|
|
||||||
default:
|
|
||||||
// Left mouse button pressed
|
|
||||||
_isMouseDown = true;
|
|
||||||
|
|
||||||
_self.dispatchEvent('mousedown', {
|
|
||||||
x: _startMouseX - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: _startMouseY - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'out' mouse event. It will just redispatch
|
|
||||||
* the event.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _outHandler(e) {
|
|
||||||
if (_settings('mouseEnabled'))
|
|
||||||
_self.dispatchEvent('mouseout');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'click' mouse event. It will redispatch the
|
|
||||||
* click event, but with normalized X and Y coordinates.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _clickHandler(e) {
|
|
||||||
if (_settings('mouseEnabled'))
|
|
||||||
_self.dispatchEvent('click', {
|
|
||||||
x: sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey,
|
|
||||||
isDragging:
|
|
||||||
(((new Date()).getTime() - _downStartTime) > 100) &&
|
|
||||||
_hasDragged
|
|
||||||
});
|
|
||||||
|
|
||||||
if (e.preventDefault)
|
|
||||||
e.preventDefault();
|
|
||||||
else
|
|
||||||
e.returnValue = false;
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the double click custom event. It will
|
|
||||||
* basically zoom into the graph.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _doubleClickHandler(e) {
|
|
||||||
var pos,
|
|
||||||
ratio,
|
|
||||||
animation;
|
|
||||||
|
|
||||||
if (_settings('mouseEnabled')) {
|
|
||||||
ratio = 1 / _settings('doubleClickZoomingRatio');
|
|
||||||
|
|
||||||
_self.dispatchEvent('doubleclick', {
|
|
||||||
x: _startMouseX - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: _startMouseY - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_settings('doubleClickEnabled')) {
|
|
||||||
pos = _camera.cameraPosition(
|
|
||||||
sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
|
|
||||||
sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
animation = {
|
|
||||||
duration: _settings('doubleClickZoomDuration')
|
|
||||||
};
|
|
||||||
|
|
||||||
sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.preventDefault)
|
|
||||||
e.preventDefault();
|
|
||||||
else
|
|
||||||
e.returnValue = false;
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'wheel' mouse event. It will basically zoom
|
|
||||||
* in or not into the graph.
|
|
||||||
*
|
|
||||||
* @param {event} e A mouse event.
|
|
||||||
*/
|
|
||||||
function _wheelHandler(e) {
|
|
||||||
var pos,
|
|
||||||
ratio,
|
|
||||||
animation;
|
|
||||||
|
|
||||||
if (_settings('mouseEnabled') && _settings('mouseWheelEnabled')) {
|
|
||||||
ratio = sigma.utils.getDelta(e) > 0 ?
|
|
||||||
1 / _settings('zoomingRatio') :
|
|
||||||
_settings('zoomingRatio');
|
|
||||||
|
|
||||||
pos = _camera.cameraPosition(
|
|
||||||
sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2,
|
|
||||||
sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
animation = {
|
|
||||||
duration: _settings('mouseZoomDuration')
|
|
||||||
};
|
|
||||||
|
|
||||||
sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
|
|
||||||
|
|
||||||
if (e.preventDefault)
|
|
||||||
e.preventDefault();
|
|
||||||
else
|
|
||||||
e.returnValue = false;
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,430 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.captors');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user inputs default captor. It deals with mouse events, keyboards
|
|
||||||
* events and touch events.
|
|
||||||
*
|
|
||||||
* @param {DOMElement} target The DOM element where the listeners will be
|
|
||||||
* bound.
|
|
||||||
* @param {camera} camera The camera related to the target.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
* @return {sigma.captor} The fresh new captor instance.
|
|
||||||
*/
|
|
||||||
sigma.captors.touch = function(target, camera, settings) {
|
|
||||||
var _self = this,
|
|
||||||
_target = target,
|
|
||||||
_camera = camera,
|
|
||||||
_settings = settings,
|
|
||||||
|
|
||||||
// CAMERA MANAGEMENT:
|
|
||||||
// ******************
|
|
||||||
// The camera position when the user starts dragging:
|
|
||||||
_startCameraX,
|
|
||||||
_startCameraY,
|
|
||||||
_startCameraAngle,
|
|
||||||
_startCameraRatio,
|
|
||||||
|
|
||||||
// The latest stage position:
|
|
||||||
_lastCameraX,
|
|
||||||
_lastCameraY,
|
|
||||||
_lastCameraAngle,
|
|
||||||
_lastCameraRatio,
|
|
||||||
|
|
||||||
// TOUCH MANAGEMENT:
|
|
||||||
// *****************
|
|
||||||
// Touches that are down:
|
|
||||||
_downTouches = [],
|
|
||||||
|
|
||||||
_startTouchX0,
|
|
||||||
_startTouchY0,
|
|
||||||
_startTouchX1,
|
|
||||||
_startTouchY1,
|
|
||||||
_startTouchAngle,
|
|
||||||
_startTouchDistance,
|
|
||||||
|
|
||||||
_touchMode,
|
|
||||||
|
|
||||||
_isMoving,
|
|
||||||
_doubleTap,
|
|
||||||
_movingTimeoutId;
|
|
||||||
|
|
||||||
sigma.classes.dispatcher.extend(this);
|
|
||||||
|
|
||||||
sigma.utils.doubleClick(_target, 'touchstart', _doubleTapHandler);
|
|
||||||
_target.addEventListener('touchstart', _handleStart, false);
|
|
||||||
_target.addEventListener('touchend', _handleLeave, false);
|
|
||||||
_target.addEventListener('touchcancel', _handleLeave, false);
|
|
||||||
_target.addEventListener('touchleave', _handleLeave, false);
|
|
||||||
_target.addEventListener('touchmove', _handleMove, false);
|
|
||||||
|
|
||||||
function position(e) {
|
|
||||||
var offset = sigma.utils.getOffset(_target);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: e.pageX - offset.left,
|
|
||||||
y: e.pageY - offset.top
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method unbinds every handlers that makes the captor work.
|
|
||||||
*/
|
|
||||||
this.kill = function() {
|
|
||||||
sigma.utils.unbindDoubleClick(_target, 'touchstart');
|
|
||||||
_target.addEventListener('touchstart', _handleStart);
|
|
||||||
_target.addEventListener('touchend', _handleLeave);
|
|
||||||
_target.addEventListener('touchcancel', _handleLeave);
|
|
||||||
_target.addEventListener('touchleave', _handleLeave);
|
|
||||||
_target.addEventListener('touchmove', _handleMove);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TOUCH EVENTS:
|
|
||||||
// *************
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'touchstart' event. It will set the touch
|
|
||||||
* mode ("_touchMode") and start observing the user touch moves.
|
|
||||||
*
|
|
||||||
* @param {event} e A touch event.
|
|
||||||
*/
|
|
||||||
function _handleStart(e) {
|
|
||||||
if (_settings('touchEnabled')) {
|
|
||||||
var x0,
|
|
||||||
x1,
|
|
||||||
y0,
|
|
||||||
y1,
|
|
||||||
pos0,
|
|
||||||
pos1;
|
|
||||||
|
|
||||||
_downTouches = e.touches;
|
|
||||||
|
|
||||||
switch (_downTouches.length) {
|
|
||||||
case 1:
|
|
||||||
_camera.isMoving = true;
|
|
||||||
_touchMode = 1;
|
|
||||||
|
|
||||||
_startCameraX = _camera.x;
|
|
||||||
_startCameraY = _camera.y;
|
|
||||||
|
|
||||||
_lastCameraX = _camera.x;
|
|
||||||
_lastCameraY = _camera.y;
|
|
||||||
|
|
||||||
pos0 = position(_downTouches[0]);
|
|
||||||
_startTouchX0 = pos0.x;
|
|
||||||
_startTouchY0 = pos0.y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
_camera.isMoving = true;
|
|
||||||
_touchMode = 2;
|
|
||||||
|
|
||||||
pos0 = position(_downTouches[0]);
|
|
||||||
pos1 = position(_downTouches[1]);
|
|
||||||
x0 = pos0.x;
|
|
||||||
y0 = pos0.y;
|
|
||||||
x1 = pos1.x;
|
|
||||||
y1 = pos1.y;
|
|
||||||
|
|
||||||
_lastCameraX = _camera.x;
|
|
||||||
_lastCameraY = _camera.y;
|
|
||||||
|
|
||||||
_startCameraAngle = _camera.angle;
|
|
||||||
_startCameraRatio = _camera.ratio;
|
|
||||||
|
|
||||||
_startCameraX = _camera.x;
|
|
||||||
_startCameraY = _camera.y;
|
|
||||||
|
|
||||||
_startTouchX0 = x0;
|
|
||||||
_startTouchY0 = y0;
|
|
||||||
_startTouchX1 = x1;
|
|
||||||
_startTouchY1 = y1;
|
|
||||||
|
|
||||||
_startTouchAngle = Math.atan2(
|
|
||||||
_startTouchY1 - _startTouchY0,
|
|
||||||
_startTouchX1 - _startTouchX0
|
|
||||||
);
|
|
||||||
_startTouchDistance = Math.sqrt(
|
|
||||||
Math.pow(_startTouchY1 - _startTouchY0, 2) +
|
|
||||||
Math.pow(_startTouchX1 - _startTouchX0, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'touchend', 'touchcancel' and 'touchleave'
|
|
||||||
* event. It will update the touch mode if there are still at least one
|
|
||||||
* finger, and stop dragging else.
|
|
||||||
*
|
|
||||||
* @param {event} e A touch event.
|
|
||||||
*/
|
|
||||||
function _handleLeave(e) {
|
|
||||||
if (_settings('touchEnabled')) {
|
|
||||||
_downTouches = e.touches;
|
|
||||||
var inertiaRatio = _settings('touchInertiaRatio');
|
|
||||||
|
|
||||||
if (_movingTimeoutId) {
|
|
||||||
_isMoving = false;
|
|
||||||
clearTimeout(_movingTimeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (_touchMode) {
|
|
||||||
case 2:
|
|
||||||
if (e.touches.length === 1) {
|
|
||||||
_handleStart(e);
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* falls through */
|
|
||||||
case 1:
|
|
||||||
_camera.isMoving = false;
|
|
||||||
_self.dispatchEvent('stopDrag');
|
|
||||||
|
|
||||||
if (_isMoving) {
|
|
||||||
_doubleTap = false;
|
|
||||||
sigma.misc.animation.camera(
|
|
||||||
_camera,
|
|
||||||
{
|
|
||||||
x: _camera.x +
|
|
||||||
inertiaRatio * (_camera.x - _lastCameraX),
|
|
||||||
y: _camera.y +
|
|
||||||
inertiaRatio * (_camera.y - _lastCameraY)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
easing: 'quadraticOut',
|
|
||||||
duration: _settings('touchInertiaDuration')
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_isMoving = false;
|
|
||||||
_touchMode = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the 'touchmove' event. It will effectively drag
|
|
||||||
* the graph, and eventually zooms and turn it if the user is using two
|
|
||||||
* fingers.
|
|
||||||
*
|
|
||||||
* @param {event} e A touch event.
|
|
||||||
*/
|
|
||||||
function _handleMove(e) {
|
|
||||||
if (!_doubleTap && _settings('touchEnabled')) {
|
|
||||||
var x0,
|
|
||||||
x1,
|
|
||||||
y0,
|
|
||||||
y1,
|
|
||||||
cos,
|
|
||||||
sin,
|
|
||||||
end,
|
|
||||||
pos0,
|
|
||||||
pos1,
|
|
||||||
diff,
|
|
||||||
start,
|
|
||||||
dAngle,
|
|
||||||
dRatio,
|
|
||||||
newStageX,
|
|
||||||
newStageY,
|
|
||||||
newStageRatio,
|
|
||||||
newStageAngle;
|
|
||||||
|
|
||||||
_downTouches = e.touches;
|
|
||||||
_isMoving = true;
|
|
||||||
|
|
||||||
if (_movingTimeoutId)
|
|
||||||
clearTimeout(_movingTimeoutId);
|
|
||||||
|
|
||||||
_movingTimeoutId = setTimeout(function() {
|
|
||||||
_isMoving = false;
|
|
||||||
}, _settings('dragTimeout'));
|
|
||||||
|
|
||||||
switch (_touchMode) {
|
|
||||||
case 1:
|
|
||||||
pos0 = position(_downTouches[0]);
|
|
||||||
x0 = pos0.x;
|
|
||||||
y0 = pos0.y;
|
|
||||||
|
|
||||||
diff = _camera.cameraPosition(
|
|
||||||
x0 - _startTouchX0,
|
|
||||||
y0 - _startTouchY0,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
newStageX = _startCameraX - diff.x;
|
|
||||||
newStageY = _startCameraY - diff.y;
|
|
||||||
|
|
||||||
if (newStageX !== _camera.x || newStageY !== _camera.y) {
|
|
||||||
_lastCameraX = _camera.x;
|
|
||||||
_lastCameraY = _camera.y;
|
|
||||||
|
|
||||||
_camera.goTo({
|
|
||||||
x: newStageX,
|
|
||||||
y: newStageY
|
|
||||||
});
|
|
||||||
|
|
||||||
_self.dispatchEvent('mousemove', {
|
|
||||||
x: pos0.x - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: pos0.y - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
|
|
||||||
_self.dispatchEvent('drag');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
pos0 = position(_downTouches[0]);
|
|
||||||
pos1 = position(_downTouches[1]);
|
|
||||||
x0 = pos0.x;
|
|
||||||
y0 = pos0.y;
|
|
||||||
x1 = pos1.x;
|
|
||||||
y1 = pos1.y;
|
|
||||||
|
|
||||||
start = _camera.cameraPosition(
|
|
||||||
(_startTouchX0 + _startTouchX1) / 2 -
|
|
||||||
sigma.utils.getWidth(e) / 2,
|
|
||||||
(_startTouchY0 + _startTouchY1) / 2 -
|
|
||||||
sigma.utils.getHeight(e) / 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
end = _camera.cameraPosition(
|
|
||||||
(x0 + x1) / 2 - sigma.utils.getWidth(e) / 2,
|
|
||||||
(y0 + y1) / 2 - sigma.utils.getHeight(e) / 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
dAngle = Math.atan2(y1 - y0, x1 - x0) - _startTouchAngle;
|
|
||||||
dRatio = Math.sqrt(
|
|
||||||
Math.pow(y1 - y0, 2) + Math.pow(x1 - x0, 2)
|
|
||||||
) / _startTouchDistance;
|
|
||||||
|
|
||||||
// Translation:
|
|
||||||
x0 = start.x;
|
|
||||||
y0 = start.y;
|
|
||||||
|
|
||||||
// Homothetic transformation:
|
|
||||||
newStageRatio = _startCameraRatio / dRatio;
|
|
||||||
x0 = x0 * dRatio;
|
|
||||||
y0 = y0 * dRatio;
|
|
||||||
|
|
||||||
// Rotation:
|
|
||||||
newStageAngle = _startCameraAngle - dAngle;
|
|
||||||
cos = Math.cos(-dAngle);
|
|
||||||
sin = Math.sin(-dAngle);
|
|
||||||
x1 = x0 * cos + y0 * sin;
|
|
||||||
y1 = y0 * cos - x0 * sin;
|
|
||||||
x0 = x1;
|
|
||||||
y0 = y1;
|
|
||||||
|
|
||||||
// Finalize:
|
|
||||||
newStageX = x0 - end.x + _startCameraX;
|
|
||||||
newStageY = y0 - end.y + _startCameraY;
|
|
||||||
|
|
||||||
if (
|
|
||||||
newStageRatio !== _camera.ratio ||
|
|
||||||
newStageAngle !== _camera.angle ||
|
|
||||||
newStageX !== _camera.x ||
|
|
||||||
newStageY !== _camera.y
|
|
||||||
) {
|
|
||||||
_lastCameraX = _camera.x;
|
|
||||||
_lastCameraY = _camera.y;
|
|
||||||
_lastCameraAngle = _camera.angle;
|
|
||||||
_lastCameraRatio = _camera.ratio;
|
|
||||||
|
|
||||||
_camera.goTo({
|
|
||||||
x: newStageX,
|
|
||||||
y: newStageY,
|
|
||||||
angle: newStageAngle,
|
|
||||||
ratio: newStageRatio
|
|
||||||
});
|
|
||||||
|
|
||||||
_self.dispatchEvent('drag');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler listening to the double tap custom event. It will
|
|
||||||
* basically zoom into the graph.
|
|
||||||
*
|
|
||||||
* @param {event} e A touch event.
|
|
||||||
*/
|
|
||||||
function _doubleTapHandler(e) {
|
|
||||||
var pos,
|
|
||||||
ratio,
|
|
||||||
animation;
|
|
||||||
|
|
||||||
if (e.touches && e.touches.length === 1 && _settings('touchEnabled')) {
|
|
||||||
_doubleTap = true;
|
|
||||||
|
|
||||||
ratio = 1 / _settings('doubleClickZoomingRatio');
|
|
||||||
|
|
||||||
pos = position(e.touches[0]);
|
|
||||||
_self.dispatchEvent('doubleclick', {
|
|
||||||
x: pos.x - sigma.utils.getWidth(e) / 2,
|
|
||||||
y: pos.y - sigma.utils.getHeight(e) / 2,
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
ctrlKey: e.ctrlKey,
|
|
||||||
metaKey: e.metaKey,
|
|
||||||
altKey: e.altKey,
|
|
||||||
shiftKey: e.shiftKey
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_settings('doubleClickEnabled')) {
|
|
||||||
pos = _camera.cameraPosition(
|
|
||||||
pos.x - sigma.utils.getWidth(e) / 2,
|
|
||||||
pos.y - sigma.utils.getHeight(e) / 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
animation = {
|
|
||||||
duration: _settings('doubleClickZoomDuration'),
|
|
||||||
onComplete: function() {
|
|
||||||
_doubleTap = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.preventDefault)
|
|
||||||
e.preventDefault();
|
|
||||||
else
|
|
||||||
e.returnValue = false;
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,240 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.classes');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The camera constructor. It just initializes its attributes and methods.
|
|
||||||
*
|
|
||||||
* @param {string} id The id.
|
|
||||||
* @param {sigma.classes.graph} graph The graph.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
* @param {?object} options Eventually some overriding options.
|
|
||||||
* @return {camera} Returns the fresh new camera instance.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera = function(id, graph, settings, options) {
|
|
||||||
sigma.classes.dispatcher.extend(this);
|
|
||||||
|
|
||||||
Object.defineProperty(this, 'graph', {
|
|
||||||
value: graph
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, 'id', {
|
|
||||||
value: id
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, 'readPrefix', {
|
|
||||||
value: 'read_cam' + id + ':'
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, 'prefix', {
|
|
||||||
value: 'cam' + id + ':'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.x = 0;
|
|
||||||
this.y = 0;
|
|
||||||
this.ratio = 1;
|
|
||||||
this.angle = 0;
|
|
||||||
this.isAnimated = false;
|
|
||||||
this.settings = (typeof options === 'object' && options) ?
|
|
||||||
settings.embedObject(options) :
|
|
||||||
settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the camera position.
|
|
||||||
*
|
|
||||||
* @param {object} coordinates The new coordinates object.
|
|
||||||
* @return {camera} Returns the camera.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera.prototype.goTo = function(coordinates) {
|
|
||||||
if (!this.settings('enableCamera'))
|
|
||||||
return this;
|
|
||||||
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
c = coordinates || {},
|
|
||||||
keys = ['x', 'y', 'ratio', 'angle'];
|
|
||||||
|
|
||||||
for (i = 0, l = keys.length; i < l; i++)
|
|
||||||
if (c[keys[i]] !== undefined) {
|
|
||||||
if (typeof c[keys[i]] === 'number' && !isNaN(c[keys[i]]))
|
|
||||||
this[keys[i]] = c[keys[i]];
|
|
||||||
else
|
|
||||||
throw 'Value for "' + keys[i] + '" is not a number.';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dispatchEvent('coordinatesUpdated');
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method takes a graph and computes for each node and edges its
|
|
||||||
* coordinates relatively to the center of the camera. Basically, it will
|
|
||||||
* compute the coordinates that will be used by the graphic renderers.
|
|
||||||
*
|
|
||||||
* Since it should be possible to use different cameras and different
|
|
||||||
* renderers, it is possible to specify a prefix to put before the new
|
|
||||||
* coordinates (to get something like "node.camera1_x")
|
|
||||||
*
|
|
||||||
* @param {?string} read The prefix of the coordinates to read.
|
|
||||||
* @param {?string} write The prefix of the coordinates to write.
|
|
||||||
* @param {?object} options Eventually an object of options. Those can be:
|
|
||||||
* - A restricted nodes array.
|
|
||||||
* - A restricted edges array.
|
|
||||||
* - A width.
|
|
||||||
* - A height.
|
|
||||||
* @return {camera} Returns the camera.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera.prototype.applyView = function(read, write, options) {
|
|
||||||
options = options || {};
|
|
||||||
write = write !== undefined ? write : this.prefix;
|
|
||||||
read = read !== undefined ? read : this.readPrefix;
|
|
||||||
|
|
||||||
var nodes = options.nodes || this.graph.nodes(),
|
|
||||||
edges = options.edges || this.graph.edges();
|
|
||||||
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
node,
|
|
||||||
cos = Math.cos(this.angle),
|
|
||||||
sin = Math.sin(this.angle),
|
|
||||||
nodeRatio = Math.pow(this.ratio, this.settings('nodesPowRatio')),
|
|
||||||
edgeRatio = Math.pow(this.ratio, this.settings('edgesPowRatio'));
|
|
||||||
|
|
||||||
for (i = 0, l = nodes.length; i < l; i++) {
|
|
||||||
node = nodes[i];
|
|
||||||
node[write + 'x'] =
|
|
||||||
(
|
|
||||||
((node[read + 'x'] || 0) - this.x) * cos +
|
|
||||||
((node[read + 'y'] || 0) - this.y) * sin
|
|
||||||
) / this.ratio + (options.width || 0) / 2;
|
|
||||||
node[write + 'y'] =
|
|
||||||
(
|
|
||||||
((node[read + 'y'] || 0) - this.y) * cos -
|
|
||||||
((node[read + 'x'] || 0) - this.x) * sin
|
|
||||||
) / this.ratio + (options.height || 0) / 2;
|
|
||||||
node[write + 'size'] =
|
|
||||||
(node[read + 'size'] || 0) /
|
|
||||||
nodeRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0, l = edges.length; i < l; i++) {
|
|
||||||
edges[i][write + 'size'] =
|
|
||||||
(edges[i][read + 'size'] || 0) /
|
|
||||||
edgeRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function converts the coordinates of a point from the frame of the
|
|
||||||
* camera to the frame of the graph.
|
|
||||||
*
|
|
||||||
* @param {number} x The X coordinate of the point in the frame of the
|
|
||||||
* camera.
|
|
||||||
* @param {number} y The Y coordinate of the point in the frame of the
|
|
||||||
* camera.
|
|
||||||
* @return {object} The point coordinates in the frame of the graph.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera.prototype.graphPosition = function(x, y, vector) {
|
|
||||||
var X = 0,
|
|
||||||
Y = 0,
|
|
||||||
cos = Math.cos(this.angle),
|
|
||||||
sin = Math.sin(this.angle);
|
|
||||||
|
|
||||||
// Revert the origin differential vector:
|
|
||||||
if (!vector) {
|
|
||||||
X = - (this.x * cos + this.y * sin) / this.ratio;
|
|
||||||
Y = - (this.y * cos - this.x * sin) / this.ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: (x * cos + y * sin) / this.ratio + X,
|
|
||||||
y: (y * cos - x * sin) / this.ratio + Y
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function converts the coordinates of a point from the frame of the
|
|
||||||
* graph to the frame of the camera.
|
|
||||||
*
|
|
||||||
* @param {number} x The X coordinate of the point in the frame of the
|
|
||||||
* graph.
|
|
||||||
* @param {number} y The Y coordinate of the point in the frame of the
|
|
||||||
* graph.
|
|
||||||
* @return {object} The point coordinates in the frame of the camera.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera.prototype.cameraPosition = function(x, y, vector) {
|
|
||||||
var X = 0,
|
|
||||||
Y = 0,
|
|
||||||
cos = Math.cos(this.angle),
|
|
||||||
sin = Math.sin(this.angle);
|
|
||||||
|
|
||||||
// Revert the origin differential vector:
|
|
||||||
if (!vector) {
|
|
||||||
X = - (this.x * cos + this.y * sin) / this.ratio;
|
|
||||||
Y = - (this.y * cos - this.x * sin) / this.ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: ((x - X) * cos - (y - Y) * sin) * this.ratio,
|
|
||||||
y: ((y - Y) * cos + (x - X) * sin) * this.ratio
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the transformation matrix of the camera. This is
|
|
||||||
* especially useful to apply the camera view directly in shaders, in case of
|
|
||||||
* WebGL rendering.
|
|
||||||
*
|
|
||||||
* @return {array} The transformation matrix.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera.prototype.getMatrix = function() {
|
|
||||||
var scale = sigma.utils.matrices.scale(1 / this.ratio),
|
|
||||||
rotation = sigma.utils.matrices.rotation(this.angle),
|
|
||||||
translation = sigma.utils.matrices.translation(-this.x, -this.y),
|
|
||||||
matrix = sigma.utils.matrices.multiply(
|
|
||||||
translation,
|
|
||||||
sigma.utils.matrices.multiply(
|
|
||||||
rotation,
|
|
||||||
scale
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return matrix;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Taking a width and a height as parameters, this method returns the
|
|
||||||
* coordinates of the rectangle representing the camera on screen, in the
|
|
||||||
* graph's referentiel.
|
|
||||||
*
|
|
||||||
* To keep displaying labels of nodes going out of the screen, the method
|
|
||||||
* keeps a margin around the screen in the returned rectangle.
|
|
||||||
*
|
|
||||||
* @param {number} width The width of the screen.
|
|
||||||
* @param {number} height The height of the screen.
|
|
||||||
* @return {object} The rectangle as x1, y1, x2 and y2, representing
|
|
||||||
* two opposite points.
|
|
||||||
*/
|
|
||||||
sigma.classes.camera.prototype.getRectangle = function(width, height) {
|
|
||||||
var widthVect = this.cameraPosition(width, 0, true),
|
|
||||||
heightVect = this.cameraPosition(0, height, true),
|
|
||||||
centerVect = this.cameraPosition(width / 2, height / 2, true),
|
|
||||||
marginX = this.cameraPosition(width / 4, 0, true).x,
|
|
||||||
marginY = this.cameraPosition(0, height / 4, true).y;
|
|
||||||
|
|
||||||
return {
|
|
||||||
x1: this.x - centerVect.x - marginX,
|
|
||||||
y1: this.y - centerVect.y - marginY,
|
|
||||||
x2: this.x - centerVect.x + marginX + widthVect.x,
|
|
||||||
y2: this.y - centerVect.y - marginY + widthVect.y,
|
|
||||||
height: Math.sqrt(
|
|
||||||
Math.pow(heightVect.x, 2) +
|
|
||||||
Math.pow(heightVect.y + 2 * marginY, 2)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,116 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This utils aims to facilitate the manipulation of each instance setting.
|
|
||||||
* Using a function instead of an object brings two main advantages: First,
|
|
||||||
* it will be easier in the future to catch settings updates through a
|
|
||||||
* function than an object. Second, giving it a full object will "merge" it
|
|
||||||
* to the settings object properly, keeping us to have to always add a loop.
|
|
||||||
*
|
|
||||||
* @return {configurable} The "settings" function.
|
|
||||||
*/
|
|
||||||
var configurable = function() {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
data = {},
|
|
||||||
datas = Array.prototype.slice.call(arguments, 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The method to use to set or get any property of this instance.
|
|
||||||
*
|
|
||||||
* @param {string|object} a1 If it is a string and if a2 is undefined,
|
|
||||||
* then it will return the corresponding
|
|
||||||
* property. If it is a string and if a2 is
|
|
||||||
* set, then it will set a2 as the property
|
|
||||||
* corresponding to a1, and return this. If
|
|
||||||
* it is an object, then each pair string +
|
|
||||||
* object(or any other type) will be set as a
|
|
||||||
* property.
|
|
||||||
* @param {*?} a2 The new property corresponding to a1 if a1
|
|
||||||
* is a string.
|
|
||||||
* @return {*|configurable} Returns itself or the corresponding
|
|
||||||
* property.
|
|
||||||
*
|
|
||||||
* Polymorphism:
|
|
||||||
* *************
|
|
||||||
* Here are some basic use examples:
|
|
||||||
*
|
|
||||||
* > settings = new configurable();
|
|
||||||
* > settings('mySetting', 42);
|
|
||||||
* > settings('mySetting'); // Logs: 42
|
|
||||||
* > settings('mySetting', 123);
|
|
||||||
* > settings('mySetting'); // Logs: 123
|
|
||||||
* > settings({mySetting: 456});
|
|
||||||
* > settings('mySetting'); // Logs: 456
|
|
||||||
*
|
|
||||||
* Also, it is possible to use the function as a fallback:
|
|
||||||
* > settings({mySetting: 'abc'}, 'mySetting'); // Logs: 'abc'
|
|
||||||
* > settings({hisSetting: 'abc'}, 'mySetting'); // Logs: 456
|
|
||||||
*/
|
|
||||||
var settings = function(a1, a2) {
|
|
||||||
var o,
|
|
||||||
i,
|
|
||||||
l,
|
|
||||||
k;
|
|
||||||
|
|
||||||
if (arguments.length === 1 && typeof a1 === 'string') {
|
|
||||||
if (data[a1] !== undefined)
|
|
||||||
return data[a1];
|
|
||||||
for (i = 0, l = datas.length; i < l; i++)
|
|
||||||
if (datas[i][a1] !== undefined)
|
|
||||||
return datas[i][a1];
|
|
||||||
return undefined;
|
|
||||||
} else if (typeof a1 === 'object' && typeof a2 === 'string') {
|
|
||||||
return (a1 || {})[a2] !== undefined ? a1[a2] : settings(a2);
|
|
||||||
} else {
|
|
||||||
o = (typeof a1 === 'object' && a2 === undefined) ? a1 : {};
|
|
||||||
|
|
||||||
if (typeof a1 === 'string')
|
|
||||||
o[a1] = a2;
|
|
||||||
|
|
||||||
for (i = 0, k = Object.keys(o), l = k.length; i < l; i++)
|
|
||||||
data[k[i]] = o[k[i]];
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns a new configurable function, with new objects
|
|
||||||
*
|
|
||||||
* @param {object*} Any number of objects to search in.
|
|
||||||
* @return {function} Returns the function. Check its documentation to know
|
|
||||||
* more about how it works.
|
|
||||||
*/
|
|
||||||
settings.embedObjects = function() {
|
|
||||||
var args = datas.concat(
|
|
||||||
data
|
|
||||||
).concat(
|
|
||||||
Array.prototype.splice.call(arguments, 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
return configurable.apply({}, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
for (i = 0, l = arguments.length; i < l; i++)
|
|
||||||
settings(arguments[i]);
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EXPORT:
|
|
||||||
* *******
|
|
||||||
*/
|
|
||||||
if (typeof this.sigma !== 'undefined') {
|
|
||||||
this.sigma.classes = this.sigma.classes || {};
|
|
||||||
this.sigma.classes.configurable = configurable;
|
|
||||||
} else if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports)
|
|
||||||
exports = module.exports = configurable;
|
|
||||||
exports.configurable = configurable;
|
|
||||||
} else
|
|
||||||
this.configurable = configurable;
|
|
||||||
}).call(this);
|
|
@ -1,204 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatcher constructor.
|
|
||||||
*
|
|
||||||
* @return {dispatcher} The new dispatcher instance.
|
|
||||||
*/
|
|
||||||
var dispatcher = function() {
|
|
||||||
Object.defineProperty(this, '_handlers', {
|
|
||||||
value: {}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will execute the handler everytime that the indicated event (or the
|
|
||||||
* indicated events) will be triggered.
|
|
||||||
*
|
|
||||||
* @param {string} events The name of the event (or the events
|
|
||||||
* separated by spaces).
|
|
||||||
* @param {function(Object)} handler The handler to bind.
|
|
||||||
* @return {dispatcher} Returns the instance itself.
|
|
||||||
*/
|
|
||||||
dispatcher.prototype.bind = function(events, handler) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
event,
|
|
||||||
eArray;
|
|
||||||
|
|
||||||
if (
|
|
||||||
arguments.length === 1 &&
|
|
||||||
typeof arguments[0] === 'object'
|
|
||||||
)
|
|
||||||
for (events in arguments[0])
|
|
||||||
this.bind(events, arguments[0][events]);
|
|
||||||
else if (
|
|
||||||
arguments.length === 2 &&
|
|
||||||
typeof arguments[1] === 'function'
|
|
||||||
) {
|
|
||||||
eArray = typeof events === 'string' ? events.split(' ') : events;
|
|
||||||
|
|
||||||
for (i = 0, l = eArray.length; i !== l; i += 1) {
|
|
||||||
event = eArray[i];
|
|
||||||
|
|
||||||
// Check that event is not '':
|
|
||||||
if (!event)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!this._handlers[event])
|
|
||||||
this._handlers[event] = [];
|
|
||||||
|
|
||||||
// Using an object instead of directly the handler will make possible
|
|
||||||
// later to add flags
|
|
||||||
this._handlers[event].push({
|
|
||||||
handler: handler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
throw 'bind: Wrong arguments.';
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the handler from a specified event (or specified events).
|
|
||||||
*
|
|
||||||
* @param {?string} events The name of the event (or the events
|
|
||||||
* separated by spaces). If undefined,
|
|
||||||
* then all handlers are removed.
|
|
||||||
* @param {?function(object)} handler The handler to unbind. If undefined,
|
|
||||||
* each handler bound to the event or the
|
|
||||||
* events will be removed.
|
|
||||||
* @return {dispatcher} Returns the instance itself.
|
|
||||||
*/
|
|
||||||
dispatcher.prototype.unbind = function(events, handler) {
|
|
||||||
var i,
|
|
||||||
n,
|
|
||||||
j,
|
|
||||||
m,
|
|
||||||
k,
|
|
||||||
a,
|
|
||||||
event,
|
|
||||||
eArray = typeof events === 'string' ? events.split(' ') : events;
|
|
||||||
|
|
||||||
if (!arguments.length) {
|
|
||||||
for (k in this._handlers)
|
|
||||||
delete this._handlers[k];
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler) {
|
|
||||||
for (i = 0, n = eArray.length; i !== n; i += 1) {
|
|
||||||
event = eArray[i];
|
|
||||||
if (this._handlers[event]) {
|
|
||||||
a = [];
|
|
||||||
for (j = 0, m = this._handlers[event].length; j !== m; j += 1)
|
|
||||||
if (this._handlers[event][j].handler !== handler)
|
|
||||||
a.push(this._handlers[event][j]);
|
|
||||||
|
|
||||||
this._handlers[event] = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._handlers[event] && this._handlers[event].length === 0)
|
|
||||||
delete this._handlers[event];
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
for (i = 0, n = eArray.length; i !== n; i += 1)
|
|
||||||
delete this._handlers[eArray[i]];
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes each handler bound to the event
|
|
||||||
*
|
|
||||||
* @param {string} events The name of the event (or the events separated
|
|
||||||
* by spaces).
|
|
||||||
* @param {?object} data The content of the event (optional).
|
|
||||||
* @return {dispatcher} Returns the instance itself.
|
|
||||||
*/
|
|
||||||
dispatcher.prototype.dispatchEvent = function(events, data) {
|
|
||||||
var i,
|
|
||||||
n,
|
|
||||||
j,
|
|
||||||
m,
|
|
||||||
a,
|
|
||||||
event,
|
|
||||||
eventName,
|
|
||||||
self = this,
|
|
||||||
eArray = typeof events === 'string' ? events.split(' ') : events;
|
|
||||||
|
|
||||||
data = data === undefined ? {} : data;
|
|
||||||
|
|
||||||
for (i = 0, n = eArray.length; i !== n; i += 1) {
|
|
||||||
eventName = eArray[i];
|
|
||||||
|
|
||||||
if (this._handlers[eventName]) {
|
|
||||||
event = self.getEvent(eventName, data);
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (j = 0, m = this._handlers[eventName].length; j !== m; j += 1) {
|
|
||||||
this._handlers[eventName][j].handler(event);
|
|
||||||
if (!this._handlers[eventName][j].one)
|
|
||||||
a.push(this._handlers[eventName][j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handlers[eventName] = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an event object.
|
|
||||||
*
|
|
||||||
* @param {string} events The name of the event.
|
|
||||||
* @param {?object} data The content of the event (optional).
|
|
||||||
* @return {object} Returns the instance itself.
|
|
||||||
*/
|
|
||||||
dispatcher.prototype.getEvent = function(event, data) {
|
|
||||||
return {
|
|
||||||
type: event,
|
|
||||||
data: data || {},
|
|
||||||
target: this
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A useful function to deal with inheritance. It will make the target
|
|
||||||
* inherit the prototype of the class dispatcher as well as its constructor.
|
|
||||||
*
|
|
||||||
* @param {object} target The target.
|
|
||||||
*/
|
|
||||||
dispatcher.extend = function(target, args) {
|
|
||||||
var k;
|
|
||||||
|
|
||||||
for (k in dispatcher.prototype)
|
|
||||||
if (dispatcher.prototype.hasOwnProperty(k))
|
|
||||||
target[k] = dispatcher.prototype[k];
|
|
||||||
|
|
||||||
dispatcher.apply(target, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EXPORT:
|
|
||||||
* *******
|
|
||||||
*/
|
|
||||||
if (typeof this.sigma !== 'undefined') {
|
|
||||||
this.sigma.classes = this.sigma.classes || {};
|
|
||||||
this.sigma.classes.dispatcher = dispatcher;
|
|
||||||
} else if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports)
|
|
||||||
exports = module.exports = dispatcher;
|
|
||||||
exports.dispatcher = dispatcher;
|
|
||||||
} else
|
|
||||||
this.dispatcher = dispatcher;
|
|
||||||
}).call(this);
|
|
@ -1,832 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sigma Quadtree Module for edges
|
|
||||||
* ===============================
|
|
||||||
*
|
|
||||||
* Author: Sébastien Heymann,
|
|
||||||
* from the quad of Guillaume Plique (Yomguithereal)
|
|
||||||
* Version: 0.2
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quad Geometric Operations
|
|
||||||
* -------------------------
|
|
||||||
*
|
|
||||||
* A useful batch of geometric operations used by the quadtree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _geom = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a graph node with x, y and size into an
|
|
||||||
* axis-aligned square.
|
|
||||||
*
|
|
||||||
* @param {object} A graph node with at least a point (x, y) and a size.
|
|
||||||
* @return {object} A square: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
pointToSquare: function(n) {
|
|
||||||
return {
|
|
||||||
x1: n.x - n.size,
|
|
||||||
y1: n.y - n.size,
|
|
||||||
x2: n.x + n.size,
|
|
||||||
y2: n.y - n.size,
|
|
||||||
height: n.size * 2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a graph edge with x1, y1, x2, y2 and size into an
|
|
||||||
* axis-aligned square.
|
|
||||||
*
|
|
||||||
* @param {object} A graph edge with at least two points
|
|
||||||
* (x1, y1), (x2, y2) and a size.
|
|
||||||
* @return {object} A square: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
lineToSquare: function(e) {
|
|
||||||
if (e.y1 < e.y2) {
|
|
||||||
// (e.x1, e.y1) on top
|
|
||||||
if (e.x1 < e.x2) {
|
|
||||||
// (e.x1, e.y1) on left
|
|
||||||
return {
|
|
||||||
x1: e.x1 - e.size,
|
|
||||||
y1: e.y1 - e.size,
|
|
||||||
x2: e.x2 + e.size,
|
|
||||||
y2: e.y1 - e.size,
|
|
||||||
height: e.y2 - e.y1 + e.size * 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// (e.x1, e.y1) on right
|
|
||||||
return {
|
|
||||||
x1: e.x2 - e.size,
|
|
||||||
y1: e.y1 - e.size,
|
|
||||||
x2: e.x1 + e.size,
|
|
||||||
y2: e.y1 - e.size,
|
|
||||||
height: e.y2 - e.y1 + e.size * 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// (e.x2, e.y2) on top
|
|
||||||
if (e.x1 < e.x2) {
|
|
||||||
// (e.x1, e.y1) on left
|
|
||||||
return {
|
|
||||||
x1: e.x1 - e.size,
|
|
||||||
y1: e.y2 - e.size,
|
|
||||||
x2: e.x2 + e.size,
|
|
||||||
y2: e.y2 - e.size,
|
|
||||||
height: e.y1 - e.y2 + e.size * 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// (e.x2, e.y2) on right
|
|
||||||
return {
|
|
||||||
x1: e.x2 - e.size,
|
|
||||||
y1: e.y2 - e.size,
|
|
||||||
x2: e.x1 + e.size,
|
|
||||||
y2: e.y2 - e.size,
|
|
||||||
height: e.y1 - e.y2 + e.size * 2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a graph edge of type 'curve' with x1, y1, x2, y2,
|
|
||||||
* control point and size into an axis-aligned square.
|
|
||||||
*
|
|
||||||
* @param {object} e A graph edge with at least two points
|
|
||||||
* (x1, y1), (x2, y2) and a size.
|
|
||||||
* @param {object} cp A control point (x,y).
|
|
||||||
* @return {object} A square: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
quadraticCurveToSquare: function(e, cp) {
|
|
||||||
var pt = sigma.utils.getPointOnQuadraticCurve(
|
|
||||||
0.5,
|
|
||||||
e.x1,
|
|
||||||
e.y1,
|
|
||||||
e.x2,
|
|
||||||
e.y2,
|
|
||||||
cp.x,
|
|
||||||
cp.y
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bounding box of the two points and the point at the middle of the
|
|
||||||
// curve:
|
|
||||||
var minX = Math.min(e.x1, e.x2, pt.x),
|
|
||||||
maxX = Math.max(e.x1, e.x2, pt.x),
|
|
||||||
minY = Math.min(e.y1, e.y2, pt.y),
|
|
||||||
maxY = Math.max(e.y1, e.y2, pt.y);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x1: minX - e.size,
|
|
||||||
y1: minY - e.size,
|
|
||||||
x2: maxX + e.size,
|
|
||||||
y2: minY - e.size,
|
|
||||||
height: maxY - minY + e.size * 2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a graph self loop into an axis-aligned square.
|
|
||||||
*
|
|
||||||
* @param {object} n A graph node with a point (x, y) and a size.
|
|
||||||
* @return {object} A square: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
selfLoopToSquare: function(n) {
|
|
||||||
// Fitting to the curve is too costly, we compute a larger bounding box
|
|
||||||
// using the control points:
|
|
||||||
var cp = sigma.utils.getSelfLoopControlPoints(n.x, n.y, n.size);
|
|
||||||
|
|
||||||
// Bounding box of the point and the two control points:
|
|
||||||
var minX = Math.min(n.x, cp.x1, cp.x2),
|
|
||||||
maxX = Math.max(n.x, cp.x1, cp.x2),
|
|
||||||
minY = Math.min(n.y, cp.y1, cp.y2),
|
|
||||||
maxY = Math.max(n.y, cp.y1, cp.y2);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x1: minX - n.size,
|
|
||||||
y1: minY - n.size,
|
|
||||||
x2: maxX + n.size,
|
|
||||||
y2: minY - n.size,
|
|
||||||
height: maxY - minY + n.size * 2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a rectangle is axis-aligned.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points
|
|
||||||
* (x1, y1) and (x2, y2).
|
|
||||||
* @return {boolean} True if the rectangle is axis-aligned.
|
|
||||||
*/
|
|
||||||
isAxisAligned: function(r) {
|
|
||||||
return r.x1 === r.x2 || r.y1 === r.y2;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute top points of an axis-aligned rectangle. This is useful in
|
|
||||||
* cases when the rectangle has been rotated (left, right or bottom up) and
|
|
||||||
* later operations need to know the top points.
|
|
||||||
*
|
|
||||||
* @param {object} An axis-aligned rectangle defined by two points
|
|
||||||
* (x1, y1), (x2, y2) and height.
|
|
||||||
* @return {object} A rectangle: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
axisAlignedTopPoints: function(r) {
|
|
||||||
|
|
||||||
// Basic
|
|
||||||
if (r.y1 === r.y2 && r.x1 < r.x2)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
// Rotated to right
|
|
||||||
if (r.x1 === r.x2 && r.y2 > r.y1)
|
|
||||||
return {
|
|
||||||
x1: r.x1 - r.height, y1: r.y1,
|
|
||||||
x2: r.x1, y2: r.y1,
|
|
||||||
height: r.height
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rotated to left
|
|
||||||
if (r.x1 === r.x2 && r.y2 < r.y1)
|
|
||||||
return {
|
|
||||||
x1: r.x1, y1: r.y2,
|
|
||||||
x2: r.x2 + r.height, y2: r.y2,
|
|
||||||
height: r.height
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bottom's up
|
|
||||||
return {
|
|
||||||
x1: r.x2, y1: r.y1 - r.height,
|
|
||||||
x2: r.x1, y2: r.y1 - r.height,
|
|
||||||
height: r.height
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get coordinates of a rectangle's lower left corner from its top points.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
|
|
||||||
* @return {object} Coordinates of the corner (x, y).
|
|
||||||
*/
|
|
||||||
lowerLeftCoor: function(r) {
|
|
||||||
var width = (
|
|
||||||
Math.sqrt(
|
|
||||||
Math.pow(r.x2 - r.x1, 2) +
|
|
||||||
Math.pow(r.y2 - r.y1, 2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: r.x1 - (r.y2 - r.y1) * r.height / width,
|
|
||||||
y: r.y1 + (r.x2 - r.x1) * r.height / width
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get coordinates of a rectangle's lower right corner from its top points
|
|
||||||
* and its lower left corner.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
|
|
||||||
* @param {object} A corner's coordinates (x, y).
|
|
||||||
* @return {object} Coordinates of the corner (x, y).
|
|
||||||
*/
|
|
||||||
lowerRightCoor: function(r, llc) {
|
|
||||||
return {
|
|
||||||
x: llc.x - r.x1 + r.x2,
|
|
||||||
y: llc.y - r.y1 + r.y2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the coordinates of all the corners of a rectangle from its top point.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
|
|
||||||
* @return {array} An array of the four corners' coordinates (x, y).
|
|
||||||
*/
|
|
||||||
rectangleCorners: function(r) {
|
|
||||||
var llc = this.lowerLeftCoor(r),
|
|
||||||
lrc = this.lowerRightCoor(r, llc);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{x: r.x1, y: r.y1},
|
|
||||||
{x: r.x2, y: r.y2},
|
|
||||||
{x: llc.x, y: llc.y},
|
|
||||||
{x: lrc.x, y: lrc.y}
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a square defined by its boundaries into four.
|
|
||||||
*
|
|
||||||
* @param {object} Boundaries of the square (x, y, width, height).
|
|
||||||
* @return {array} An array containing the four new squares, themselves
|
|
||||||
* defined by an array of their four corners (x, y).
|
|
||||||
*/
|
|
||||||
splitSquare: function(b) {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
{x: b.x, y: b.y},
|
|
||||||
{x: b.x + b.width / 2, y: b.y},
|
|
||||||
{x: b.x, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{x: b.x + b.width / 2, y: b.y},
|
|
||||||
{x: b.x + b.width, y: b.y},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width, y: b.y + b.height / 2}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{x: b.x, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2},
|
|
||||||
{x: b.x, y: b.y + b.height},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height},
|
|
||||||
{x: b.x + b.width, y: b.y + b.height}
|
|
||||||
]
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the four axis between corners of rectangle A and corners of
|
|
||||||
* rectangle B. This is needed later to check an eventual collision.
|
|
||||||
*
|
|
||||||
* @param {array} An array of rectangle A's four corners (x, y).
|
|
||||||
* @param {array} An array of rectangle B's four corners (x, y).
|
|
||||||
* @return {array} An array of four axis defined by their coordinates (x,y).
|
|
||||||
*/
|
|
||||||
axis: function(c1, c2) {
|
|
||||||
return [
|
|
||||||
{x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y},
|
|
||||||
{x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y},
|
|
||||||
{x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y},
|
|
||||||
{x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y}
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project a rectangle's corner on an axis.
|
|
||||||
*
|
|
||||||
* @param {object} Coordinates of a corner (x, y).
|
|
||||||
* @param {object} Coordinates of an axis (x, y).
|
|
||||||
* @return {object} The projection defined by coordinates (x, y).
|
|
||||||
*/
|
|
||||||
projection: function(c, a) {
|
|
||||||
var l = (
|
|
||||||
(c.x * a.x + c.y * a.y) /
|
|
||||||
(Math.pow(a.x, 2) + Math.pow(a.y, 2))
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: l * a.x,
|
|
||||||
y: l * a.y
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether two rectangles collide on one particular axis.
|
|
||||||
*
|
|
||||||
* @param {object} An axis' coordinates (x, y).
|
|
||||||
* @param {array} Rectangle A's corners.
|
|
||||||
* @param {array} Rectangle B's corners.
|
|
||||||
* @return {boolean} True if the rectangles collide on the axis.
|
|
||||||
*/
|
|
||||||
axisCollision: function(a, c1, c2) {
|
|
||||||
var sc1 = [],
|
|
||||||
sc2 = [];
|
|
||||||
|
|
||||||
for (var ci = 0; ci < 4; ci++) {
|
|
||||||
var p1 = this.projection(c1[ci], a),
|
|
||||||
p2 = this.projection(c2[ci], a);
|
|
||||||
|
|
||||||
sc1.push(p1.x * a.x + p1.y * a.y);
|
|
||||||
sc2.push(p2.x * a.x + p2.y * a.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxc1 = Math.max.apply(Math, sc1),
|
|
||||||
maxc2 = Math.max.apply(Math, sc2),
|
|
||||||
minc1 = Math.min.apply(Math, sc1),
|
|
||||||
minc2 = Math.min.apply(Math, sc2);
|
|
||||||
|
|
||||||
return (minc2 <= maxc1 && maxc2 >= minc1);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether two rectangles collide on each one of their four axis. If
|
|
||||||
* all axis collide, then the two rectangles do collide on the plane.
|
|
||||||
*
|
|
||||||
* @param {array} Rectangle A's corners.
|
|
||||||
* @param {array} Rectangle B's corners.
|
|
||||||
* @return {boolean} True if the rectangles collide.
|
|
||||||
*/
|
|
||||||
collision: function(c1, c2) {
|
|
||||||
var axis = this.axis(c1, c2),
|
|
||||||
col = true;
|
|
||||||
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
col = col && this.axisCollision(axis[i], c1, c2);
|
|
||||||
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quad Functions
|
|
||||||
* ------------
|
|
||||||
*
|
|
||||||
* The Quadtree functions themselves.
|
|
||||||
* For each of those functions, we consider that in a splitted quad, the
|
|
||||||
* index of each node is the following:
|
|
||||||
* 0: top left
|
|
||||||
* 1: top right
|
|
||||||
* 2: bottom left
|
|
||||||
* 3: bottom right
|
|
||||||
*
|
|
||||||
* Moreover, the hereafter quad's philosophy is to consider that if an element
|
|
||||||
* collides with more than one nodes, this element belongs to each of the
|
|
||||||
* nodes it collides with where other would let it lie on a higher node.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the index of the node containing the point in the quad
|
|
||||||
*
|
|
||||||
* @param {object} point A point defined by coordinates (x, y).
|
|
||||||
* @param {object} quadBounds Boundaries of the quad (x, y, width, heigth).
|
|
||||||
* @return {integer} The index of the node containing the point.
|
|
||||||
*/
|
|
||||||
function _quadIndex(point, quadBounds) {
|
|
||||||
var xmp = quadBounds.x + quadBounds.width / 2,
|
|
||||||
ymp = quadBounds.y + quadBounds.height / 2,
|
|
||||||
top = (point.y < ymp),
|
|
||||||
left = (point.x < xmp);
|
|
||||||
|
|
||||||
if (top) {
|
|
||||||
if (left)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (left)
|
|
||||||
return 2;
|
|
||||||
else
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of indexes of nodes containing an axis-aligned rectangle
|
|
||||||
*
|
|
||||||
* @param {object} rectangle A rectangle defined by two points (x1, y1),
|
|
||||||
* (x2, y2) and height.
|
|
||||||
* @param {array} quadCorners An array of the quad nodes' corners.
|
|
||||||
* @return {array} An array of indexes containing one to
|
|
||||||
* four integers.
|
|
||||||
*/
|
|
||||||
function _quadIndexes(rectangle, quadCorners) {
|
|
||||||
var indexes = [];
|
|
||||||
|
|
||||||
// Iterating through quads
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
if ((rectangle.x2 >= quadCorners[i][0].x) &&
|
|
||||||
(rectangle.x1 <= quadCorners[i][1].x) &&
|
|
||||||
(rectangle.y1 + rectangle.height >= quadCorners[i][0].y) &&
|
|
||||||
(rectangle.y1 <= quadCorners[i][2].y))
|
|
||||||
indexes.push(i);
|
|
||||||
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of indexes of nodes containing a non-axis-aligned rectangle
|
|
||||||
*
|
|
||||||
* @param {array} corners An array containing each corner of the
|
|
||||||
* rectangle defined by its coordinates (x, y).
|
|
||||||
* @param {array} quadCorners An array of the quad nodes' corners.
|
|
||||||
* @return {array} An array of indexes containing one to
|
|
||||||
* four integers.
|
|
||||||
*/
|
|
||||||
function _quadCollision(corners, quadCorners) {
|
|
||||||
var indexes = [];
|
|
||||||
|
|
||||||
// Iterating through quads
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
if (_geom.collision(corners, quadCorners[i]))
|
|
||||||
indexes.push(i);
|
|
||||||
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subdivide a quad by creating a node at a precise index. The function does
|
|
||||||
* not generate all four nodes not to potentially create unused nodes.
|
|
||||||
*
|
|
||||||
* @param {integer} index The index of the node to create.
|
|
||||||
* @param {object} quad The quad object to subdivide.
|
|
||||||
* @return {object} A new quad representing the node created.
|
|
||||||
*/
|
|
||||||
function _quadSubdivide(index, quad) {
|
|
||||||
var next = quad.level + 1,
|
|
||||||
subw = Math.round(quad.bounds.width / 2),
|
|
||||||
subh = Math.round(quad.bounds.height / 2),
|
|
||||||
qx = Math.round(quad.bounds.x),
|
|
||||||
qy = Math.round(quad.bounds.y),
|
|
||||||
x,
|
|
||||||
y;
|
|
||||||
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
x = qx;
|
|
||||||
y = qy;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
x = qx + subw;
|
|
||||||
y = qy;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
x = qx;
|
|
||||||
y = qy + subh;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
x = qx + subw;
|
|
||||||
y = qy + subh;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _quadTree(
|
|
||||||
{x: x, y: y, width: subw, height: subh},
|
|
||||||
next,
|
|
||||||
quad.maxElements,
|
|
||||||
quad.maxLevel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively insert an element into the quadtree. Only points
|
|
||||||
* with size, i.e. axis-aligned squares, may be inserted with this
|
|
||||||
* method.
|
|
||||||
*
|
|
||||||
* @param {object} el The element to insert in the quadtree.
|
|
||||||
* @param {object} sizedPoint A sized point defined by two top points
|
|
||||||
* (x1, y1), (x2, y2) and height.
|
|
||||||
* @param {object} quad The quad in which to insert the element.
|
|
||||||
* @return {undefined} The function does not return anything.
|
|
||||||
*/
|
|
||||||
function _quadInsert(el, sizedPoint, quad) {
|
|
||||||
if (quad.level < quad.maxLevel) {
|
|
||||||
|
|
||||||
// Searching appropriate quads
|
|
||||||
var indexes = _quadIndexes(sizedPoint, quad.corners);
|
|
||||||
|
|
||||||
// Iterating
|
|
||||||
for (var i = 0, l = indexes.length; i < l; i++) {
|
|
||||||
|
|
||||||
// Subdividing if necessary
|
|
||||||
if (quad.nodes[indexes[i]] === undefined)
|
|
||||||
quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad);
|
|
||||||
|
|
||||||
// Recursion
|
|
||||||
_quadInsert(el, sizedPoint, quad.nodes[indexes[i]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Pushing the element in a leaf node
|
|
||||||
quad.elements.push(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively retrieve every elements held by the node containing the
|
|
||||||
* searched point.
|
|
||||||
*
|
|
||||||
* @param {object} point The searched point (x, y).
|
|
||||||
* @param {object} quad The searched quad.
|
|
||||||
* @return {array} An array of elements contained in the relevant
|
|
||||||
* node.
|
|
||||||
*/
|
|
||||||
function _quadRetrievePoint(point, quad) {
|
|
||||||
if (quad.level < quad.maxLevel) {
|
|
||||||
var index = _quadIndex(point, quad.bounds);
|
|
||||||
|
|
||||||
// If node does not exist we return an empty list
|
|
||||||
if (quad.nodes[index] !== undefined) {
|
|
||||||
return _quadRetrievePoint(point, quad.nodes[index]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return quad.elements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively retrieve every elements contained within an rectangular area
|
|
||||||
* that may or may not be axis-aligned.
|
|
||||||
*
|
|
||||||
* @param {object|array} rectData The searched area defined either by
|
|
||||||
* an array of four corners (x, y) in
|
|
||||||
* the case of a non-axis-aligned
|
|
||||||
* rectangle or an object with two top
|
|
||||||
* points (x1, y1), (x2, y2) and height.
|
|
||||||
* @param {object} quad The searched quad.
|
|
||||||
* @param {function} collisionFunc The collision function used to search
|
|
||||||
* for node indexes.
|
|
||||||
* @param {array?} els The retrieved elements.
|
|
||||||
* @return {array} An array of elements contained in the
|
|
||||||
* area.
|
|
||||||
*/
|
|
||||||
function _quadRetrieveArea(rectData, quad, collisionFunc, els) {
|
|
||||||
els = els || {};
|
|
||||||
|
|
||||||
if (quad.level < quad.maxLevel) {
|
|
||||||
var indexes = collisionFunc(rectData, quad.corners);
|
|
||||||
|
|
||||||
for (var i = 0, l = indexes.length; i < l; i++)
|
|
||||||
if (quad.nodes[indexes[i]] !== undefined)
|
|
||||||
_quadRetrieveArea(
|
|
||||||
rectData,
|
|
||||||
quad.nodes[indexes[i]],
|
|
||||||
collisionFunc,
|
|
||||||
els
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
for (var j = 0, m = quad.elements.length; j < m; j++)
|
|
||||||
if (els[quad.elements[j].id] === undefined)
|
|
||||||
els[quad.elements[j].id] = quad.elements[j];
|
|
||||||
|
|
||||||
return els;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the quadtree object itself.
|
|
||||||
*
|
|
||||||
* @param {object} bounds The boundaries of the quad defined by an
|
|
||||||
* origin (x, y), width and heigth.
|
|
||||||
* @param {integer} level The level of the quad in the tree.
|
|
||||||
* @param {integer} maxElements The max number of element in a leaf node.
|
|
||||||
* @param {integer} maxLevel The max recursion level of the tree.
|
|
||||||
* @return {object} The quadtree object.
|
|
||||||
*/
|
|
||||||
function _quadTree(bounds, level, maxElements, maxLevel) {
|
|
||||||
return {
|
|
||||||
level: level || 0,
|
|
||||||
bounds: bounds,
|
|
||||||
corners: _geom.splitSquare(bounds),
|
|
||||||
maxElements: maxElements || 40,
|
|
||||||
maxLevel: maxLevel || 8,
|
|
||||||
elements: [],
|
|
||||||
nodes: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sigma Quad Constructor
|
|
||||||
* ----------------------
|
|
||||||
*
|
|
||||||
* The edgequad API as exposed to sigma.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The edgequad core that will become the sigma interface with the quadtree.
|
|
||||||
*
|
|
||||||
* property {object} _tree Property holding the quadtree object.
|
|
||||||
* property {object} _geom Exposition of the _geom namespace for testing.
|
|
||||||
* property {object} _cache Cache for the area method.
|
|
||||||
* property {boolean} _enabled Can index and retreive elements.
|
|
||||||
*/
|
|
||||||
var edgequad = function() {
|
|
||||||
this._geom = _geom;
|
|
||||||
this._tree = null;
|
|
||||||
this._cache = {
|
|
||||||
query: false,
|
|
||||||
result: false
|
|
||||||
};
|
|
||||||
this._enabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Index a graph by inserting its edges into the quadtree.
|
|
||||||
*
|
|
||||||
* @param {object} graph A graph instance.
|
|
||||||
* @param {object} params An object of parameters with at least the quad
|
|
||||||
* bounds.
|
|
||||||
* @return {object} The quadtree object.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* ----------
|
|
||||||
* bounds: {object} boundaries of the quad defined by its origin (x, y)
|
|
||||||
* width and heigth.
|
|
||||||
* prefix: {string?} a prefix for edge geometric attributes.
|
|
||||||
* maxElements: {integer?} the max number of elements in a leaf node.
|
|
||||||
* maxLevel: {integer?} the max recursion level of the tree.
|
|
||||||
*/
|
|
||||||
edgequad.prototype.index = function(graph, params) {
|
|
||||||
if (!this._enabled)
|
|
||||||
return this._tree;
|
|
||||||
|
|
||||||
// Enforcing presence of boundaries
|
|
||||||
if (!params.bounds)
|
|
||||||
throw 'sigma.classes.edgequad.index: bounds information not given.';
|
|
||||||
|
|
||||||
// Prefix
|
|
||||||
var prefix = params.prefix || '',
|
|
||||||
cp,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
n,
|
|
||||||
e;
|
|
||||||
|
|
||||||
// Building the tree
|
|
||||||
this._tree = _quadTree(
|
|
||||||
params.bounds,
|
|
||||||
0,
|
|
||||||
params.maxElements,
|
|
||||||
params.maxLevel
|
|
||||||
);
|
|
||||||
|
|
||||||
var edges = graph.edges();
|
|
||||||
|
|
||||||
// Inserting graph edges into the tree
|
|
||||||
for (var i = 0, l = edges.length; i < l; i++) {
|
|
||||||
source = graph.nodes(edges[i].source);
|
|
||||||
target = graph.nodes(edges[i].target);
|
|
||||||
e = {
|
|
||||||
x1: source[prefix + 'x'],
|
|
||||||
y1: source[prefix + 'y'],
|
|
||||||
x2: target[prefix + 'x'],
|
|
||||||
y2: target[prefix + 'y'],
|
|
||||||
size: edges[i][prefix + 'size'] || 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inserting edge
|
|
||||||
if (edges[i].type === 'curve' || edges[i].type === 'curvedArrow') {
|
|
||||||
if (source.id === target.id) {
|
|
||||||
n = {
|
|
||||||
x: source[prefix + 'x'],
|
|
||||||
y: source[prefix + 'y'],
|
|
||||||
size: source[prefix + 'size'] || 0
|
|
||||||
};
|
|
||||||
_quadInsert(
|
|
||||||
edges[i],
|
|
||||||
_geom.selfLoopToSquare(n),
|
|
||||||
this._tree);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cp = sigma.utils.getQuadraticControlPoint(e.x1, e.y1, e.x2, e.y2);
|
|
||||||
_quadInsert(
|
|
||||||
edges[i],
|
|
||||||
_geom.quadraticCurveToSquare(e, cp),
|
|
||||||
this._tree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_quadInsert(
|
|
||||||
edges[i],
|
|
||||||
_geom.lineToSquare(e),
|
|
||||||
this._tree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset cache:
|
|
||||||
this._cache = {
|
|
||||||
query: false,
|
|
||||||
result: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// remove?
|
|
||||||
return this._tree;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve every graph edges held by the quadtree node containing the
|
|
||||||
* searched point.
|
|
||||||
*
|
|
||||||
* @param {number} x of the point.
|
|
||||||
* @param {number} y of the point.
|
|
||||||
* @return {array} An array of edges retrieved.
|
|
||||||
*/
|
|
||||||
edgequad.prototype.point = function(x, y) {
|
|
||||||
if (!this._enabled)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return this._tree ?
|
|
||||||
_quadRetrievePoint({x: x, y: y}, this._tree) || [] :
|
|
||||||
[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve every graph edges within a rectangular area. The methods keep the
|
|
||||||
* last area queried in cache for optimization reason and will act differently
|
|
||||||
* for the same reason if the area is axis-aligned or not.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two top points (x1, y1), (x2, y2)
|
|
||||||
* and height.
|
|
||||||
* @return {array} An array of edges retrieved.
|
|
||||||
*/
|
|
||||||
edgequad.prototype.area = function(rect) {
|
|
||||||
if (!this._enabled)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var serialized = JSON.stringify(rect),
|
|
||||||
collisionFunc,
|
|
||||||
rectData;
|
|
||||||
|
|
||||||
// Returning cache?
|
|
||||||
if (this._cache.query === serialized)
|
|
||||||
return this._cache.result;
|
|
||||||
|
|
||||||
// Axis aligned ?
|
|
||||||
if (_geom.isAxisAligned(rect)) {
|
|
||||||
collisionFunc = _quadIndexes;
|
|
||||||
rectData = _geom.axisAlignedTopPoints(rect);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
collisionFunc = _quadCollision;
|
|
||||||
rectData = _geom.rectangleCorners(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieving edges
|
|
||||||
var edges = this._tree ?
|
|
||||||
_quadRetrieveArea(
|
|
||||||
rectData,
|
|
||||||
this._tree,
|
|
||||||
collisionFunc
|
|
||||||
) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
// Object to array
|
|
||||||
var edgesArray = [];
|
|
||||||
for (var i in edges)
|
|
||||||
edgesArray.push(edges[i]);
|
|
||||||
|
|
||||||
// Caching
|
|
||||||
this._cache.query = serialized;
|
|
||||||
this._cache.result = edgesArray;
|
|
||||||
|
|
||||||
return edgesArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EXPORT:
|
|
||||||
* *******
|
|
||||||
*/
|
|
||||||
if (typeof this.sigma !== 'undefined') {
|
|
||||||
this.sigma.classes = this.sigma.classes || {};
|
|
||||||
this.sigma.classes.edgequad = edgequad;
|
|
||||||
} else if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports)
|
|
||||||
exports = module.exports = edgequad;
|
|
||||||
exports.edgequad = edgequad;
|
|
||||||
} else
|
|
||||||
this.edgequad = edgequad;
|
|
||||||
}).call(this);
|
|
@ -1,859 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var _methods = Object.create(null),
|
|
||||||
_indexes = Object.create(null),
|
|
||||||
_initBindings = Object.create(null),
|
|
||||||
_methodBindings = Object.create(null),
|
|
||||||
_methodBeforeBindings = Object.create(null),
|
|
||||||
_defaultSettings = {
|
|
||||||
immutable: true,
|
|
||||||
clone: true
|
|
||||||
},
|
|
||||||
_defaultSettingsFunction = function(key) {
|
|
||||||
return _defaultSettings[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The graph constructor. It initializes the data and the indexes, and binds
|
|
||||||
* the custom indexes and methods to its own scope.
|
|
||||||
*
|
|
||||||
* Recognized parameters:
|
|
||||||
* **********************
|
|
||||||
* Here is the exhaustive list of every accepted parameters in the settings
|
|
||||||
* object:
|
|
||||||
*
|
|
||||||
* {boolean} clone Indicates if the data have to be cloned in methods
|
|
||||||
* to add nodes or edges.
|
|
||||||
* {boolean} immutable Indicates if nodes "id" values and edges "id",
|
|
||||||
* "source" and "target" values must be set as
|
|
||||||
* immutable.
|
|
||||||
*
|
|
||||||
* @param {?configurable} settings Eventually a settings function.
|
|
||||||
* @return {graph} The new graph instance.
|
|
||||||
*/
|
|
||||||
var graph = function(settings) {
|
|
||||||
var k,
|
|
||||||
fn,
|
|
||||||
data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DATA:
|
|
||||||
* *****
|
|
||||||
* Every data that is callable from graph methods are stored in this "data"
|
|
||||||
* object. This object will be served as context for all these methods,
|
|
||||||
* and it is possible to add other type of data in it.
|
|
||||||
*/
|
|
||||||
data = {
|
|
||||||
/**
|
|
||||||
* SETTINGS FUNCTION:
|
|
||||||
* ******************
|
|
||||||
*/
|
|
||||||
settings: settings || _defaultSettingsFunction,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MAIN DATA:
|
|
||||||
* **********
|
|
||||||
*/
|
|
||||||
nodesArray: [],
|
|
||||||
edgesArray: [],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GLOBAL INDEXES:
|
|
||||||
* ***************
|
|
||||||
* These indexes just index data by ids.
|
|
||||||
*/
|
|
||||||
nodesIndex: Object.create(null),
|
|
||||||
edgesIndex: Object.create(null),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LOCAL INDEXES:
|
|
||||||
* **************
|
|
||||||
* These indexes refer from node to nodes. Each key is an id, and each
|
|
||||||
* value is the array of the ids of related nodes.
|
|
||||||
*/
|
|
||||||
inNeighborsIndex: Object.create(null),
|
|
||||||
outNeighborsIndex: Object.create(null),
|
|
||||||
allNeighborsIndex: Object.create(null),
|
|
||||||
|
|
||||||
inNeighborsCount: Object.create(null),
|
|
||||||
outNeighborsCount: Object.create(null),
|
|
||||||
allNeighborsCount: Object.create(null)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute bindings:
|
|
||||||
for (k in _initBindings)
|
|
||||||
_initBindings[k].call(data);
|
|
||||||
|
|
||||||
// Add methods to both the scope and the data objects:
|
|
||||||
for (k in _methods) {
|
|
||||||
fn = __bindGraphMethod(k, data, _methods[k]);
|
|
||||||
this[k] = fn;
|
|
||||||
data[k] = fn;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A custom tool to bind methods such that function that are bound to it will
|
|
||||||
* be executed anytime the method is called.
|
|
||||||
*
|
|
||||||
* @param {string} methodName The name of the method to bind.
|
|
||||||
* @param {object} scope The scope where the method must be executed.
|
|
||||||
* @param {function} fn The method itself.
|
|
||||||
* @return {function} The new method.
|
|
||||||
*/
|
|
||||||
function __bindGraphMethod(methodName, scope, fn) {
|
|
||||||
var result = function() {
|
|
||||||
var k,
|
|
||||||
res;
|
|
||||||
|
|
||||||
// Execute "before" bound functions:
|
|
||||||
for (k in _methodBeforeBindings[methodName])
|
|
||||||
_methodBeforeBindings[methodName][k].apply(scope, arguments);
|
|
||||||
|
|
||||||
// Apply the method:
|
|
||||||
res = fn.apply(scope, arguments);
|
|
||||||
|
|
||||||
// Execute bound functions:
|
|
||||||
for (k in _methodBindings[methodName])
|
|
||||||
_methodBindings[methodName][k].apply(scope, arguments);
|
|
||||||
|
|
||||||
// Return res:
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This custom tool function removes every pair key/value from an hash. The
|
|
||||||
* goal is to avoid creating a new object while some other references are
|
|
||||||
* still hanging in some scopes...
|
|
||||||
*
|
|
||||||
* @param {object} obj The object to empty.
|
|
||||||
* @return {object} The empty object.
|
|
||||||
*/
|
|
||||||
function __emptyObject(obj) {
|
|
||||||
var k;
|
|
||||||
|
|
||||||
for (k in obj)
|
|
||||||
if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
|
|
||||||
delete obj[k];
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This global method adds a method that will be bound to the futurly created
|
|
||||||
* graph instances.
|
|
||||||
*
|
|
||||||
* Since these methods will be bound to their scope when the instances are
|
|
||||||
* created, it does not use the prototype. Because of that, methods have to
|
|
||||||
* be added before instances are created to make them available.
|
|
||||||
*
|
|
||||||
* Here is an example:
|
|
||||||
*
|
|
||||||
* > graph.addMethod('getNodesCount', function() {
|
|
||||||
* > return this.nodesArray.length;
|
|
||||||
* > });
|
|
||||||
* >
|
|
||||||
* > var myGraph = new graph();
|
|
||||||
* > console.log(myGraph.getNodesCount()); // outputs 0
|
|
||||||
*
|
|
||||||
* @param {string} methodName The name of the method.
|
|
||||||
* @param {function} fn The method itself.
|
|
||||||
* @return {object} The global graph constructor.
|
|
||||||
*/
|
|
||||||
graph.addMethod = function(methodName, fn) {
|
|
||||||
if (
|
|
||||||
typeof methodName !== 'string' ||
|
|
||||||
typeof fn !== 'function' ||
|
|
||||||
arguments.length !== 2
|
|
||||||
)
|
|
||||||
throw 'addMethod: Wrong arguments.';
|
|
||||||
|
|
||||||
if (_methods[methodName] || graph[methodName])
|
|
||||||
throw 'The method "' + methodName + '" already exists.';
|
|
||||||
|
|
||||||
_methods[methodName] = fn;
|
|
||||||
_methodBindings[methodName] = Object.create(null);
|
|
||||||
_methodBeforeBindings[methodName] = Object.create(null);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This global method returns true if the method has already been added, and
|
|
||||||
* false else.
|
|
||||||
*
|
|
||||||
* Here are some examples:
|
|
||||||
*
|
|
||||||
* > graph.hasMethod('addNode'); // returns true
|
|
||||||
* > graph.hasMethod('hasMethod'); // returns true
|
|
||||||
* > graph.hasMethod('unexistingMethod'); // returns false
|
|
||||||
*
|
|
||||||
* @param {string} methodName The name of the method.
|
|
||||||
* @return {boolean} The result.
|
|
||||||
*/
|
|
||||||
graph.hasMethod = function(methodName) {
|
|
||||||
return !!(_methods[methodName] || graph[methodName]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This global methods attaches a function to a method. Anytime the specified
|
|
||||||
* method is called, the attached function is called right after, with the
|
|
||||||
* same arguments and in the same scope. The attached function is called
|
|
||||||
* right before if the last argument is true, unless the method is the graph
|
|
||||||
* constructor.
|
|
||||||
*
|
|
||||||
* To attach a function to the graph constructor, use 'constructor' as the
|
|
||||||
* method name (first argument).
|
|
||||||
*
|
|
||||||
* The main idea is to have a clean way to keep custom indexes up to date,
|
|
||||||
* for instance:
|
|
||||||
*
|
|
||||||
* > var timesAddNodeCalled = 0;
|
|
||||||
* > graph.attach('addNode', 'timesAddNodeCalledInc', function() {
|
|
||||||
* > timesAddNodeCalled++;
|
|
||||||
* > });
|
|
||||||
* >
|
|
||||||
* > var myGraph = new graph();
|
|
||||||
* > console.log(timesAddNodeCalled); // outputs 0
|
|
||||||
* >
|
|
||||||
* > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
|
|
||||||
* > console.log(timesAddNodeCalled); // outputs 2
|
|
||||||
*
|
|
||||||
* The idea for calling a function before is to provide pre-processors, for
|
|
||||||
* instance:
|
|
||||||
*
|
|
||||||
* > var colorPalette = { Person: '#C3CBE1', Place: '#9BDEBD' };
|
|
||||||
* > graph.attach('addNode', 'applyNodeColorPalette', function(n) {
|
|
||||||
* > n.color = colorPalette[n.category];
|
|
||||||
* > }, true);
|
|
||||||
* >
|
|
||||||
* > var myGraph = new graph();
|
|
||||||
* > myGraph.addNode({ id: 'n0', category: 'Person' });
|
|
||||||
* > console.log(myGraph.nodes('n0').color); // outputs '#C3CBE1'
|
|
||||||
*
|
|
||||||
* @param {string} methodName The name of the related method or
|
|
||||||
* "constructor".
|
|
||||||
* @param {string} key The key to identify the function to attach.
|
|
||||||
* @param {function} fn The function to bind.
|
|
||||||
* @param {boolean} before If true the function is called right before.
|
|
||||||
* @return {object} The global graph constructor.
|
|
||||||
*/
|
|
||||||
graph.attach = function(methodName, key, fn, before) {
|
|
||||||
if (
|
|
||||||
typeof methodName !== 'string' ||
|
|
||||||
typeof key !== 'string' ||
|
|
||||||
typeof fn !== 'function' ||
|
|
||||||
arguments.length < 3 ||
|
|
||||||
arguments.length > 4
|
|
||||||
)
|
|
||||||
throw 'attach: Wrong arguments.';
|
|
||||||
|
|
||||||
var bindings;
|
|
||||||
|
|
||||||
if (methodName === 'constructor')
|
|
||||||
bindings = _initBindings;
|
|
||||||
else {
|
|
||||||
if (before) {
|
|
||||||
if (!_methodBeforeBindings[methodName])
|
|
||||||
throw 'The method "' + methodName + '" does not exist.';
|
|
||||||
|
|
||||||
bindings = _methodBeforeBindings[methodName];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!_methodBindings[methodName])
|
|
||||||
throw 'The method "' + methodName + '" does not exist.';
|
|
||||||
|
|
||||||
bindings = _methodBindings[methodName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bindings[key])
|
|
||||||
throw 'A function "' + key + '" is already attached ' +
|
|
||||||
'to the method "' + methodName + '".';
|
|
||||||
|
|
||||||
bindings[key] = fn;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias of attach(methodName, key, fn, true).
|
|
||||||
*/
|
|
||||||
graph.attachBefore = function(methodName, key, fn) {
|
|
||||||
return this.attach(methodName, key, fn, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This methods is just an helper to deal with custom indexes. It takes as
|
|
||||||
* arguments the name of the index and an object containing all the different
|
|
||||||
* functions to bind to the methods.
|
|
||||||
*
|
|
||||||
* Here is a basic example, that creates an index to keep the number of nodes
|
|
||||||
* in the current graph. It also adds a method to provide a getter on that
|
|
||||||
* new index:
|
|
||||||
*
|
|
||||||
* > sigma.classes.graph.addIndex('nodesCount', {
|
|
||||||
* > constructor: function() {
|
|
||||||
* > this.nodesCount = 0;
|
|
||||||
* > },
|
|
||||||
* > addNode: function() {
|
|
||||||
* > this.nodesCount++;
|
|
||||||
* > },
|
|
||||||
* > dropNode: function() {
|
|
||||||
* > this.nodesCount--;
|
|
||||||
* > }
|
|
||||||
* > });
|
|
||||||
* >
|
|
||||||
* > sigma.classes.graph.addMethod('getNodesCount', function() {
|
|
||||||
* > return this.nodesCount;
|
|
||||||
* > });
|
|
||||||
* >
|
|
||||||
* > var myGraph = new sigma.classes.graph();
|
|
||||||
* > console.log(myGraph.getNodesCount()); // outputs 0
|
|
||||||
* >
|
|
||||||
* > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
|
|
||||||
* > console.log(myGraph.getNodesCount()); // outputs 2
|
|
||||||
*
|
|
||||||
* @param {string} name The name of the index.
|
|
||||||
* @param {object} bindings The object containing the functions to bind.
|
|
||||||
* @return {object} The global graph constructor.
|
|
||||||
*/
|
|
||||||
graph.addIndex = function(name, bindings) {
|
|
||||||
if (
|
|
||||||
typeof name !== 'string' ||
|
|
||||||
Object(bindings) !== bindings ||
|
|
||||||
arguments.length !== 2
|
|
||||||
)
|
|
||||||
throw 'addIndex: Wrong arguments.';
|
|
||||||
|
|
||||||
if (_indexes[name])
|
|
||||||
throw 'The index "' + name + '" already exists.';
|
|
||||||
|
|
||||||
var k;
|
|
||||||
|
|
||||||
// Store the bindings:
|
|
||||||
_indexes[name] = bindings;
|
|
||||||
|
|
||||||
// Attach the bindings:
|
|
||||||
for (k in bindings)
|
|
||||||
if (typeof bindings[k] !== 'function')
|
|
||||||
throw 'The bindings must be functions.';
|
|
||||||
else
|
|
||||||
graph.attach(k, name, bindings[k]);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method adds a node to the graph. The node must be an object, with a
|
|
||||||
* string under the key "id". Except for this, it is possible to add any
|
|
||||||
* other attribute, that will be preserved all along the manipulations.
|
|
||||||
*
|
|
||||||
* If the graph option "clone" has a truthy value, the node will be cloned
|
|
||||||
* when added to the graph. Also, if the graph option "immutable" has a
|
|
||||||
* truthy value, its id will be defined as immutable.
|
|
||||||
*
|
|
||||||
* @param {object} node The node to add.
|
|
||||||
* @return {object} The graph instance.
|
|
||||||
*/
|
|
||||||
graph.addMethod('addNode', function(node) {
|
|
||||||
// Check that the node is an object and has an id:
|
|
||||||
if (Object(node) !== node || arguments.length !== 1)
|
|
||||||
throw 'addNode: Wrong arguments.';
|
|
||||||
|
|
||||||
if (typeof node.id !== 'string' && typeof node.id !== 'number')
|
|
||||||
throw 'The node must have a string or number id.';
|
|
||||||
|
|
||||||
if (this.nodesIndex[node.id])
|
|
||||||
throw 'The node "' + node.id + '" already exists.';
|
|
||||||
|
|
||||||
var k,
|
|
||||||
id = node.id,
|
|
||||||
validNode = Object.create(null);
|
|
||||||
|
|
||||||
// Check the "clone" option:
|
|
||||||
if (this.settings('clone')) {
|
|
||||||
for (k in node)
|
|
||||||
if (k !== 'id')
|
|
||||||
validNode[k] = node[k];
|
|
||||||
} else
|
|
||||||
validNode = node;
|
|
||||||
|
|
||||||
// Check the "immutable" option:
|
|
||||||
if (this.settings('immutable'))
|
|
||||||
Object.defineProperty(validNode, 'id', {
|
|
||||||
value: id,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
else
|
|
||||||
validNode.id = id;
|
|
||||||
|
|
||||||
// Add empty containers for edges indexes:
|
|
||||||
this.inNeighborsIndex[id] = Object.create(null);
|
|
||||||
this.outNeighborsIndex[id] = Object.create(null);
|
|
||||||
this.allNeighborsIndex[id] = Object.create(null);
|
|
||||||
|
|
||||||
this.inNeighborsCount[id] = 0;
|
|
||||||
this.outNeighborsCount[id] = 0;
|
|
||||||
this.allNeighborsCount[id] = 0;
|
|
||||||
|
|
||||||
// Add the node to indexes:
|
|
||||||
this.nodesArray.push(validNode);
|
|
||||||
this.nodesIndex[validNode.id] = validNode;
|
|
||||||
|
|
||||||
// Return the current instance:
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method adds an edge to the graph. The edge must be an object, with a
|
|
||||||
* string under the key "id", and strings under the keys "source" and
|
|
||||||
* "target" that design existing nodes. Except for this, it is possible to
|
|
||||||
* add any other attribute, that will be preserved all along the
|
|
||||||
* manipulations.
|
|
||||||
*
|
|
||||||
* If the graph option "clone" has a truthy value, the edge will be cloned
|
|
||||||
* when added to the graph. Also, if the graph option "immutable" has a
|
|
||||||
* truthy value, its id, source and target will be defined as immutable.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge to add.
|
|
||||||
* @return {object} The graph instance.
|
|
||||||
*/
|
|
||||||
graph.addMethod('addEdge', function(edge) {
|
|
||||||
// Check that the edge is an object and has an id:
|
|
||||||
if (Object(edge) !== edge || arguments.length !== 1)
|
|
||||||
throw 'addEdge: Wrong arguments.';
|
|
||||||
|
|
||||||
if (typeof edge.id !== 'string' && typeof edge.id !== 'number')
|
|
||||||
throw 'The edge must have a string or number id.';
|
|
||||||
|
|
||||||
if ((typeof edge.source !== 'string' && typeof edge.source !== 'number') ||
|
|
||||||
!this.nodesIndex[edge.source])
|
|
||||||
throw 'The edge source must have an existing node id.';
|
|
||||||
|
|
||||||
if ((typeof edge.target !== 'string' && typeof edge.target !== 'number') ||
|
|
||||||
!this.nodesIndex[edge.target])
|
|
||||||
throw 'The edge target must have an existing node id.';
|
|
||||||
|
|
||||||
if (this.edgesIndex[edge.id])
|
|
||||||
throw 'The edge "' + edge.id + '" already exists.';
|
|
||||||
|
|
||||||
var k,
|
|
||||||
validEdge = Object.create(null);
|
|
||||||
|
|
||||||
// Check the "clone" option:
|
|
||||||
if (this.settings('clone')) {
|
|
||||||
for (k in edge)
|
|
||||||
if (k !== 'id' && k !== 'source' && k !== 'target')
|
|
||||||
validEdge[k] = edge[k];
|
|
||||||
} else
|
|
||||||
validEdge = edge;
|
|
||||||
|
|
||||||
// Check the "immutable" option:
|
|
||||||
if (this.settings('immutable')) {
|
|
||||||
Object.defineProperty(validEdge, 'id', {
|
|
||||||
value: edge.id,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(validEdge, 'source', {
|
|
||||||
value: edge.source,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(validEdge, 'target', {
|
|
||||||
value: edge.target,
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
validEdge.id = edge.id;
|
|
||||||
validEdge.source = edge.source;
|
|
||||||
validEdge.target = edge.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the edge to indexes:
|
|
||||||
this.edgesArray.push(validEdge);
|
|
||||||
this.edgesIndex[validEdge.id] = validEdge;
|
|
||||||
|
|
||||||
if (!this.inNeighborsIndex[validEdge.target][validEdge.source])
|
|
||||||
this.inNeighborsIndex[validEdge.target][validEdge.source] =
|
|
||||||
Object.create(null);
|
|
||||||
this.inNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
|
|
||||||
validEdge;
|
|
||||||
|
|
||||||
if (!this.outNeighborsIndex[validEdge.source][validEdge.target])
|
|
||||||
this.outNeighborsIndex[validEdge.source][validEdge.target] =
|
|
||||||
Object.create(null);
|
|
||||||
this.outNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
|
|
||||||
validEdge;
|
|
||||||
|
|
||||||
if (!this.allNeighborsIndex[validEdge.source][validEdge.target])
|
|
||||||
this.allNeighborsIndex[validEdge.source][validEdge.target] =
|
|
||||||
Object.create(null);
|
|
||||||
this.allNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
|
|
||||||
validEdge;
|
|
||||||
|
|
||||||
if (validEdge.target !== validEdge.source) {
|
|
||||||
if (!this.allNeighborsIndex[validEdge.target][validEdge.source])
|
|
||||||
this.allNeighborsIndex[validEdge.target][validEdge.source] =
|
|
||||||
Object.create(null);
|
|
||||||
this.allNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
|
|
||||||
validEdge;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep counts up to date:
|
|
||||||
this.inNeighborsCount[validEdge.target]++;
|
|
||||||
this.outNeighborsCount[validEdge.source]++;
|
|
||||||
this.allNeighborsCount[validEdge.target]++;
|
|
||||||
this.allNeighborsCount[validEdge.source]++;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method drops a node from the graph. It also removes each edge that is
|
|
||||||
* bound to it, through the dropEdge method. An error is thrown if the node
|
|
||||||
* does not exist.
|
|
||||||
*
|
|
||||||
* @param {string} id The node id.
|
|
||||||
* @return {object} The graph instance.
|
|
||||||
*/
|
|
||||||
graph.addMethod('dropNode', function(id) {
|
|
||||||
// Check that the arguments are valid:
|
|
||||||
if ((typeof id !== 'string' && typeof id !== 'number') ||
|
|
||||||
arguments.length !== 1)
|
|
||||||
throw 'dropNode: Wrong arguments.';
|
|
||||||
|
|
||||||
if (!this.nodesIndex[id])
|
|
||||||
throw 'The node "' + id + '" does not exist.';
|
|
||||||
|
|
||||||
var i, k, l;
|
|
||||||
|
|
||||||
// Remove the node from indexes:
|
|
||||||
delete this.nodesIndex[id];
|
|
||||||
for (i = 0, l = this.nodesArray.length; i < l; i++)
|
|
||||||
if (this.nodesArray[i].id === id) {
|
|
||||||
this.nodesArray.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove related edges:
|
|
||||||
for (i = this.edgesArray.length - 1; i >= 0; i--)
|
|
||||||
if (this.edgesArray[i].source === id || this.edgesArray[i].target === id)
|
|
||||||
this.dropEdge(this.edgesArray[i].id);
|
|
||||||
|
|
||||||
// Remove related edge indexes:
|
|
||||||
delete this.inNeighborsIndex[id];
|
|
||||||
delete this.outNeighborsIndex[id];
|
|
||||||
delete this.allNeighborsIndex[id];
|
|
||||||
|
|
||||||
delete this.inNeighborsCount[id];
|
|
||||||
delete this.outNeighborsCount[id];
|
|
||||||
delete this.allNeighborsCount[id];
|
|
||||||
|
|
||||||
for (k in this.nodesIndex) {
|
|
||||||
delete this.inNeighborsIndex[k][id];
|
|
||||||
delete this.outNeighborsIndex[k][id];
|
|
||||||
delete this.allNeighborsIndex[k][id];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method drops an edge from the graph. An error is thrown if the edge
|
|
||||||
* does not exist.
|
|
||||||
*
|
|
||||||
* @param {string} id The edge id.
|
|
||||||
* @return {object} The graph instance.
|
|
||||||
*/
|
|
||||||
graph.addMethod('dropEdge', function(id) {
|
|
||||||
// Check that the arguments are valid:
|
|
||||||
if ((typeof id !== 'string' && typeof id !== 'number') ||
|
|
||||||
arguments.length !== 1)
|
|
||||||
throw 'dropEdge: Wrong arguments.';
|
|
||||||
|
|
||||||
if (!this.edgesIndex[id])
|
|
||||||
throw 'The edge "' + id + '" does not exist.';
|
|
||||||
|
|
||||||
var i, l, edge;
|
|
||||||
|
|
||||||
// Remove the edge from indexes:
|
|
||||||
edge = this.edgesIndex[id];
|
|
||||||
delete this.edgesIndex[id];
|
|
||||||
for (i = 0, l = this.edgesArray.length; i < l; i++)
|
|
||||||
if (this.edgesArray[i].id === id) {
|
|
||||||
this.edgesArray.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete this.inNeighborsIndex[edge.target][edge.source][edge.id];
|
|
||||||
if (!Object.keys(this.inNeighborsIndex[edge.target][edge.source]).length)
|
|
||||||
delete this.inNeighborsIndex[edge.target][edge.source];
|
|
||||||
|
|
||||||
delete this.outNeighborsIndex[edge.source][edge.target][edge.id];
|
|
||||||
if (!Object.keys(this.outNeighborsIndex[edge.source][edge.target]).length)
|
|
||||||
delete this.outNeighborsIndex[edge.source][edge.target];
|
|
||||||
|
|
||||||
delete this.allNeighborsIndex[edge.source][edge.target][edge.id];
|
|
||||||
if (!Object.keys(this.allNeighborsIndex[edge.source][edge.target]).length)
|
|
||||||
delete this.allNeighborsIndex[edge.source][edge.target];
|
|
||||||
|
|
||||||
if (edge.target !== edge.source) {
|
|
||||||
delete this.allNeighborsIndex[edge.target][edge.source][edge.id];
|
|
||||||
if (!Object.keys(this.allNeighborsIndex[edge.target][edge.source]).length)
|
|
||||||
delete this.allNeighborsIndex[edge.target][edge.source];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inNeighborsCount[edge.target]--;
|
|
||||||
this.outNeighborsCount[edge.source]--;
|
|
||||||
this.allNeighborsCount[edge.source]--;
|
|
||||||
this.allNeighborsCount[edge.target]--;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method destroys the current instance. It basically empties each index
|
|
||||||
* and methods attached to the graph.
|
|
||||||
*/
|
|
||||||
graph.addMethod('kill', function() {
|
|
||||||
// Delete arrays:
|
|
||||||
this.nodesArray.length = 0;
|
|
||||||
this.edgesArray.length = 0;
|
|
||||||
delete this.nodesArray;
|
|
||||||
delete this.edgesArray;
|
|
||||||
|
|
||||||
// Delete indexes:
|
|
||||||
delete this.nodesIndex;
|
|
||||||
delete this.edgesIndex;
|
|
||||||
delete this.inNeighborsIndex;
|
|
||||||
delete this.outNeighborsIndex;
|
|
||||||
delete this.allNeighborsIndex;
|
|
||||||
delete this.inNeighborsCount;
|
|
||||||
delete this.outNeighborsCount;
|
|
||||||
delete this.allNeighborsCount;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method empties the nodes and edges arrays, as well as the different
|
|
||||||
* indexes.
|
|
||||||
*
|
|
||||||
* @return {object} The graph instance.
|
|
||||||
*/
|
|
||||||
graph.addMethod('clear', function() {
|
|
||||||
this.nodesArray.length = 0;
|
|
||||||
this.edgesArray.length = 0;
|
|
||||||
|
|
||||||
// Due to GC issues, I prefer not to create new object. These objects are
|
|
||||||
// only available from the methods and attached functions, but still, it is
|
|
||||||
// better to prevent ghost references to unrelevant data...
|
|
||||||
__emptyObject(this.nodesIndex);
|
|
||||||
__emptyObject(this.edgesIndex);
|
|
||||||
__emptyObject(this.nodesIndex);
|
|
||||||
__emptyObject(this.inNeighborsIndex);
|
|
||||||
__emptyObject(this.outNeighborsIndex);
|
|
||||||
__emptyObject(this.allNeighborsIndex);
|
|
||||||
__emptyObject(this.inNeighborsCount);
|
|
||||||
__emptyObject(this.outNeighborsCount);
|
|
||||||
__emptyObject(this.allNeighborsCount);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method reads an object and adds the nodes and edges, through the
|
|
||||||
* proper methods "addNode" and "addEdge".
|
|
||||||
*
|
|
||||||
* Here is an example:
|
|
||||||
*
|
|
||||||
* > var myGraph = new graph();
|
|
||||||
* > myGraph.read({
|
|
||||||
* > nodes: [
|
|
||||||
* > { id: 'n0' },
|
|
||||||
* > { id: 'n1' }
|
|
||||||
* > ],
|
|
||||||
* > edges: [
|
|
||||||
* > {
|
|
||||||
* > id: 'e0',
|
|
||||||
* > source: 'n0',
|
|
||||||
* > target: 'n1'
|
|
||||||
* > }
|
|
||||||
* > ]
|
|
||||||
* > });
|
|
||||||
* >
|
|
||||||
* > console.log(
|
|
||||||
* > myGraph.nodes().length,
|
|
||||||
* > myGraph.edges().length
|
|
||||||
* > ); // outputs 2 1
|
|
||||||
*
|
|
||||||
* @param {object} g The graph object.
|
|
||||||
* @return {object} The graph instance.
|
|
||||||
*/
|
|
||||||
graph.addMethod('read', function(g) {
|
|
||||||
var i,
|
|
||||||
a,
|
|
||||||
l;
|
|
||||||
|
|
||||||
a = g.nodes || [];
|
|
||||||
for (i = 0, l = a.length; i < l; i++)
|
|
||||||
this.addNode(a[i]);
|
|
||||||
|
|
||||||
a = g.edges || [];
|
|
||||||
for (i = 0, l = a.length; i < l; i++)
|
|
||||||
this.addEdge(a[i]);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This methods returns one or several nodes, depending on how it is called.
|
|
||||||
*
|
|
||||||
* To get the array of nodes, call "nodes" without argument. To get a
|
|
||||||
* specific node, call it with the id of the node. The get multiple node,
|
|
||||||
* call it with an array of ids, and it will return the array of nodes, in
|
|
||||||
* the same order.
|
|
||||||
*
|
|
||||||
* @param {?(string|array)} v Eventually one id, an array of ids.
|
|
||||||
* @return {object|array} The related node or array of nodes.
|
|
||||||
*/
|
|
||||||
graph.addMethod('nodes', function(v) {
|
|
||||||
// Clone the array of nodes and return it:
|
|
||||||
if (!arguments.length)
|
|
||||||
return this.nodesArray.slice(0);
|
|
||||||
|
|
||||||
// Return the related node:
|
|
||||||
if (arguments.length === 1 &&
|
|
||||||
(typeof v === 'string' || typeof v === 'number'))
|
|
||||||
return this.nodesIndex[v];
|
|
||||||
|
|
||||||
// Return an array of the related node:
|
|
||||||
if (
|
|
||||||
arguments.length === 1 &&
|
|
||||||
Object.prototype.toString.call(v) === '[object Array]'
|
|
||||||
) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (i = 0, l = v.length; i < l; i++)
|
|
||||||
if (typeof v[i] === 'string' || typeof v[i] === 'number')
|
|
||||||
a.push(this.nodesIndex[v[i]]);
|
|
||||||
else
|
|
||||||
throw 'nodes: Wrong arguments.';
|
|
||||||
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw 'nodes: Wrong arguments.';
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This methods returns the degree of one or several nodes, depending on how
|
|
||||||
* it is called. It is also possible to get incoming or outcoming degrees
|
|
||||||
* instead by specifying 'in' or 'out' as a second argument.
|
|
||||||
*
|
|
||||||
* @param {string|array} v One id, an array of ids.
|
|
||||||
* @param {?string} which Which degree is required. Values are 'in',
|
|
||||||
* 'out', and by default the normal degree.
|
|
||||||
* @return {number|array} The related degree or array of degrees.
|
|
||||||
*/
|
|
||||||
graph.addMethod('degree', function(v, which) {
|
|
||||||
// Check which degree is required:
|
|
||||||
which = {
|
|
||||||
'in': this.inNeighborsCount,
|
|
||||||
'out': this.outNeighborsCount
|
|
||||||
}[which || ''] || this.allNeighborsCount;
|
|
||||||
|
|
||||||
// Return the related node:
|
|
||||||
if (typeof v === 'string' || typeof v === 'number')
|
|
||||||
return which[v];
|
|
||||||
|
|
||||||
// Return an array of the related node:
|
|
||||||
if (Object.prototype.toString.call(v) === '[object Array]') {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (i = 0, l = v.length; i < l; i++)
|
|
||||||
if (typeof v[i] === 'string' || typeof v[i] === 'number')
|
|
||||||
a.push(which[v[i]]);
|
|
||||||
else
|
|
||||||
throw 'degree: Wrong arguments.';
|
|
||||||
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw 'degree: Wrong arguments.';
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This methods returns one or several edges, depending on how it is called.
|
|
||||||
*
|
|
||||||
* To get the array of edges, call "edges" without argument. To get a
|
|
||||||
* specific edge, call it with the id of the edge. The get multiple edge,
|
|
||||||
* call it with an array of ids, and it will return the array of edges, in
|
|
||||||
* the same order.
|
|
||||||
*
|
|
||||||
* @param {?(string|array)} v Eventually one id, an array of ids.
|
|
||||||
* @return {object|array} The related edge or array of edges.
|
|
||||||
*/
|
|
||||||
graph.addMethod('edges', function(v) {
|
|
||||||
// Clone the array of edges and return it:
|
|
||||||
if (!arguments.length)
|
|
||||||
return this.edgesArray.slice(0);
|
|
||||||
|
|
||||||
// Return the related edge:
|
|
||||||
if (arguments.length === 1 &&
|
|
||||||
(typeof v === 'string' || typeof v === 'number'))
|
|
||||||
return this.edgesIndex[v];
|
|
||||||
|
|
||||||
// Return an array of the related edge:
|
|
||||||
if (
|
|
||||||
arguments.length === 1 &&
|
|
||||||
Object.prototype.toString.call(v) === '[object Array]'
|
|
||||||
) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (i = 0, l = v.length; i < l; i++)
|
|
||||||
if (typeof v[i] === 'string' || typeof v[i] === 'number')
|
|
||||||
a.push(this.edgesIndex[v[i]]);
|
|
||||||
else
|
|
||||||
throw 'edges: Wrong arguments.';
|
|
||||||
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw 'edges: Wrong arguments.';
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EXPORT:
|
|
||||||
* *******
|
|
||||||
*/
|
|
||||||
if (typeof sigma !== 'undefined') {
|
|
||||||
sigma.classes = sigma.classes || Object.create(null);
|
|
||||||
sigma.classes.graph = graph;
|
|
||||||
} else if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports)
|
|
||||||
exports = module.exports = graph;
|
|
||||||
exports.graph = graph;
|
|
||||||
} else
|
|
||||||
this.graph = graph;
|
|
||||||
}).call(this);
|
|
@ -1,674 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sigma Quadtree Module
|
|
||||||
* =====================
|
|
||||||
*
|
|
||||||
* Author: Guillaume Plique (Yomguithereal)
|
|
||||||
* Version: 0.2
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quad Geometric Operations
|
|
||||||
* -------------------------
|
|
||||||
*
|
|
||||||
* A useful batch of geometric operations used by the quadtree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _geom = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a graph node with x, y and size into an
|
|
||||||
* axis-aligned square.
|
|
||||||
*
|
|
||||||
* @param {object} A graph node with at least a point (x, y) and a size.
|
|
||||||
* @return {object} A square: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
pointToSquare: function(n) {
|
|
||||||
return {
|
|
||||||
x1: n.x - n.size,
|
|
||||||
y1: n.y - n.size,
|
|
||||||
x2: n.x + n.size,
|
|
||||||
y2: n.y - n.size,
|
|
||||||
height: n.size * 2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a rectangle is axis-aligned.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points
|
|
||||||
* (x1, y1) and (x2, y2).
|
|
||||||
* @return {boolean} True if the rectangle is axis-aligned.
|
|
||||||
*/
|
|
||||||
isAxisAligned: function(r) {
|
|
||||||
return r.x1 === r.x2 || r.y1 === r.y2;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute top points of an axis-aligned rectangle. This is useful in
|
|
||||||
* cases when the rectangle has been rotated (left, right or bottom up) and
|
|
||||||
* later operations need to know the top points.
|
|
||||||
*
|
|
||||||
* @param {object} An axis-aligned rectangle defined by two points
|
|
||||||
* (x1, y1), (x2, y2) and height.
|
|
||||||
* @return {object} A rectangle: two points (x1, y1), (x2, y2) and height.
|
|
||||||
*/
|
|
||||||
axisAlignedTopPoints: function(r) {
|
|
||||||
|
|
||||||
// Basic
|
|
||||||
if (r.y1 === r.y2 && r.x1 < r.x2)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
// Rotated to right
|
|
||||||
if (r.x1 === r.x2 && r.y2 > r.y1)
|
|
||||||
return {
|
|
||||||
x1: r.x1 - r.height, y1: r.y1,
|
|
||||||
x2: r.x1, y2: r.y1,
|
|
||||||
height: r.height
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rotated to left
|
|
||||||
if (r.x1 === r.x2 && r.y2 < r.y1)
|
|
||||||
return {
|
|
||||||
x1: r.x1, y1: r.y2,
|
|
||||||
x2: r.x2 + r.height, y2: r.y2,
|
|
||||||
height: r.height
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bottom's up
|
|
||||||
return {
|
|
||||||
x1: r.x2, y1: r.y1 - r.height,
|
|
||||||
x2: r.x1, y2: r.y1 - r.height,
|
|
||||||
height: r.height
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get coordinates of a rectangle's lower left corner from its top points.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
|
|
||||||
* @return {object} Coordinates of the corner (x, y).
|
|
||||||
*/
|
|
||||||
lowerLeftCoor: function(r) {
|
|
||||||
var width = (
|
|
||||||
Math.sqrt(
|
|
||||||
Math.pow(r.x2 - r.x1, 2) +
|
|
||||||
Math.pow(r.y2 - r.y1, 2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: r.x1 - (r.y2 - r.y1) * r.height / width,
|
|
||||||
y: r.y1 + (r.x2 - r.x1) * r.height / width
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get coordinates of a rectangle's lower right corner from its top points
|
|
||||||
* and its lower left corner.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
|
|
||||||
* @param {object} A corner's coordinates (x, y).
|
|
||||||
* @return {object} Coordinates of the corner (x, y).
|
|
||||||
*/
|
|
||||||
lowerRightCoor: function(r, llc) {
|
|
||||||
return {
|
|
||||||
x: llc.x - r.x1 + r.x2,
|
|
||||||
y: llc.y - r.y1 + r.y2
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the coordinates of all the corners of a rectangle from its top point.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two points (x1, y1) and (x2, y2).
|
|
||||||
* @return {array} An array of the four corners' coordinates (x, y).
|
|
||||||
*/
|
|
||||||
rectangleCorners: function(r) {
|
|
||||||
var llc = this.lowerLeftCoor(r),
|
|
||||||
lrc = this.lowerRightCoor(r, llc);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{x: r.x1, y: r.y1},
|
|
||||||
{x: r.x2, y: r.y2},
|
|
||||||
{x: llc.x, y: llc.y},
|
|
||||||
{x: lrc.x, y: lrc.y}
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a square defined by its boundaries into four.
|
|
||||||
*
|
|
||||||
* @param {object} Boundaries of the square (x, y, width, height).
|
|
||||||
* @return {array} An array containing the four new squares, themselves
|
|
||||||
* defined by an array of their four corners (x, y).
|
|
||||||
*/
|
|
||||||
splitSquare: function(b) {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
{x: b.x, y: b.y},
|
|
||||||
{x: b.x + b.width / 2, y: b.y},
|
|
||||||
{x: b.x, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{x: b.x + b.width / 2, y: b.y},
|
|
||||||
{x: b.x + b.width, y: b.y},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width, y: b.y + b.height / 2}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{x: b.x, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2},
|
|
||||||
{x: b.x, y: b.y + b.height},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width, y: b.y + b.height / 2},
|
|
||||||
{x: b.x + b.width / 2, y: b.y + b.height},
|
|
||||||
{x: b.x + b.width, y: b.y + b.height}
|
|
||||||
]
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the four axis between corners of rectangle A and corners of
|
|
||||||
* rectangle B. This is needed later to check an eventual collision.
|
|
||||||
*
|
|
||||||
* @param {array} An array of rectangle A's four corners (x, y).
|
|
||||||
* @param {array} An array of rectangle B's four corners (x, y).
|
|
||||||
* @return {array} An array of four axis defined by their coordinates (x,y).
|
|
||||||
*/
|
|
||||||
axis: function(c1, c2) {
|
|
||||||
return [
|
|
||||||
{x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y},
|
|
||||||
{x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y},
|
|
||||||
{x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y},
|
|
||||||
{x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y}
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project a rectangle's corner on an axis.
|
|
||||||
*
|
|
||||||
* @param {object} Coordinates of a corner (x, y).
|
|
||||||
* @param {object} Coordinates of an axis (x, y).
|
|
||||||
* @return {object} The projection defined by coordinates (x, y).
|
|
||||||
*/
|
|
||||||
projection: function(c, a) {
|
|
||||||
var l = (
|
|
||||||
(c.x * a.x + c.y * a.y) /
|
|
||||||
(Math.pow(a.x, 2) + Math.pow(a.y, 2))
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: l * a.x,
|
|
||||||
y: l * a.y
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether two rectangles collide on one particular axis.
|
|
||||||
*
|
|
||||||
* @param {object} An axis' coordinates (x, y).
|
|
||||||
* @param {array} Rectangle A's corners.
|
|
||||||
* @param {array} Rectangle B's corners.
|
|
||||||
* @return {boolean} True if the rectangles collide on the axis.
|
|
||||||
*/
|
|
||||||
axisCollision: function(a, c1, c2) {
|
|
||||||
var sc1 = [],
|
|
||||||
sc2 = [];
|
|
||||||
|
|
||||||
for (var ci = 0; ci < 4; ci++) {
|
|
||||||
var p1 = this.projection(c1[ci], a),
|
|
||||||
p2 = this.projection(c2[ci], a);
|
|
||||||
|
|
||||||
sc1.push(p1.x * a.x + p1.y * a.y);
|
|
||||||
sc2.push(p2.x * a.x + p2.y * a.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxc1 = Math.max.apply(Math, sc1),
|
|
||||||
maxc2 = Math.max.apply(Math, sc2),
|
|
||||||
minc1 = Math.min.apply(Math, sc1),
|
|
||||||
minc2 = Math.min.apply(Math, sc2);
|
|
||||||
|
|
||||||
return (minc2 <= maxc1 && maxc2 >= minc1);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether two rectangles collide on each one of their four axis. If
|
|
||||||
* all axis collide, then the two rectangles do collide on the plane.
|
|
||||||
*
|
|
||||||
* @param {array} Rectangle A's corners.
|
|
||||||
* @param {array} Rectangle B's corners.
|
|
||||||
* @return {boolean} True if the rectangles collide.
|
|
||||||
*/
|
|
||||||
collision: function(c1, c2) {
|
|
||||||
var axis = this.axis(c1, c2),
|
|
||||||
col = true;
|
|
||||||
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
col = col && this.axisCollision(axis[i], c1, c2);
|
|
||||||
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quad Functions
|
|
||||||
* ------------
|
|
||||||
*
|
|
||||||
* The Quadtree functions themselves.
|
|
||||||
* For each of those functions, we consider that in a splitted quad, the
|
|
||||||
* index of each node is the following:
|
|
||||||
* 0: top left
|
|
||||||
* 1: top right
|
|
||||||
* 2: bottom left
|
|
||||||
* 3: bottom right
|
|
||||||
*
|
|
||||||
* Moreover, the hereafter quad's philosophy is to consider that if an element
|
|
||||||
* collides with more than one nodes, this element belongs to each of the
|
|
||||||
* nodes it collides with where other would let it lie on a higher node.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the index of the node containing the point in the quad
|
|
||||||
*
|
|
||||||
* @param {object} point A point defined by coordinates (x, y).
|
|
||||||
* @param {object} quadBounds Boundaries of the quad (x, y, width, heigth).
|
|
||||||
* @return {integer} The index of the node containing the point.
|
|
||||||
*/
|
|
||||||
function _quadIndex(point, quadBounds) {
|
|
||||||
var xmp = quadBounds.x + quadBounds.width / 2,
|
|
||||||
ymp = quadBounds.y + quadBounds.height / 2,
|
|
||||||
top = (point.y < ymp),
|
|
||||||
left = (point.x < xmp);
|
|
||||||
|
|
||||||
if (top) {
|
|
||||||
if (left)
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (left)
|
|
||||||
return 2;
|
|
||||||
else
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of indexes of nodes containing an axis-aligned rectangle
|
|
||||||
*
|
|
||||||
* @param {object} rectangle A rectangle defined by two points (x1, y1),
|
|
||||||
* (x2, y2) and height.
|
|
||||||
* @param {array} quadCorners An array of the quad nodes' corners.
|
|
||||||
* @return {array} An array of indexes containing one to
|
|
||||||
* four integers.
|
|
||||||
*/
|
|
||||||
function _quadIndexes(rectangle, quadCorners) {
|
|
||||||
var indexes = [];
|
|
||||||
|
|
||||||
// Iterating through quads
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
if ((rectangle.x2 >= quadCorners[i][0].x) &&
|
|
||||||
(rectangle.x1 <= quadCorners[i][1].x) &&
|
|
||||||
(rectangle.y1 + rectangle.height >= quadCorners[i][0].y) &&
|
|
||||||
(rectangle.y1 <= quadCorners[i][2].y))
|
|
||||||
indexes.push(i);
|
|
||||||
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of indexes of nodes containing a non-axis-aligned rectangle
|
|
||||||
*
|
|
||||||
* @param {array} corners An array containing each corner of the
|
|
||||||
* rectangle defined by its coordinates (x, y).
|
|
||||||
* @param {array} quadCorners An array of the quad nodes' corners.
|
|
||||||
* @return {array} An array of indexes containing one to
|
|
||||||
* four integers.
|
|
||||||
*/
|
|
||||||
function _quadCollision(corners, quadCorners) {
|
|
||||||
var indexes = [];
|
|
||||||
|
|
||||||
// Iterating through quads
|
|
||||||
for (var i = 0; i < 4; i++)
|
|
||||||
if (_geom.collision(corners, quadCorners[i]))
|
|
||||||
indexes.push(i);
|
|
||||||
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subdivide a quad by creating a node at a precise index. The function does
|
|
||||||
* not generate all four nodes not to potentially create unused nodes.
|
|
||||||
*
|
|
||||||
* @param {integer} index The index of the node to create.
|
|
||||||
* @param {object} quad The quad object to subdivide.
|
|
||||||
* @return {object} A new quad representing the node created.
|
|
||||||
*/
|
|
||||||
function _quadSubdivide(index, quad) {
|
|
||||||
var next = quad.level + 1,
|
|
||||||
subw = Math.round(quad.bounds.width / 2),
|
|
||||||
subh = Math.round(quad.bounds.height / 2),
|
|
||||||
qx = Math.round(quad.bounds.x),
|
|
||||||
qy = Math.round(quad.bounds.y),
|
|
||||||
x,
|
|
||||||
y;
|
|
||||||
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
x = qx;
|
|
||||||
y = qy;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
x = qx + subw;
|
|
||||||
y = qy;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
x = qx;
|
|
||||||
y = qy + subh;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
x = qx + subw;
|
|
||||||
y = qy + subh;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _quadTree(
|
|
||||||
{x: x, y: y, width: subw, height: subh},
|
|
||||||
next,
|
|
||||||
quad.maxElements,
|
|
||||||
quad.maxLevel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively insert an element into the quadtree. Only points
|
|
||||||
* with size, i.e. axis-aligned squares, may be inserted with this
|
|
||||||
* method.
|
|
||||||
*
|
|
||||||
* @param {object} el The element to insert in the quadtree.
|
|
||||||
* @param {object} sizedPoint A sized point defined by two top points
|
|
||||||
* (x1, y1), (x2, y2) and height.
|
|
||||||
* @param {object} quad The quad in which to insert the element.
|
|
||||||
* @return {undefined} The function does not return anything.
|
|
||||||
*/
|
|
||||||
function _quadInsert(el, sizedPoint, quad) {
|
|
||||||
if (quad.level < quad.maxLevel) {
|
|
||||||
|
|
||||||
// Searching appropriate quads
|
|
||||||
var indexes = _quadIndexes(sizedPoint, quad.corners);
|
|
||||||
|
|
||||||
// Iterating
|
|
||||||
for (var i = 0, l = indexes.length; i < l; i++) {
|
|
||||||
|
|
||||||
// Subdividing if necessary
|
|
||||||
if (quad.nodes[indexes[i]] === undefined)
|
|
||||||
quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad);
|
|
||||||
|
|
||||||
// Recursion
|
|
||||||
_quadInsert(el, sizedPoint, quad.nodes[indexes[i]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Pushing the element in a leaf node
|
|
||||||
quad.elements.push(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively retrieve every elements held by the node containing the
|
|
||||||
* searched point.
|
|
||||||
*
|
|
||||||
* @param {object} point The searched point (x, y).
|
|
||||||
* @param {object} quad The searched quad.
|
|
||||||
* @return {array} An array of elements contained in the relevant
|
|
||||||
* node.
|
|
||||||
*/
|
|
||||||
function _quadRetrievePoint(point, quad) {
|
|
||||||
if (quad.level < quad.maxLevel) {
|
|
||||||
var index = _quadIndex(point, quad.bounds);
|
|
||||||
|
|
||||||
// If node does not exist we return an empty list
|
|
||||||
if (quad.nodes[index] !== undefined) {
|
|
||||||
return _quadRetrievePoint(point, quad.nodes[index]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return quad.elements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively retrieve every elements contained within an rectangular area
|
|
||||||
* that may or may not be axis-aligned.
|
|
||||||
*
|
|
||||||
* @param {object|array} rectData The searched area defined either by
|
|
||||||
* an array of four corners (x, y) in
|
|
||||||
* the case of a non-axis-aligned
|
|
||||||
* rectangle or an object with two top
|
|
||||||
* points (x1, y1), (x2, y2) and height.
|
|
||||||
* @param {object} quad The searched quad.
|
|
||||||
* @param {function} collisionFunc The collision function used to search
|
|
||||||
* for node indexes.
|
|
||||||
* @param {array?} els The retrieved elements.
|
|
||||||
* @return {array} An array of elements contained in the
|
|
||||||
* area.
|
|
||||||
*/
|
|
||||||
function _quadRetrieveArea(rectData, quad, collisionFunc, els) {
|
|
||||||
els = els || {};
|
|
||||||
|
|
||||||
if (quad.level < quad.maxLevel) {
|
|
||||||
var indexes = collisionFunc(rectData, quad.corners);
|
|
||||||
|
|
||||||
for (var i = 0, l = indexes.length; i < l; i++)
|
|
||||||
if (quad.nodes[indexes[i]] !== undefined)
|
|
||||||
_quadRetrieveArea(
|
|
||||||
rectData,
|
|
||||||
quad.nodes[indexes[i]],
|
|
||||||
collisionFunc,
|
|
||||||
els
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
for (var j = 0, m = quad.elements.length; j < m; j++)
|
|
||||||
if (els[quad.elements[j].id] === undefined)
|
|
||||||
els[quad.elements[j].id] = quad.elements[j];
|
|
||||||
|
|
||||||
return els;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the quadtree object itself.
|
|
||||||
*
|
|
||||||
* @param {object} bounds The boundaries of the quad defined by an
|
|
||||||
* origin (x, y), width and heigth.
|
|
||||||
* @param {integer} level The level of the quad in the tree.
|
|
||||||
* @param {integer} maxElements The max number of element in a leaf node.
|
|
||||||
* @param {integer} maxLevel The max recursion level of the tree.
|
|
||||||
* @return {object} The quadtree object.
|
|
||||||
*/
|
|
||||||
function _quadTree(bounds, level, maxElements, maxLevel) {
|
|
||||||
return {
|
|
||||||
level: level || 0,
|
|
||||||
bounds: bounds,
|
|
||||||
corners: _geom.splitSquare(bounds),
|
|
||||||
maxElements: maxElements || 20,
|
|
||||||
maxLevel: maxLevel || 4,
|
|
||||||
elements: [],
|
|
||||||
nodes: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sigma Quad Constructor
|
|
||||||
* ----------------------
|
|
||||||
*
|
|
||||||
* The quad API as exposed to sigma.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The quad core that will become the sigma interface with the quadtree.
|
|
||||||
*
|
|
||||||
* property {object} _tree Property holding the quadtree object.
|
|
||||||
* property {object} _geom Exposition of the _geom namespace for testing.
|
|
||||||
* property {object} _cache Cache for the area method.
|
|
||||||
*/
|
|
||||||
var quad = function() {
|
|
||||||
this._geom = _geom;
|
|
||||||
this._tree = null;
|
|
||||||
this._cache = {
|
|
||||||
query: false,
|
|
||||||
result: false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Index a graph by inserting its nodes into the quadtree.
|
|
||||||
*
|
|
||||||
* @param {array} nodes An array of nodes to index.
|
|
||||||
* @param {object} params An object of parameters with at least the quad
|
|
||||||
* bounds.
|
|
||||||
* @return {object} The quadtree object.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* ----------
|
|
||||||
* bounds: {object} boundaries of the quad defined by its origin (x, y)
|
|
||||||
* width and heigth.
|
|
||||||
* prefix: {string?} a prefix for node geometric attributes.
|
|
||||||
* maxElements: {integer?} the max number of elements in a leaf node.
|
|
||||||
* maxLevel: {integer?} the max recursion level of the tree.
|
|
||||||
*/
|
|
||||||
quad.prototype.index = function(nodes, params) {
|
|
||||||
|
|
||||||
// Enforcing presence of boundaries
|
|
||||||
if (!params.bounds)
|
|
||||||
throw 'sigma.classes.quad.index: bounds information not given.';
|
|
||||||
|
|
||||||
// Prefix
|
|
||||||
var prefix = params.prefix || '';
|
|
||||||
|
|
||||||
// Building the tree
|
|
||||||
this._tree = _quadTree(
|
|
||||||
params.bounds,
|
|
||||||
0,
|
|
||||||
params.maxElements,
|
|
||||||
params.maxLevel
|
|
||||||
);
|
|
||||||
|
|
||||||
// Inserting graph nodes into the tree
|
|
||||||
for (var i = 0, l = nodes.length; i < l; i++) {
|
|
||||||
|
|
||||||
// Inserting node
|
|
||||||
_quadInsert(
|
|
||||||
nodes[i],
|
|
||||||
_geom.pointToSquare({
|
|
||||||
x: nodes[i][prefix + 'x'],
|
|
||||||
y: nodes[i][prefix + 'y'],
|
|
||||||
size: nodes[i][prefix + 'size']
|
|
||||||
}),
|
|
||||||
this._tree
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset cache:
|
|
||||||
this._cache = {
|
|
||||||
query: false,
|
|
||||||
result: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// remove?
|
|
||||||
return this._tree;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve every graph nodes held by the quadtree node containing the
|
|
||||||
* searched point.
|
|
||||||
*
|
|
||||||
* @param {number} x of the point.
|
|
||||||
* @param {number} y of the point.
|
|
||||||
* @return {array} An array of nodes retrieved.
|
|
||||||
*/
|
|
||||||
quad.prototype.point = function(x, y) {
|
|
||||||
return this._tree ?
|
|
||||||
_quadRetrievePoint({x: x, y: y}, this._tree) || [] :
|
|
||||||
[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve every graph nodes within a rectangular area. The methods keep the
|
|
||||||
* last area queried in cache for optimization reason and will act differently
|
|
||||||
* for the same reason if the area is axis-aligned or not.
|
|
||||||
*
|
|
||||||
* @param {object} A rectangle defined by two top points (x1, y1), (x2, y2)
|
|
||||||
* and height.
|
|
||||||
* @return {array} An array of nodes retrieved.
|
|
||||||
*/
|
|
||||||
quad.prototype.area = function(rect) {
|
|
||||||
var serialized = JSON.stringify(rect),
|
|
||||||
collisionFunc,
|
|
||||||
rectData;
|
|
||||||
|
|
||||||
// Returning cache?
|
|
||||||
if (this._cache.query === serialized)
|
|
||||||
return this._cache.result;
|
|
||||||
|
|
||||||
// Axis aligned ?
|
|
||||||
if (_geom.isAxisAligned(rect)) {
|
|
||||||
collisionFunc = _quadIndexes;
|
|
||||||
rectData = _geom.axisAlignedTopPoints(rect);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
collisionFunc = _quadCollision;
|
|
||||||
rectData = _geom.rectangleCorners(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieving nodes
|
|
||||||
var nodes = this._tree ?
|
|
||||||
_quadRetrieveArea(
|
|
||||||
rectData,
|
|
||||||
this._tree,
|
|
||||||
collisionFunc
|
|
||||||
) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
// Object to array
|
|
||||||
var nodesArray = [];
|
|
||||||
for (var i in nodes)
|
|
||||||
nodesArray.push(nodes[i]);
|
|
||||||
|
|
||||||
// Caching
|
|
||||||
this._cache.query = serialized;
|
|
||||||
this._cache.result = nodesArray;
|
|
||||||
|
|
||||||
return nodesArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EXPORT:
|
|
||||||
* *******
|
|
||||||
*/
|
|
||||||
if (typeof this.sigma !== 'undefined') {
|
|
||||||
this.sigma.classes = this.sigma.classes || {};
|
|
||||||
this.sigma.classes.quad = quad;
|
|
||||||
} else if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports)
|
|
||||||
exports = module.exports = quad;
|
|
||||||
exports.quad = quad;
|
|
||||||
} else
|
|
||||||
this.quad = quad;
|
|
||||||
}).call(this);
|
|
@ -1,984 +0,0 @@
|
|||||||
/**
|
|
||||||
* conrad.js is a tiny JavaScript jobs scheduler,
|
|
||||||
*
|
|
||||||
* Version: 0.1.0
|
|
||||||
* Sources: http://github.com/jacomyal/conrad.js
|
|
||||||
* Doc: http://github.com/jacomyal/conrad.js#readme
|
|
||||||
*
|
|
||||||
* License:
|
|
||||||
* --------
|
|
||||||
* Copyright © 2013 Alexis Jacomy, Sciences-Po médialab
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* The Software is provided "as is", without warranty of any kind, express or
|
|
||||||
* implied, including but not limited to the warranties of merchantability,
|
|
||||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
|
||||||
* authors or copyright holders be liable for any claim, damages or other
|
|
||||||
* liability, whether in an action of contract, tort or otherwise, arising
|
|
||||||
* from, out of or in connection with the software or the use or other dealings
|
|
||||||
* in the Software.
|
|
||||||
*/
|
|
||||||
(function(global) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Check that conrad.js has not been loaded yet:
|
|
||||||
if (global.conrad)
|
|
||||||
throw new Error('conrad already exists');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PRIVATE VARIABLES:
|
|
||||||
* ******************
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flag indicating whether conrad is running or not.
|
|
||||||
*
|
|
||||||
* @type {Number}
|
|
||||||
*/
|
|
||||||
var _lastFrameTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flag indicating whether conrad is running or not.
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
var _isRunning = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The hash of registered jobs. Each job must at least have a unique ID
|
|
||||||
* under the key "id" and a function under the key "job". This hash
|
|
||||||
* contains each running job and each waiting job.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
var _jobs = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The hash of currently running jobs.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
var _runningJobs = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The array of currently running jobs, sorted by priority.
|
|
||||||
*
|
|
||||||
* @type {Array}
|
|
||||||
*/
|
|
||||||
var _sortedByPriorityJobs = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The array of currently waiting jobs.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
var _waitingJobs = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The array of finished jobs. They are stored in an array, since two jobs
|
|
||||||
* with the same "id" can happen at two different times.
|
|
||||||
*
|
|
||||||
* @type {Array}
|
|
||||||
*/
|
|
||||||
var _doneJobs = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dirty flag to keep conrad from starting: Indeed, when addJob() is called
|
|
||||||
* with several jobs, conrad must be started only at the end. This flag keeps
|
|
||||||
* me from duplicating the code that effectively adds a job.
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
var _noStart = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An hash containing some global settings about how conrad.js should
|
|
||||||
* behave.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
var _parameters = {
|
|
||||||
frameDuration: 20,
|
|
||||||
history: true
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This object contains every handlers bound to conrad events. It does not
|
|
||||||
* requirea any DOM implementation, since the events are all JavaScript.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
var _handlers = Object.create(null);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PRIVATE FUNCTIONS:
|
|
||||||
* ******************
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will execute the handler everytime that the indicated event (or the
|
|
||||||
* indicated events) will be triggered.
|
|
||||||
*
|
|
||||||
* @param {string|array|object} events The name of the event (or the events
|
|
||||||
* separated by spaces).
|
|
||||||
* @param {function(Object)} handler The handler to bind.
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*/
|
|
||||||
function _bind(events, handler) {
|
|
||||||
var i,
|
|
||||||
i_end,
|
|
||||||
event,
|
|
||||||
eArray;
|
|
||||||
|
|
||||||
if (!arguments.length)
|
|
||||||
return;
|
|
||||||
else if (
|
|
||||||
arguments.length === 1 &&
|
|
||||||
Object(arguments[0]) === arguments[0]
|
|
||||||
)
|
|
||||||
for (events in arguments[0])
|
|
||||||
_bind(events, arguments[0][events]);
|
|
||||||
else if (arguments.length > 1) {
|
|
||||||
eArray =
|
|
||||||
Array.isArray(events) ?
|
|
||||||
events :
|
|
||||||
events.split(/ /);
|
|
||||||
|
|
||||||
for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
|
|
||||||
event = eArray[i];
|
|
||||||
|
|
||||||
if (!_handlers[event])
|
|
||||||
_handlers[event] = [];
|
|
||||||
|
|
||||||
// Using an object instead of directly the handler will make possible
|
|
||||||
// later to add flags
|
|
||||||
_handlers[event].push({
|
|
||||||
handler: handler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the handler from a specified event (or specified events).
|
|
||||||
*
|
|
||||||
* @param {?string} events The name of the event (or the events
|
|
||||||
* separated by spaces). If undefined,
|
|
||||||
* then all handlers are removed.
|
|
||||||
* @param {?function(Object)} handler The handler to unbind. If undefined,
|
|
||||||
* each handler bound to the event or the
|
|
||||||
* events will be removed.
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*/
|
|
||||||
function _unbind(events, handler) {
|
|
||||||
var i,
|
|
||||||
i_end,
|
|
||||||
j,
|
|
||||||
j_end,
|
|
||||||
a,
|
|
||||||
event,
|
|
||||||
eArray = Array.isArray(events) ?
|
|
||||||
events :
|
|
||||||
events.split(/ /);
|
|
||||||
|
|
||||||
if (!arguments.length)
|
|
||||||
_handlers = Object.create(null);
|
|
||||||
else if (handler) {
|
|
||||||
for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
|
|
||||||
event = eArray[i];
|
|
||||||
if (_handlers[event]) {
|
|
||||||
a = [];
|
|
||||||
for (j = 0, j_end = _handlers[event].length; j !== j_end; j += 1)
|
|
||||||
if (_handlers[event][j].handler !== handler)
|
|
||||||
a.push(_handlers[event][j]);
|
|
||||||
|
|
||||||
_handlers[event] = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_handlers[event] && _handlers[event].length === 0)
|
|
||||||
delete _handlers[event];
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
for (i = 0, i_end = eArray.length; i !== i_end; i += 1)
|
|
||||||
delete _handlers[eArray[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes each handler bound to the event.
|
|
||||||
*
|
|
||||||
* @param {string} events The name of the event (or the events separated
|
|
||||||
* by spaces).
|
|
||||||
* @param {?Object} data The content of the event (optional).
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*/
|
|
||||||
function _dispatch(events, data) {
|
|
||||||
var i,
|
|
||||||
j,
|
|
||||||
i_end,
|
|
||||||
j_end,
|
|
||||||
event,
|
|
||||||
eventName,
|
|
||||||
eArray = Array.isArray(events) ?
|
|
||||||
events :
|
|
||||||
events.split(/ /);
|
|
||||||
|
|
||||||
data = data === undefined ? {} : data;
|
|
||||||
|
|
||||||
for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
|
|
||||||
eventName = eArray[i];
|
|
||||||
|
|
||||||
if (_handlers[eventName]) {
|
|
||||||
event = {
|
|
||||||
type: eventName,
|
|
||||||
data: data || {}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (j = 0, j_end = _handlers[eventName].length; j !== j_end; j += 1)
|
|
||||||
try {
|
|
||||||
_handlers[eventName][j].handler(event);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the most prioritary job once, and deals with filling the stats
|
|
||||||
* (done, time, averageTime, currentTime, etc...).
|
|
||||||
*
|
|
||||||
* @return {?Object} Returns the job object if it has to be killed, null else.
|
|
||||||
*/
|
|
||||||
function _executeFirstJob() {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
test,
|
|
||||||
kill,
|
|
||||||
pushed = false,
|
|
||||||
time = __dateNow(),
|
|
||||||
job = _sortedByPriorityJobs.shift();
|
|
||||||
|
|
||||||
// Execute the job and look at the result:
|
|
||||||
test = job.job();
|
|
||||||
|
|
||||||
// Deal with stats:
|
|
||||||
time = __dateNow() - time;
|
|
||||||
job.done++;
|
|
||||||
job.time += time;
|
|
||||||
job.currentTime += time;
|
|
||||||
job.weightTime = job.currentTime / (job.weight || 1);
|
|
||||||
job.averageTime = job.time / job.done;
|
|
||||||
|
|
||||||
// Check if the job has to be killed:
|
|
||||||
kill = job.count ? (job.count <= job.done) : !test;
|
|
||||||
|
|
||||||
// Reset priorities:
|
|
||||||
if (!kill) {
|
|
||||||
for (i = 0, l = _sortedByPriorityJobs.length; i < l; i++)
|
|
||||||
if (_sortedByPriorityJobs[i].weightTime > job.weightTime) {
|
|
||||||
_sortedByPriorityJobs.splice(i, 0, job);
|
|
||||||
pushed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pushed)
|
|
||||||
_sortedByPriorityJobs.push(job);
|
|
||||||
}
|
|
||||||
|
|
||||||
return kill ? job : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activates a job, by adding it to the _runningJobs object and the
|
|
||||||
* _sortedByPriorityJobs array. It also initializes its currentTime value.
|
|
||||||
*
|
|
||||||
* @param {Object} job The job to activate.
|
|
||||||
*/
|
|
||||||
function _activateJob(job) {
|
|
||||||
var l = _sortedByPriorityJobs.length;
|
|
||||||
|
|
||||||
// Add the job to the running jobs:
|
|
||||||
_runningJobs[job.id] = job;
|
|
||||||
job.status = 'running';
|
|
||||||
|
|
||||||
// Add the job to the priorities:
|
|
||||||
if (l) {
|
|
||||||
job.weightTime = _sortedByPriorityJobs[l - 1].weightTime;
|
|
||||||
job.currentTime = job.weightTime * (job.weight || 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the job and dispatch:
|
|
||||||
job.startTime = __dateNow();
|
|
||||||
_dispatch('jobStarted', __clone(job));
|
|
||||||
|
|
||||||
_sortedByPriorityJobs.push(job);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main loop of conrad.js:
|
|
||||||
* . It executes job such that they all occupate the same processing time.
|
|
||||||
* . It stops jobs that do not need to be executed anymore.
|
|
||||||
* . It triggers callbacks when it is relevant.
|
|
||||||
* . It starts waiting jobs when they need to be started.
|
|
||||||
* . It injects frames to keep a constant frapes per second ratio.
|
|
||||||
* . It stops itself when there are no more jobs to execute.
|
|
||||||
*/
|
|
||||||
function _loop() {
|
|
||||||
var k,
|
|
||||||
o,
|
|
||||||
l,
|
|
||||||
job,
|
|
||||||
time,
|
|
||||||
deadJob;
|
|
||||||
|
|
||||||
// Deal with the newly added jobs (the _jobs object):
|
|
||||||
for (k in _jobs) {
|
|
||||||
job = _jobs[k];
|
|
||||||
|
|
||||||
if (job.after)
|
|
||||||
_waitingJobs[k] = job;
|
|
||||||
else
|
|
||||||
_activateJob(job);
|
|
||||||
|
|
||||||
delete _jobs[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the _isRunning flag to false if there are no running job:
|
|
||||||
_isRunning = !!_sortedByPriorityJobs.length;
|
|
||||||
|
|
||||||
// Deal with the running jobs (the _runningJobs object):
|
|
||||||
while (
|
|
||||||
_sortedByPriorityJobs.length &&
|
|
||||||
__dateNow() - _lastFrameTime < _parameters.frameDuration
|
|
||||||
) {
|
|
||||||
deadJob = _executeFirstJob();
|
|
||||||
|
|
||||||
// Deal with the case where the job has ended:
|
|
||||||
if (deadJob) {
|
|
||||||
_killJob(deadJob.id);
|
|
||||||
|
|
||||||
// Check for waiting jobs:
|
|
||||||
for (k in _waitingJobs)
|
|
||||||
if (_waitingJobs[k].after === deadJob.id) {
|
|
||||||
_activateJob(_waitingJobs[k]);
|
|
||||||
delete _waitingJobs[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if conrad still has jobs to deal with, and kill it if not:
|
|
||||||
if (_isRunning) {
|
|
||||||
// Update the _lastFrameTime:
|
|
||||||
_lastFrameTime = __dateNow();
|
|
||||||
|
|
||||||
_dispatch('enterFrame');
|
|
||||||
setTimeout(_loop, 0);
|
|
||||||
} else
|
|
||||||
_dispatch('stop');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one or more jobs, and starts the loop if no job was running before. A
|
|
||||||
* job is at least a unique string "id" and a function, and there are some
|
|
||||||
* parameters that you can specify for each job to modify the way conrad will
|
|
||||||
* execute it. If a job is added with the "id" of another job that is waiting
|
|
||||||
* or still running, an error will be thrown.
|
|
||||||
*
|
|
||||||
* When a job is added, it is referenced in the _jobs object, by its id.
|
|
||||||
* Then, if it has to be executed right now, it will be also referenced in
|
|
||||||
* the _runningJobs object. If it has to wait, then it will be added into the
|
|
||||||
* _waitingJobs object, until it can start.
|
|
||||||
*
|
|
||||||
* Keep reading this documentation to see how to call this method.
|
|
||||||
*
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*
|
|
||||||
* Adding one job:
|
|
||||||
* ***************
|
|
||||||
* Basically, a job is defined by its string id and a function (the job). It
|
|
||||||
* is also possible to add some parameters:
|
|
||||||
*
|
|
||||||
* > conrad.addJob('myJobId', myJobFunction);
|
|
||||||
* > conrad.addJob('myJobId', {
|
|
||||||
* > job: myJobFunction,
|
|
||||||
* > someParameter: someValue
|
|
||||||
* > });
|
|
||||||
* > conrad.addJob({
|
|
||||||
* > id: 'myJobId',
|
|
||||||
* > job: myJobFunction,
|
|
||||||
* > someParameter: someValue
|
|
||||||
* > });
|
|
||||||
*
|
|
||||||
* Adding several jobs:
|
|
||||||
* ********************
|
|
||||||
* When adding several jobs at the same time, it is possible to specify
|
|
||||||
* parameters for each one individually or for all:
|
|
||||||
*
|
|
||||||
* > conrad.addJob([
|
|
||||||
* > {
|
|
||||||
* > id: 'myJobId1',
|
|
||||||
* > job: myJobFunction1,
|
|
||||||
* > someParameter1: someValue1
|
|
||||||
* > },
|
|
||||||
* > {
|
|
||||||
* > id: 'myJobId2',
|
|
||||||
* > job: myJobFunction2,
|
|
||||||
* > someParameter2: someValue2
|
|
||||||
* > }
|
|
||||||
* > ], {
|
|
||||||
* > someCommonParameter: someCommonValue
|
|
||||||
* > });
|
|
||||||
* > conrad.addJob({
|
|
||||||
* > myJobId1: {,
|
|
||||||
* > job: myJobFunction1,
|
|
||||||
* > someParameter1: someValue1
|
|
||||||
* > },
|
|
||||||
* > myJobId2: {,
|
|
||||||
* > job: myJobFunction2,
|
|
||||||
* > someParameter2: someValue2
|
|
||||||
* > }
|
|
||||||
* > }, {
|
|
||||||
* > someCommonParameter: someCommonValue
|
|
||||||
* > });
|
|
||||||
* > conrad.addJob({
|
|
||||||
* > myJobId1: myJobFunction1,
|
|
||||||
* > myJobId2: myJobFunction2
|
|
||||||
* > }, {
|
|
||||||
* > someCommonParameter: someCommonValue
|
|
||||||
* > });
|
|
||||||
*
|
|
||||||
* Recognized parameters:
|
|
||||||
* **********************
|
|
||||||
* Here is the exhaustive list of every accepted parameters:
|
|
||||||
*
|
|
||||||
* {?Function} end A callback to execute when the job is ended. It is
|
|
||||||
* not executed if the job is killed instead of ended
|
|
||||||
* "naturally".
|
|
||||||
* {?Integer} count The number of time the job has to be executed.
|
|
||||||
* {?Number} weight If specified, the job will be executed as it was
|
|
||||||
* added "weight" times.
|
|
||||||
* {?String} after The id of another job (eventually not added yet).
|
|
||||||
* If specified, this job will start only when the
|
|
||||||
* specified "after" job is ended.
|
|
||||||
*/
|
|
||||||
function _addJob(v1, v2) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
o;
|
|
||||||
|
|
||||||
// Array of jobs:
|
|
||||||
if (Array.isArray(v1)) {
|
|
||||||
// Keep conrad to start until the last job is added:
|
|
||||||
_noStart = true;
|
|
||||||
|
|
||||||
for (i = 0, l = v1.length; i < l; i++)
|
|
||||||
_addJob(v1[i].id, __extend(v1[i], v2));
|
|
||||||
|
|
||||||
_noStart = false;
|
|
||||||
if (!_isRunning) {
|
|
||||||
// Update the _lastFrameTime:
|
|
||||||
_lastFrameTime = __dateNow();
|
|
||||||
|
|
||||||
_dispatch('start');
|
|
||||||
_loop();
|
|
||||||
}
|
|
||||||
} else if (typeof v1 === 'object') {
|
|
||||||
// One job (object):
|
|
||||||
if (typeof v1.id === 'string')
|
|
||||||
_addJob(v1.id, v1);
|
|
||||||
|
|
||||||
// Hash of jobs:
|
|
||||||
else {
|
|
||||||
// Keep conrad to start until the last job is added:
|
|
||||||
_noStart = true;
|
|
||||||
|
|
||||||
for (i in v1)
|
|
||||||
if (typeof v1[i] === 'function')
|
|
||||||
_addJob(i, __extend({
|
|
||||||
job: v1[i]
|
|
||||||
}, v2));
|
|
||||||
else
|
|
||||||
_addJob(i, __extend(v1[i], v2));
|
|
||||||
|
|
||||||
_noStart = false;
|
|
||||||
if (!_isRunning) {
|
|
||||||
// Update the _lastFrameTime:
|
|
||||||
_lastFrameTime = __dateNow();
|
|
||||||
|
|
||||||
_dispatch('start');
|
|
||||||
_loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// One job (string, *):
|
|
||||||
} else if (typeof v1 === 'string') {
|
|
||||||
if (_hasJob(v1))
|
|
||||||
throw new Error(
|
|
||||||
'[conrad.addJob] Job with id "' + v1 + '" already exists.'
|
|
||||||
);
|
|
||||||
|
|
||||||
// One job (string, function):
|
|
||||||
if (typeof v2 === 'function') {
|
|
||||||
o = {
|
|
||||||
id: v1,
|
|
||||||
done: 0,
|
|
||||||
time: 0,
|
|
||||||
status: 'waiting',
|
|
||||||
currentTime: 0,
|
|
||||||
averageTime: 0,
|
|
||||||
weightTime: 0,
|
|
||||||
job: v2
|
|
||||||
};
|
|
||||||
|
|
||||||
// One job (string, object):
|
|
||||||
} else if (typeof v2 === 'object') {
|
|
||||||
o = __extend(
|
|
||||||
{
|
|
||||||
id: v1,
|
|
||||||
done: 0,
|
|
||||||
time: 0,
|
|
||||||
status: 'waiting',
|
|
||||||
currentTime: 0,
|
|
||||||
averageTime: 0,
|
|
||||||
weightTime: 0
|
|
||||||
},
|
|
||||||
v2
|
|
||||||
);
|
|
||||||
|
|
||||||
// If none of those cases, throw an error:
|
|
||||||
} else
|
|
||||||
throw new Error('[conrad.addJob] Wrong arguments.');
|
|
||||||
|
|
||||||
// Effectively add the job:
|
|
||||||
_jobs[v1] = o;
|
|
||||||
_dispatch('jobAdded', __clone(o));
|
|
||||||
|
|
||||||
// Check if the loop has to be started:
|
|
||||||
if (!_isRunning && !_noStart) {
|
|
||||||
// Update the _lastFrameTime:
|
|
||||||
_lastFrameTime = __dateNow();
|
|
||||||
|
|
||||||
_dispatch('start');
|
|
||||||
_loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of those cases, throw an error:
|
|
||||||
} else
|
|
||||||
throw new Error('[conrad.addJob] Wrong arguments.');
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills one or more jobs, indicated by their ids. It is only possible to
|
|
||||||
* kill running jobs or waiting jobs. If you try to kill a job that does not
|
|
||||||
* exists or that is already killed, a warning will be thrown.
|
|
||||||
*
|
|
||||||
* @param {Array|String} v1 A string job id or an array of job ids.
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*/
|
|
||||||
function _killJob(v1) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
k,
|
|
||||||
a,
|
|
||||||
job,
|
|
||||||
found = false;
|
|
||||||
|
|
||||||
// Array of job ids:
|
|
||||||
if (Array.isArray(v1))
|
|
||||||
for (i = 0, l = v1.length; i < l; i++)
|
|
||||||
_killJob(v1[i]);
|
|
||||||
|
|
||||||
// One job's id:
|
|
||||||
else if (typeof v1 === 'string') {
|
|
||||||
a = [_runningJobs, _waitingJobs, _jobs];
|
|
||||||
|
|
||||||
// Remove the job from the hashes:
|
|
||||||
for (i = 0, l = a.length; i < l; i++)
|
|
||||||
if (v1 in a[i]) {
|
|
||||||
job = a[i][v1];
|
|
||||||
|
|
||||||
if (_parameters.history) {
|
|
||||||
job.status = 'done';
|
|
||||||
_doneJobs.push(job);
|
|
||||||
}
|
|
||||||
|
|
||||||
_dispatch('jobEnded', __clone(job));
|
|
||||||
delete a[i][v1];
|
|
||||||
|
|
||||||
if (typeof job.end === 'function')
|
|
||||||
job.end();
|
|
||||||
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the priorities array:
|
|
||||||
a = _sortedByPriorityJobs;
|
|
||||||
for (i = 0, l = a.length; i < l; i++)
|
|
||||||
if (a[i].id === v1) {
|
|
||||||
a.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
throw new Error('[conrad.killJob] Job "' + v1 + '" not found.');
|
|
||||||
|
|
||||||
// If none of those cases, throw an error:
|
|
||||||
} else
|
|
||||||
throw new Error('[conrad.killJob] Wrong arguments.');
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills every running, waiting, and just added jobs.
|
|
||||||
*
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*/
|
|
||||||
function _killAll() {
|
|
||||||
var k,
|
|
||||||
jobs = __extend(_jobs, _runningJobs, _waitingJobs);
|
|
||||||
|
|
||||||
// Take every jobs and push them into the _doneJobs object:
|
|
||||||
if (_parameters.history)
|
|
||||||
for (k in jobs) {
|
|
||||||
jobs[k].status = 'done';
|
|
||||||
_doneJobs.push(jobs[k]);
|
|
||||||
|
|
||||||
if (typeof jobs[k].end === 'function')
|
|
||||||
jobs[k].end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reinitialize the different jobs lists:
|
|
||||||
_jobs = {};
|
|
||||||
_waitingJobs = {};
|
|
||||||
_runningJobs = {};
|
|
||||||
_sortedByPriorityJobs = [];
|
|
||||||
|
|
||||||
// In case some jobs are added right after the kill:
|
|
||||||
_isRunning = false;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a job with the specified id is currently running or
|
|
||||||
* waiting, and false else.
|
|
||||||
*
|
|
||||||
* @param {String} id The id of the job.
|
|
||||||
* @return {?Object} Returns the job object if it exists.
|
|
||||||
*/
|
|
||||||
function _hasJob(id) {
|
|
||||||
var job = _jobs[id] || _runningJobs[id] || _waitingJobs[id];
|
|
||||||
return job ? __extend(job) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will set the setting specified by "v1" to the value specified
|
|
||||||
* by "v2" if both are given, and else return the current value of the
|
|
||||||
* settings "v1".
|
|
||||||
*
|
|
||||||
* @param {String} v1 The name of the property.
|
|
||||||
* @param {?*} v2 Eventually, a value to set to the specified
|
|
||||||
* property.
|
|
||||||
* @return {Object|*} Returns the specified settings value if "v2" is not
|
|
||||||
* given, and conrad else.
|
|
||||||
*/
|
|
||||||
function _settings(v1, v2) {
|
|
||||||
var o;
|
|
||||||
|
|
||||||
if (typeof a1 === 'string' && arguments.length === 1)
|
|
||||||
return _parameters[a1];
|
|
||||||
else {
|
|
||||||
o = (typeof a1 === 'object' && arguments.length === 1) ?
|
|
||||||
a1 || {} :
|
|
||||||
{};
|
|
||||||
if (typeof a1 === 'string')
|
|
||||||
o[a1] = a2;
|
|
||||||
|
|
||||||
for (var k in o)
|
|
||||||
if (o[k] !== undefined)
|
|
||||||
_parameters[k] = o[k];
|
|
||||||
else
|
|
||||||
delete _parameters[k];
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if conrad is currently running, and false else.
|
|
||||||
*
|
|
||||||
* @return {Boolean} Returns _isRunning.
|
|
||||||
*/
|
|
||||||
function _getIsRunning() {
|
|
||||||
return _isRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unreference every jobs that are stored in the _doneJobs object. It will
|
|
||||||
* not be possible anymore to get stats about these jobs, but it will release
|
|
||||||
* the memory.
|
|
||||||
*
|
|
||||||
* @return {Object} Returns conrad.
|
|
||||||
*/
|
|
||||||
function _clearHistory() {
|
|
||||||
_doneJobs = [];
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a snapshot of every data about jobs that wait to be started, are
|
|
||||||
* currently running or are done.
|
|
||||||
*
|
|
||||||
* It is possible to get only running, waiting or done jobs by giving
|
|
||||||
* "running", "waiting" or "done" as fist argument.
|
|
||||||
*
|
|
||||||
* It is also possible to get every job with a specified id by giving it as
|
|
||||||
* first argument. Also, using a RegExp instead of an id will return every
|
|
||||||
* jobs whose ids match the RegExp. And these two last use cases work as well
|
|
||||||
* by giving before "running", "waiting" or "done".
|
|
||||||
*
|
|
||||||
* @return {Array} The array of the matching jobs.
|
|
||||||
*
|
|
||||||
* Some call examples:
|
|
||||||
* *******************
|
|
||||||
* > conrad.getStats('running')
|
|
||||||
* > conrad.getStats('waiting')
|
|
||||||
* > conrad.getStats('done')
|
|
||||||
* > conrad.getStats('myJob')
|
|
||||||
* > conrad.getStats(/test/)
|
|
||||||
* > conrad.getStats('running', 'myRunningJob')
|
|
||||||
* > conrad.getStats('running', /test/)
|
|
||||||
*/
|
|
||||||
function _getStats(v1, v2) {
|
|
||||||
var a,
|
|
||||||
k,
|
|
||||||
i,
|
|
||||||
l,
|
|
||||||
stats,
|
|
||||||
pattern,
|
|
||||||
isPatternString;
|
|
||||||
|
|
||||||
if (!arguments.length) {
|
|
||||||
stats = [];
|
|
||||||
|
|
||||||
for (k in _jobs)
|
|
||||||
stats.push(_jobs[k]);
|
|
||||||
|
|
||||||
for (k in _waitingJobs)
|
|
||||||
stats.push(_waitingJobs[k]);
|
|
||||||
|
|
||||||
for (k in _runningJobs)
|
|
||||||
stats.push(_runningJobs[k]);
|
|
||||||
|
|
||||||
stats = stats.concat(_doneJobs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof v1 === 'string')
|
|
||||||
switch (v1) {
|
|
||||||
case 'waiting':
|
|
||||||
stats = __objectValues(_waitingJobs);
|
|
||||||
break;
|
|
||||||
case 'running':
|
|
||||||
stats = __objectValues(_runningJobs);
|
|
||||||
break;
|
|
||||||
case 'done':
|
|
||||||
stats = _doneJobs;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
pattern = v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v1 instanceof RegExp)
|
|
||||||
pattern = v1;
|
|
||||||
|
|
||||||
if (!pattern && (typeof v2 === 'string' || v2 instanceof RegExp))
|
|
||||||
pattern = v2;
|
|
||||||
|
|
||||||
// Filter jobs if a pattern is given:
|
|
||||||
if (pattern) {
|
|
||||||
isPatternString = typeof pattern === 'string';
|
|
||||||
|
|
||||||
if (stats instanceof Array) {
|
|
||||||
a = stats;
|
|
||||||
} else if (typeof stats === 'object') {
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (k in stats)
|
|
||||||
a = a.concat(stats[k]);
|
|
||||||
} else {
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (k in _jobs)
|
|
||||||
a.push(_jobs[k]);
|
|
||||||
|
|
||||||
for (k in _waitingJobs)
|
|
||||||
a.push(_waitingJobs[k]);
|
|
||||||
|
|
||||||
for (k in _runningJobs)
|
|
||||||
a.push(_runningJobs[k]);
|
|
||||||
|
|
||||||
a = a.concat(_doneJobs);
|
|
||||||
}
|
|
||||||
|
|
||||||
stats = [];
|
|
||||||
for (i = 0, l = a.length; i < l; i++)
|
|
||||||
if (isPatternString ? a[i].id === pattern : a[i].id.match(pattern))
|
|
||||||
stats.push(a[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return __clone(stats);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TOOLS FUNCTIONS:
|
|
||||||
* ****************
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function takes any number of objects as arguments, copies from each
|
|
||||||
* of these objects each pair key/value into a new object, and finally
|
|
||||||
* returns this object.
|
|
||||||
*
|
|
||||||
* The arguments are parsed from the last one to the first one, such that
|
|
||||||
* when two objects have keys in common, the "earliest" object wins.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ********
|
|
||||||
* > var o1 = {
|
|
||||||
* > a: 1,
|
|
||||||
* > b: 2,
|
|
||||||
* > c: '3'
|
|
||||||
* > },
|
|
||||||
* > o2 = {
|
|
||||||
* > c: '4',
|
|
||||||
* > d: [ 5 ]
|
|
||||||
* > };
|
|
||||||
* > __extend(o1, o2);
|
|
||||||
* > // Returns: {
|
|
||||||
* > // a: 1,
|
|
||||||
* > // b: 2,
|
|
||||||
* > // c: '3',
|
|
||||||
* > // d: [ 5 ]
|
|
||||||
* > // };
|
|
||||||
*
|
|
||||||
* @param {Object+} Any number of objects.
|
|
||||||
* @return {Object} The merged object.
|
|
||||||
*/
|
|
||||||
function __extend() {
|
|
||||||
var i,
|
|
||||||
k,
|
|
||||||
res = {},
|
|
||||||
l = arguments.length;
|
|
||||||
|
|
||||||
for (i = l - 1; i >= 0; i--)
|
|
||||||
for (k in arguments[i])
|
|
||||||
res[k] = arguments[i][k];
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function simply clones an object. This object must contain only
|
|
||||||
* objects, arrays and immutable values. Since it is not public, it does not
|
|
||||||
* deal with cyclic references, DOM elements and instantiated objects - so
|
|
||||||
* use it carefully.
|
|
||||||
*
|
|
||||||
* @param {Object} The object to clone.
|
|
||||||
* @return {Object} The clone.
|
|
||||||
*/
|
|
||||||
function __clone(item) {
|
|
||||||
var result, i, k, l;
|
|
||||||
|
|
||||||
if (!item)
|
|
||||||
return item;
|
|
||||||
|
|
||||||
if (Array.isArray(item)) {
|
|
||||||
result = [];
|
|
||||||
for (i = 0, l = item.length; i < l; i++)
|
|
||||||
result.push(__clone(item[i]));
|
|
||||||
} else if (typeof item === 'object') {
|
|
||||||
result = {};
|
|
||||||
for (i in item)
|
|
||||||
result[i] = __clone(item[i]);
|
|
||||||
} else
|
|
||||||
result = item;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array containing the values of an object.
|
|
||||||
*
|
|
||||||
* @param {Object} The object.
|
|
||||||
* @return {Array} The array of values.
|
|
||||||
*/
|
|
||||||
function __objectValues(o) {
|
|
||||||
var k,
|
|
||||||
a = [];
|
|
||||||
|
|
||||||
for (k in o)
|
|
||||||
a.push(o[k]);
|
|
||||||
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A short "Date.now()" polyfill.
|
|
||||||
*
|
|
||||||
* @return {Number} The current time (in ms).
|
|
||||||
*/
|
|
||||||
function __dateNow() {
|
|
||||||
return Date.now ? Date.now() : new Date().getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polyfill for the Array.isArray function:
|
|
||||||
*/
|
|
||||||
if (!Array.isArray)
|
|
||||||
Array.isArray = function(v) {
|
|
||||||
return Object.prototype.toString.call(v) === '[object Array]';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EXPORT PUBLIC API:
|
|
||||||
* ******************
|
|
||||||
*/
|
|
||||||
var conrad = {
|
|
||||||
hasJob: _hasJob,
|
|
||||||
addJob: _addJob,
|
|
||||||
killJob: _killJob,
|
|
||||||
killAll: _killAll,
|
|
||||||
settings: _settings,
|
|
||||||
getStats: _getStats,
|
|
||||||
isRunning: _getIsRunning,
|
|
||||||
clearHistory: _clearHistory,
|
|
||||||
|
|
||||||
// Events management:
|
|
||||||
bind: _bind,
|
|
||||||
unbind: _unbind,
|
|
||||||
|
|
||||||
// Version:
|
|
||||||
version: '0.1.0'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports)
|
|
||||||
exports = module.exports = conrad;
|
|
||||||
exports.conrad = conrad;
|
|
||||||
}
|
|
||||||
global.conrad = conrad;
|
|
||||||
})(this);
|
|
@ -1,35 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.middlewares');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This middleware will just copy the graphic properties.
|
|
||||||
*
|
|
||||||
* @param {?string} readPrefix The read prefix.
|
|
||||||
* @param {?string} writePrefix The write prefix.
|
|
||||||
*/
|
|
||||||
sigma.middlewares.copy = function(readPrefix, writePrefix) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
a;
|
|
||||||
|
|
||||||
if (writePrefix + '' === readPrefix + '')
|
|
||||||
return;
|
|
||||||
|
|
||||||
a = this.graph.nodes();
|
|
||||||
for (i = 0, l = a.length; i < l; i++) {
|
|
||||||
a[i][writePrefix + 'x'] = a[i][readPrefix + 'x'];
|
|
||||||
a[i][writePrefix + 'y'] = a[i][readPrefix + 'y'];
|
|
||||||
a[i][writePrefix + 'size'] = a[i][readPrefix + 'size'];
|
|
||||||
}
|
|
||||||
|
|
||||||
a = this.graph.edges();
|
|
||||||
for (i = 0, l = a.length; i < l; i++)
|
|
||||||
a[i][writePrefix + 'size'] = a[i][readPrefix + 'size'];
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,189 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.middlewares');
|
|
||||||
sigma.utils.pkg('sigma.utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This middleware will rescale the graph such that it takes an optimal space
|
|
||||||
* on the renderer.
|
|
||||||
*
|
|
||||||
* As each middleware, this function is executed in the scope of the sigma
|
|
||||||
* instance.
|
|
||||||
*
|
|
||||||
* @param {?string} readPrefix The read prefix.
|
|
||||||
* @param {?string} writePrefix The write prefix.
|
|
||||||
* @param {object} options The parameters.
|
|
||||||
*/
|
|
||||||
sigma.middlewares.rescale = function(readPrefix, writePrefix, options) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c,
|
|
||||||
d,
|
|
||||||
scale,
|
|
||||||
margin,
|
|
||||||
n = this.graph.nodes(),
|
|
||||||
e = this.graph.edges(),
|
|
||||||
settings = this.settings.embedObjects(options || {}),
|
|
||||||
bounds = settings('bounds') || sigma.utils.getBoundaries(
|
|
||||||
this.graph,
|
|
||||||
readPrefix,
|
|
||||||
true
|
|
||||||
),
|
|
||||||
minX = bounds.minX,
|
|
||||||
minY = bounds.minY,
|
|
||||||
maxX = bounds.maxX,
|
|
||||||
maxY = bounds.maxY,
|
|
||||||
sizeMax = bounds.sizeMax,
|
|
||||||
weightMax = bounds.weightMax,
|
|
||||||
w = settings('width') || 1,
|
|
||||||
h = settings('height') || 1,
|
|
||||||
rescaleSettings = settings('autoRescale'),
|
|
||||||
validSettings = {
|
|
||||||
nodePosition: 1,
|
|
||||||
nodeSize: 1,
|
|
||||||
edgeSize: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* What elements should we rescale?
|
|
||||||
*/
|
|
||||||
if (!(rescaleSettings instanceof Array))
|
|
||||||
rescaleSettings = ['nodePosition', 'nodeSize', 'edgeSize'];
|
|
||||||
|
|
||||||
for (i = 0, l = rescaleSettings.length; i < l; i++)
|
|
||||||
if (!validSettings[rescaleSettings[i]])
|
|
||||||
throw new Error(
|
|
||||||
'The rescale setting "' + rescaleSettings[i] + '" is not recognized.'
|
|
||||||
);
|
|
||||||
|
|
||||||
var np = ~rescaleSettings.indexOf('nodePosition'),
|
|
||||||
ns = ~rescaleSettings.indexOf('nodeSize'),
|
|
||||||
es = ~rescaleSettings.indexOf('edgeSize');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First, we compute the scaling ratio, without considering the sizes
|
|
||||||
* of the nodes : Each node will have its center in the canvas, but might
|
|
||||||
* be partially out of it.
|
|
||||||
*/
|
|
||||||
scale = settings('scalingMode') === 'outside' ?
|
|
||||||
Math.max(
|
|
||||||
w / Math.max(maxX - minX, 1),
|
|
||||||
h / Math.max(maxY - minY, 1)
|
|
||||||
) :
|
|
||||||
Math.min(
|
|
||||||
w / Math.max(maxX - minX, 1),
|
|
||||||
h / Math.max(maxY - minY, 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Then, we correct that scaling ratio considering a margin, which is
|
|
||||||
* basically the size of the biggest node.
|
|
||||||
* This has to be done as a correction since to compare the size of the
|
|
||||||
* biggest node to the X and Y values, we have to first get an
|
|
||||||
* approximation of the scaling ratio.
|
|
||||||
**/
|
|
||||||
margin =
|
|
||||||
(
|
|
||||||
settings('rescaleIgnoreSize') ?
|
|
||||||
0 :
|
|
||||||
(settings('maxNodeSize') || sizeMax) / scale
|
|
||||||
) +
|
|
||||||
(settings('sideMargin') || 0);
|
|
||||||
maxX += margin;
|
|
||||||
minX -= margin;
|
|
||||||
maxY += margin;
|
|
||||||
minY -= margin;
|
|
||||||
|
|
||||||
// Fix the scaling with the new extrema:
|
|
||||||
scale = settings('scalingMode') === 'outside' ?
|
|
||||||
Math.max(
|
|
||||||
w / Math.max(maxX - minX, 1),
|
|
||||||
h / Math.max(maxY - minY, 1)
|
|
||||||
) :
|
|
||||||
Math.min(
|
|
||||||
w / Math.max(maxX - minX, 1),
|
|
||||||
h / Math.max(maxY - minY, 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Size homothetic parameters:
|
|
||||||
if (!settings('maxNodeSize') && !settings('minNodeSize')) {
|
|
||||||
a = 1;
|
|
||||||
b = 0;
|
|
||||||
} else if (settings('maxNodeSize') === settings('minNodeSize')) {
|
|
||||||
a = 0;
|
|
||||||
b = +settings('maxNodeSize');
|
|
||||||
} else {
|
|
||||||
a = (settings('maxNodeSize') - settings('minNodeSize')) / sizeMax;
|
|
||||||
b = +settings('minNodeSize');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!settings('maxEdgeSize') && !settings('minEdgeSize')) {
|
|
||||||
c = 1;
|
|
||||||
d = 0;
|
|
||||||
} else if (settings('maxEdgeSize') === settings('minEdgeSize')) {
|
|
||||||
c = 0;
|
|
||||||
d = +settings('minEdgeSize');
|
|
||||||
} else {
|
|
||||||
c = (settings('maxEdgeSize') - settings('minEdgeSize')) / weightMax;
|
|
||||||
d = +settings('minEdgeSize');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rescale the nodes and edges:
|
|
||||||
for (i = 0, l = e.length; i < l; i++)
|
|
||||||
e[i][writePrefix + 'size'] =
|
|
||||||
e[i][readPrefix + 'size'] * (es ? c : 1) + (es ? d : 0);
|
|
||||||
|
|
||||||
for (i = 0, l = n.length; i < l; i++) {
|
|
||||||
n[i][writePrefix + 'size'] =
|
|
||||||
n[i][readPrefix + 'size'] * (ns ? a : 1) + (ns ? b : 0);
|
|
||||||
n[i][writePrefix + 'x'] =
|
|
||||||
(n[i][readPrefix + 'x'] - (maxX + minX) / 2) * (np ? scale : 1);
|
|
||||||
n[i][writePrefix + 'y'] =
|
|
||||||
(n[i][readPrefix + 'y'] - (maxY + minY) / 2) * (np ? scale : 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sigma.utils.getBoundaries = function(graph, prefix, doEdges) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
e = graph.edges(),
|
|
||||||
n = graph.nodes(),
|
|
||||||
weightMax = -Infinity,
|
|
||||||
sizeMax = -Infinity,
|
|
||||||
minX = Infinity,
|
|
||||||
minY = Infinity,
|
|
||||||
maxX = -Infinity,
|
|
||||||
maxY = -Infinity;
|
|
||||||
|
|
||||||
if (doEdges)
|
|
||||||
for (i = 0, l = e.length; i < l; i++)
|
|
||||||
weightMax = Math.max(e[i][prefix + 'size'], weightMax);
|
|
||||||
|
|
||||||
for (i = 0, l = n.length; i < l; i++) {
|
|
||||||
sizeMax = Math.max(n[i][prefix + 'size'], sizeMax);
|
|
||||||
maxX = Math.max(n[i][prefix + 'x'], maxX);
|
|
||||||
minX = Math.min(n[i][prefix + 'x'], minX);
|
|
||||||
maxY = Math.max(n[i][prefix + 'y'], maxY);
|
|
||||||
minY = Math.min(n[i][prefix + 'y'], minY);
|
|
||||||
}
|
|
||||||
|
|
||||||
weightMax = weightMax || 1;
|
|
||||||
sizeMax = sizeMax || 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
weightMax: weightMax,
|
|
||||||
sizeMax: sizeMax,
|
|
||||||
minX: minX,
|
|
||||||
minY: minY,
|
|
||||||
maxX: maxX,
|
|
||||||
maxY: maxY
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,239 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.misc.animation.running');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a unique ID for the animation.
|
|
||||||
*
|
|
||||||
* @return {string} Returns the new ID.
|
|
||||||
*/
|
|
||||||
var _getID = (function() {
|
|
||||||
var id = 0;
|
|
||||||
return function() {
|
|
||||||
return '' + (++id);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function animates a camera. It has to be called with the camera to
|
|
||||||
* animate, the values of the coordinates to reach and eventually some
|
|
||||||
* options. It returns a number id, that you can use to kill the animation,
|
|
||||||
* with the method sigma.misc.animation.kill(id).
|
|
||||||
*
|
|
||||||
* The available options are:
|
|
||||||
*
|
|
||||||
* {?number} duration The duration of the animation.
|
|
||||||
* {?function} onNewFrame A callback to execute when the animation
|
|
||||||
* enter a new frame.
|
|
||||||
* {?function} onComplete A callback to execute when the animation
|
|
||||||
* is completed or killed.
|
|
||||||
* {?(string|function)} easing The name of a function from the package
|
|
||||||
* sigma.utils.easings, or a custom easing
|
|
||||||
* function.
|
|
||||||
*
|
|
||||||
* @param {camera} camera The camera to animate.
|
|
||||||
* @param {object} target The coordinates to reach.
|
|
||||||
* @param {?object} options Eventually an object to specify some options to
|
|
||||||
* the function. The available options are
|
|
||||||
* presented in the description of the function.
|
|
||||||
* @return {number} The animation id, to make it easy to kill
|
|
||||||
* through the method "sigma.misc.animation.kill".
|
|
||||||
*/
|
|
||||||
sigma.misc.animation.camera = function(camera, val, options) {
|
|
||||||
if (
|
|
||||||
!(camera instanceof sigma.classes.camera) ||
|
|
||||||
typeof val !== 'object' ||
|
|
||||||
!val
|
|
||||||
)
|
|
||||||
throw 'animation.camera: Wrong arguments.';
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof val.x !== 'number' &&
|
|
||||||
typeof val.y !== 'number' &&
|
|
||||||
typeof val.ratio !== 'number' &&
|
|
||||||
typeof val.angle !== 'number'
|
|
||||||
)
|
|
||||||
throw 'There must be at least one valid coordinate in the given val.';
|
|
||||||
|
|
||||||
var fn,
|
|
||||||
id,
|
|
||||||
anim,
|
|
||||||
easing,
|
|
||||||
duration,
|
|
||||||
initialVal,
|
|
||||||
o = options || {},
|
|
||||||
start = sigma.utils.dateNow();
|
|
||||||
|
|
||||||
// Store initial values:
|
|
||||||
initialVal = {
|
|
||||||
x: camera.x,
|
|
||||||
y: camera.y,
|
|
||||||
ratio: camera.ratio,
|
|
||||||
angle: camera.angle
|
|
||||||
};
|
|
||||||
|
|
||||||
duration = o.duration;
|
|
||||||
easing = typeof o.easing !== 'function' ?
|
|
||||||
sigma.utils.easings[o.easing || 'quadraticInOut'] :
|
|
||||||
o.easing;
|
|
||||||
|
|
||||||
fn = function() {
|
|
||||||
var coef,
|
|
||||||
t = o.duration ? (sigma.utils.dateNow() - start) / o.duration : 1;
|
|
||||||
|
|
||||||
// If the animation is over:
|
|
||||||
if (t >= 1) {
|
|
||||||
camera.isAnimated = false;
|
|
||||||
camera.goTo({
|
|
||||||
x: val.x !== undefined ? val.x : initialVal.x,
|
|
||||||
y: val.y !== undefined ? val.y : initialVal.y,
|
|
||||||
ratio: val.ratio !== undefined ? val.ratio : initialVal.ratio,
|
|
||||||
angle: val.angle !== undefined ? val.angle : initialVal.angle
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelAnimationFrame(id);
|
|
||||||
delete sigma.misc.animation.running[id];
|
|
||||||
|
|
||||||
// Check callbacks:
|
|
||||||
if (typeof o.onComplete === 'function')
|
|
||||||
o.onComplete();
|
|
||||||
|
|
||||||
// Else, let's keep going:
|
|
||||||
} else {
|
|
||||||
coef = easing(t);
|
|
||||||
camera.isAnimated = true;
|
|
||||||
camera.goTo({
|
|
||||||
x: val.x !== undefined ?
|
|
||||||
initialVal.x + (val.x - initialVal.x) * coef :
|
|
||||||
initialVal.x,
|
|
||||||
y: val.y !== undefined ?
|
|
||||||
initialVal.y + (val.y - initialVal.y) * coef :
|
|
||||||
initialVal.y,
|
|
||||||
ratio: val.ratio !== undefined ?
|
|
||||||
initialVal.ratio + (val.ratio - initialVal.ratio) * coef :
|
|
||||||
initialVal.ratio,
|
|
||||||
angle: val.angle !== undefined ?
|
|
||||||
initialVal.angle + (val.angle - initialVal.angle) * coef :
|
|
||||||
initialVal.angle
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check callbacks:
|
|
||||||
if (typeof o.onNewFrame === 'function')
|
|
||||||
o.onNewFrame();
|
|
||||||
|
|
||||||
anim.frameId = requestAnimationFrame(fn);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
id = _getID();
|
|
||||||
anim = {
|
|
||||||
frameId: requestAnimationFrame(fn),
|
|
||||||
target: camera,
|
|
||||||
type: 'camera',
|
|
||||||
options: o,
|
|
||||||
fn: fn
|
|
||||||
};
|
|
||||||
sigma.misc.animation.running[id] = anim;
|
|
||||||
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills a running animation. It triggers the eventual onComplete callback.
|
|
||||||
*
|
|
||||||
* @param {number} id The id of the animation to kill.
|
|
||||||
* @return {object} Returns the sigma.misc.animation package.
|
|
||||||
*/
|
|
||||||
sigma.misc.animation.kill = function(id) {
|
|
||||||
if (arguments.length !== 1 || typeof id !== 'number')
|
|
||||||
throw 'animation.kill: Wrong arguments.';
|
|
||||||
|
|
||||||
var o = sigma.misc.animation.running[id];
|
|
||||||
|
|
||||||
if (o) {
|
|
||||||
cancelAnimationFrame(id);
|
|
||||||
delete sigma.misc.animation.running[o.frameId];
|
|
||||||
|
|
||||||
if (o.type === 'camera')
|
|
||||||
o.target.isAnimated = false;
|
|
||||||
|
|
||||||
// Check callbacks:
|
|
||||||
if (typeof (o.options || {}).onComplete === 'function')
|
|
||||||
o.options.onComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills every running animations, or only the one with the specified type,
|
|
||||||
* if a string parameter is given.
|
|
||||||
*
|
|
||||||
* @param {?(string|object)} filter A string to filter the animations to kill
|
|
||||||
* on their type (example: "camera"), or an
|
|
||||||
* object to filter on their target.
|
|
||||||
* @return {number} Returns the number of animations killed
|
|
||||||
* that way.
|
|
||||||
*/
|
|
||||||
sigma.misc.animation.killAll = function(filter) {
|
|
||||||
var o,
|
|
||||||
id,
|
|
||||||
count = 0,
|
|
||||||
type = typeof filter === 'string' ? filter : null,
|
|
||||||
target = typeof filter === 'object' ? filter : null,
|
|
||||||
running = sigma.misc.animation.running;
|
|
||||||
|
|
||||||
for (id in running)
|
|
||||||
if (
|
|
||||||
(!type || running[id].type === type) &&
|
|
||||||
(!target || running[id].target === target)
|
|
||||||
) {
|
|
||||||
o = sigma.misc.animation.running[id];
|
|
||||||
cancelAnimationFrame(o.frameId);
|
|
||||||
delete sigma.misc.animation.running[id];
|
|
||||||
|
|
||||||
if (o.type === 'camera')
|
|
||||||
o.target.isAnimated = false;
|
|
||||||
|
|
||||||
// Increment counter:
|
|
||||||
count++;
|
|
||||||
|
|
||||||
// Check callbacks:
|
|
||||||
if (typeof (o.options || {}).onComplete === 'function')
|
|
||||||
o.options.onComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns "true" if any animation that is currently still running matches
|
|
||||||
* the filter given to the function.
|
|
||||||
*
|
|
||||||
* @param {string|object} filter A string to filter the animations to kill
|
|
||||||
* on their type (example: "camera"), or an
|
|
||||||
* object to filter on their target.
|
|
||||||
* @return {boolean} Returns true if any running animation
|
|
||||||
* matches.
|
|
||||||
*/
|
|
||||||
sigma.misc.animation.has = function(filter) {
|
|
||||||
var id,
|
|
||||||
type = typeof filter === 'string' ? filter : null,
|
|
||||||
target = typeof filter === 'object' ? filter : null,
|
|
||||||
running = sigma.misc.animation.running;
|
|
||||||
|
|
||||||
for (id in running)
|
|
||||||
if (
|
|
||||||
(!type || running[id].type === type) &&
|
|
||||||
(!target || running[id].target === target)
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,156 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.misc');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This helper will bind any DOM renderer (for instance svg)
|
|
||||||
* to its captors, to properly dispatch the good events to the sigma instance
|
|
||||||
* to manage clicking, hovering etc...
|
|
||||||
*
|
|
||||||
* It has to be called in the scope of the related renderer.
|
|
||||||
*/
|
|
||||||
sigma.misc.bindDOMEvents = function(container) {
|
|
||||||
var self = this,
|
|
||||||
graph = this.graph;
|
|
||||||
|
|
||||||
// DOMElement abstraction
|
|
||||||
function Element(domElement) {
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
this.attr = function(attrName) {
|
|
||||||
return domElement.getAttributeNS(null, attrName);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
this.tag = domElement.tagName;
|
|
||||||
this.class = this.attr('class');
|
|
||||||
this.id = this.attr('id');
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
this.isNode = function() {
|
|
||||||
return !!~this.class.indexOf(self.settings('classPrefix') + '-node');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isEdge = function() {
|
|
||||||
return !!~this.class.indexOf(self.settings('classPrefix') + '-edge');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isHover = function() {
|
|
||||||
return !!~this.class.indexOf(self.settings('classPrefix') + '-hover');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Click
|
|
||||||
function click(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Generic event
|
|
||||||
self.dispatchEvent('click', e);
|
|
||||||
|
|
||||||
// Are we on a node?
|
|
||||||
var element = new Element(e.target);
|
|
||||||
|
|
||||||
if (element.isNode())
|
|
||||||
self.dispatchEvent('clickNode', {
|
|
||||||
node: graph.nodes(element.attr('data-node-id'))
|
|
||||||
});
|
|
||||||
else
|
|
||||||
self.dispatchEvent('clickStage');
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double click
|
|
||||||
function doubleClick(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Generic event
|
|
||||||
self.dispatchEvent('doubleClick', e);
|
|
||||||
|
|
||||||
// Are we on a node?
|
|
||||||
var element = new Element(e.target);
|
|
||||||
|
|
||||||
if (element.isNode())
|
|
||||||
self.dispatchEvent('doubleClickNode', {
|
|
||||||
node: graph.nodes(element.attr('data-node-id'))
|
|
||||||
});
|
|
||||||
else
|
|
||||||
self.dispatchEvent('doubleClickStage');
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// On over
|
|
||||||
function onOver(e) {
|
|
||||||
var target = e.toElement || e.target;
|
|
||||||
|
|
||||||
if (!self.settings('eventsEnabled') || !target)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var el = new Element(target);
|
|
||||||
|
|
||||||
if (el.isNode()) {
|
|
||||||
self.dispatchEvent('overNode', {
|
|
||||||
node: graph.nodes(el.attr('data-node-id'))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (el.isEdge()) {
|
|
||||||
var edge = graph.edges(el.attr('data-edge-id'));
|
|
||||||
self.dispatchEvent('overEdge', {
|
|
||||||
edge: edge,
|
|
||||||
source: graph.nodes(edge.source),
|
|
||||||
target: graph.nodes(edge.target)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On out
|
|
||||||
function onOut(e) {
|
|
||||||
var target = e.fromElement || e.originalTarget;
|
|
||||||
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var el = new Element(target);
|
|
||||||
|
|
||||||
if (el.isNode()) {
|
|
||||||
self.dispatchEvent('outNode', {
|
|
||||||
node: graph.nodes(el.attr('data-node-id'))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (el.isEdge()) {
|
|
||||||
var edge = graph.edges(el.attr('data-edge-id'));
|
|
||||||
self.dispatchEvent('outEdge', {
|
|
||||||
edge: edge,
|
|
||||||
source: graph.nodes(edge.source),
|
|
||||||
target: graph.nodes(edge.target)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registering Events:
|
|
||||||
|
|
||||||
// Click
|
|
||||||
container.addEventListener('click', click, false);
|
|
||||||
sigma.utils.doubleClick(container, 'click', doubleClick);
|
|
||||||
|
|
||||||
// Touch counterparts
|
|
||||||
container.addEventListener('touchstart', click, false);
|
|
||||||
sigma.utils.doubleClick(container, 'touchstart', doubleClick);
|
|
||||||
|
|
||||||
// Mouseover
|
|
||||||
container.addEventListener('mouseover', onOver, true);
|
|
||||||
|
|
||||||
// Mouseout
|
|
||||||
container.addEventListener('mouseout', onOut, true);
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,509 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.misc');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This helper will bind any no-DOM renderer (for instance canvas or WebGL)
|
|
||||||
* to its captors, to properly dispatch the good events to the sigma instance
|
|
||||||
* to manage clicking, hovering etc...
|
|
||||||
*
|
|
||||||
* It has to be called in the scope of the related renderer.
|
|
||||||
*/
|
|
||||||
sigma.misc.bindEvents = function(prefix) {
|
|
||||||
var i,
|
|
||||||
l,
|
|
||||||
mX,
|
|
||||||
mY,
|
|
||||||
captor,
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
function getNodes(e) {
|
|
||||||
if (e) {
|
|
||||||
mX = 'x' in e.data ? e.data.x : mX;
|
|
||||||
mY = 'y' in e.data ? e.data.y : mY;
|
|
||||||
}
|
|
||||||
|
|
||||||
var i,
|
|
||||||
j,
|
|
||||||
l,
|
|
||||||
n,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
s,
|
|
||||||
inserted,
|
|
||||||
selected = [],
|
|
||||||
modifiedX = mX + self.width / 2,
|
|
||||||
modifiedY = mY + self.height / 2,
|
|
||||||
point = self.camera.cameraPosition(
|
|
||||||
mX,
|
|
||||||
mY
|
|
||||||
),
|
|
||||||
nodes = self.camera.quadtree.point(
|
|
||||||
point.x,
|
|
||||||
point.y
|
|
||||||
);
|
|
||||||
|
|
||||||
if (nodes.length)
|
|
||||||
for (i = 0, l = nodes.length; i < l; i++) {
|
|
||||||
n = nodes[i];
|
|
||||||
x = n[prefix + 'x'];
|
|
||||||
y = n[prefix + 'y'];
|
|
||||||
s = n[prefix + 'size'];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!n.hidden &&
|
|
||||||
modifiedX > x - s &&
|
|
||||||
modifiedX < x + s &&
|
|
||||||
modifiedY > y - s &&
|
|
||||||
modifiedY < y + s &&
|
|
||||||
Math.sqrt(
|
|
||||||
Math.pow(modifiedX - x, 2) +
|
|
||||||
Math.pow(modifiedY - y, 2)
|
|
||||||
) < s
|
|
||||||
) {
|
|
||||||
// Insert the node:
|
|
||||||
inserted = false;
|
|
||||||
|
|
||||||
for (j = 0; j < selected.length; j++)
|
|
||||||
if (n.size > selected[j].size) {
|
|
||||||
selected.splice(j, 0, n);
|
|
||||||
inserted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inserted)
|
|
||||||
selected.push(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getEdges(e) {
|
|
||||||
if (!self.settings('enableEdgeHovering')) {
|
|
||||||
// No event if the setting is off:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var isCanvas = (
|
|
||||||
sigma.renderers.canvas && self instanceof sigma.renderers.canvas);
|
|
||||||
|
|
||||||
if (!isCanvas) {
|
|
||||||
// A quick hardcoded rule to prevent people from using this feature
|
|
||||||
// with the WebGL renderer (which is not good enough at the moment):
|
|
||||||
throw new Error(
|
|
||||||
'The edge events feature is not compatible with the WebGL renderer'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
mX = 'x' in e.data ? e.data.x : mX;
|
|
||||||
mY = 'y' in e.data ? e.data.y : mY;
|
|
||||||
}
|
|
||||||
|
|
||||||
var i,
|
|
||||||
j,
|
|
||||||
l,
|
|
||||||
a,
|
|
||||||
edge,
|
|
||||||
s,
|
|
||||||
maxEpsilon = self.settings('edgeHoverPrecision'),
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
cp,
|
|
||||||
nodeIndex = {},
|
|
||||||
inserted,
|
|
||||||
selected = [],
|
|
||||||
modifiedX = mX + self.width / 2,
|
|
||||||
modifiedY = mY + self.height / 2,
|
|
||||||
point = self.camera.cameraPosition(
|
|
||||||
mX,
|
|
||||||
mY
|
|
||||||
),
|
|
||||||
edges = [];
|
|
||||||
|
|
||||||
if (isCanvas) {
|
|
||||||
var nodesOnScreen = self.camera.quadtree.area(
|
|
||||||
self.camera.getRectangle(self.width, self.height)
|
|
||||||
);
|
|
||||||
for (a = nodesOnScreen, i = 0, l = a.length; i < l; i++)
|
|
||||||
nodeIndex[a[i].id] = a[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.camera.edgequadtree !== undefined) {
|
|
||||||
edges = self.camera.edgequadtree.point(
|
|
||||||
point.x,
|
|
||||||
point.y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertEdge(selected, edge) {
|
|
||||||
inserted = false;
|
|
||||||
|
|
||||||
for (j = 0; j < selected.length; j++)
|
|
||||||
if (edge.size > selected[j].size) {
|
|
||||||
selected.splice(j, 0, edge);
|
|
||||||
inserted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inserted)
|
|
||||||
selected.push(edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edges.length)
|
|
||||||
for (i = 0, l = edges.length; i < l; i++) {
|
|
||||||
edge = edges[i];
|
|
||||||
source = self.graph.nodes(edge.source);
|
|
||||||
target = self.graph.nodes(edge.target);
|
|
||||||
// (HACK) we can't get edge[prefix + 'size'] on WebGL renderer:
|
|
||||||
s = edge[prefix + 'size'] ||
|
|
||||||
edge['read_' + prefix + 'size'];
|
|
||||||
|
|
||||||
// First, let's identify which edges are drawn. To do this, we keep
|
|
||||||
// every edges that have at least one extremity displayed according to
|
|
||||||
// the quadtree and the "hidden" attribute. We also do not keep hidden
|
|
||||||
// edges.
|
|
||||||
// Then, let's check if the mouse is on the edge (we suppose that it
|
|
||||||
// is a line segment).
|
|
||||||
|
|
||||||
if (
|
|
||||||
!edge.hidden &&
|
|
||||||
!source.hidden && !target.hidden &&
|
|
||||||
(!isCanvas ||
|
|
||||||
(nodeIndex[edge.source] || nodeIndex[edge.target])) &&
|
|
||||||
sigma.utils.getDistance(
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y'],
|
|
||||||
modifiedX,
|
|
||||||
modifiedY) > source[prefix + 'size'] &&
|
|
||||||
sigma.utils.getDistance(
|
|
||||||
target[prefix + 'x'],
|
|
||||||
target[prefix + 'y'],
|
|
||||||
modifiedX,
|
|
||||||
modifiedY) > target[prefix + 'size']
|
|
||||||
) {
|
|
||||||
if (edge.type == 'curve' || edge.type == 'curvedArrow') {
|
|
||||||
if (source.id === target.id) {
|
|
||||||
cp = sigma.utils.getSelfLoopControlPoints(
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y'],
|
|
||||||
source[prefix + 'size']
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
sigma.utils.isPointOnBezierCurve(
|
|
||||||
modifiedX,
|
|
||||||
modifiedY,
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y'],
|
|
||||||
target[prefix + 'x'],
|
|
||||||
target[prefix + 'y'],
|
|
||||||
cp.x1,
|
|
||||||
cp.y1,
|
|
||||||
cp.x2,
|
|
||||||
cp.y2,
|
|
||||||
Math.max(s, maxEpsilon)
|
|
||||||
)) {
|
|
||||||
insertEdge(selected, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cp = sigma.utils.getQuadraticControlPoint(
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y'],
|
|
||||||
target[prefix + 'x'],
|
|
||||||
target[prefix + 'y']);
|
|
||||||
if (
|
|
||||||
sigma.utils.isPointOnQuadraticCurve(
|
|
||||||
modifiedX,
|
|
||||||
modifiedY,
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y'],
|
|
||||||
target[prefix + 'x'],
|
|
||||||
target[prefix + 'y'],
|
|
||||||
cp.x,
|
|
||||||
cp.y,
|
|
||||||
Math.max(s, maxEpsilon)
|
|
||||||
)) {
|
|
||||||
insertEdge(selected, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
sigma.utils.isPointOnSegment(
|
|
||||||
modifiedX,
|
|
||||||
modifiedY,
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y'],
|
|
||||||
target[prefix + 'x'],
|
|
||||||
target[prefix + 'y'],
|
|
||||||
Math.max(s, maxEpsilon)
|
|
||||||
)) {
|
|
||||||
insertEdge(selected, edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function bindCaptor(captor) {
|
|
||||||
var nodes,
|
|
||||||
edges,
|
|
||||||
overNodes = {},
|
|
||||||
overEdges = {};
|
|
||||||
|
|
||||||
function onClick(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
self.dispatchEvent('click', e.data);
|
|
||||||
|
|
||||||
nodes = getNodes(e);
|
|
||||||
edges = getEdges(e);
|
|
||||||
|
|
||||||
if (nodes.length) {
|
|
||||||
self.dispatchEvent('clickNode', {
|
|
||||||
node: nodes[0],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
self.dispatchEvent('clickNodes', {
|
|
||||||
node: nodes,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
} else if (edges.length) {
|
|
||||||
self.dispatchEvent('clickEdge', {
|
|
||||||
edge: edges[0],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
self.dispatchEvent('clickEdges', {
|
|
||||||
edge: edges,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
self.dispatchEvent('clickStage', {captor: e.data});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDoubleClick(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
self.dispatchEvent('doubleClick', e.data);
|
|
||||||
|
|
||||||
nodes = getNodes(e);
|
|
||||||
edges = getEdges(e);
|
|
||||||
|
|
||||||
if (nodes.length) {
|
|
||||||
self.dispatchEvent('doubleClickNode', {
|
|
||||||
node: nodes[0],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
self.dispatchEvent('doubleClickNodes', {
|
|
||||||
node: nodes,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
} else if (edges.length) {
|
|
||||||
self.dispatchEvent('doubleClickEdge', {
|
|
||||||
edge: edges[0],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
self.dispatchEvent('doubleClickEdges', {
|
|
||||||
edge: edges,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
self.dispatchEvent('doubleClickStage', {captor: e.data});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRightClick(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
self.dispatchEvent('rightClick', e.data);
|
|
||||||
|
|
||||||
nodes = getNodes(e);
|
|
||||||
edges = getEdges(e);
|
|
||||||
|
|
||||||
if (nodes.length) {
|
|
||||||
self.dispatchEvent('rightClickNode', {
|
|
||||||
node: nodes[0],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
self.dispatchEvent('rightClickNodes', {
|
|
||||||
node: nodes,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
} else if (edges.length) {
|
|
||||||
self.dispatchEvent('rightClickEdge', {
|
|
||||||
edge: edges[0],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
self.dispatchEvent('rightClickEdges', {
|
|
||||||
edge: edges,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
self.dispatchEvent('rightClickStage', {captor: e.data});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOut(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var k,
|
|
||||||
i,
|
|
||||||
l,
|
|
||||||
le,
|
|
||||||
outNodes = [],
|
|
||||||
outEdges = [];
|
|
||||||
|
|
||||||
for (k in overNodes)
|
|
||||||
outNodes.push(overNodes[k]);
|
|
||||||
|
|
||||||
overNodes = {};
|
|
||||||
// Dispatch both single and multi events:
|
|
||||||
for (i = 0, l = outNodes.length; i < l; i++)
|
|
||||||
self.dispatchEvent('outNode', {
|
|
||||||
node: outNodes[i],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
if (outNodes.length)
|
|
||||||
self.dispatchEvent('outNodes', {
|
|
||||||
nodes: outNodes,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
|
|
||||||
overEdges = {};
|
|
||||||
// Dispatch both single and multi events:
|
|
||||||
for (i = 0, le = outEdges.length; i < le; i++)
|
|
||||||
self.dispatchEvent('outEdge', {
|
|
||||||
edge: outEdges[i],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
if (outEdges.length)
|
|
||||||
self.dispatchEvent('outEdges', {
|
|
||||||
edges: outEdges,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMove(e) {
|
|
||||||
if (!self.settings('eventsEnabled'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
nodes = getNodes(e);
|
|
||||||
edges = getEdges(e);
|
|
||||||
|
|
||||||
var i,
|
|
||||||
k,
|
|
||||||
node,
|
|
||||||
edge,
|
|
||||||
newOutNodes = [],
|
|
||||||
newOverNodes = [],
|
|
||||||
currentOverNodes = {},
|
|
||||||
l = nodes.length,
|
|
||||||
newOutEdges = [],
|
|
||||||
newOverEdges = [],
|
|
||||||
currentOverEdges = {},
|
|
||||||
le = edges.length;
|
|
||||||
|
|
||||||
// Check newly overred nodes:
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
node = nodes[i];
|
|
||||||
currentOverNodes[node.id] = node;
|
|
||||||
if (!overNodes[node.id]) {
|
|
||||||
newOverNodes.push(node);
|
|
||||||
overNodes[node.id] = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check no more overred nodes:
|
|
||||||
for (k in overNodes)
|
|
||||||
if (!currentOverNodes[k]) {
|
|
||||||
newOutNodes.push(overNodes[k]);
|
|
||||||
delete overNodes[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch both single and multi events:
|
|
||||||
for (i = 0, l = newOverNodes.length; i < l; i++)
|
|
||||||
self.dispatchEvent('overNode', {
|
|
||||||
node: newOverNodes[i],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
for (i = 0, l = newOutNodes.length; i < l; i++)
|
|
||||||
self.dispatchEvent('outNode', {
|
|
||||||
node: newOutNodes[i],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
if (newOverNodes.length)
|
|
||||||
self.dispatchEvent('overNodes', {
|
|
||||||
nodes: newOverNodes,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
if (newOutNodes.length)
|
|
||||||
self.dispatchEvent('outNodes', {
|
|
||||||
nodes: newOutNodes,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check newly overred edges:
|
|
||||||
for (i = 0; i < le; i++) {
|
|
||||||
edge = edges[i];
|
|
||||||
currentOverEdges[edge.id] = edge;
|
|
||||||
if (!overEdges[edge.id]) {
|
|
||||||
newOverEdges.push(edge);
|
|
||||||
overEdges[edge.id] = edge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check no more overred edges:
|
|
||||||
for (k in overEdges)
|
|
||||||
if (!currentOverEdges[k]) {
|
|
||||||
newOutEdges.push(overEdges[k]);
|
|
||||||
delete overEdges[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch both single and multi events:
|
|
||||||
for (i = 0, le = newOverEdges.length; i < le; i++)
|
|
||||||
self.dispatchEvent('overEdge', {
|
|
||||||
edge: newOverEdges[i],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
for (i = 0, le = newOutEdges.length; i < le; i++)
|
|
||||||
self.dispatchEvent('outEdge', {
|
|
||||||
edge: newOutEdges[i],
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
if (newOverEdges.length)
|
|
||||||
self.dispatchEvent('overEdges', {
|
|
||||||
edges: newOverEdges,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
if (newOutEdges.length)
|
|
||||||
self.dispatchEvent('outEdges', {
|
|
||||||
edges: newOutEdges,
|
|
||||||
captor: e.data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind events:
|
|
||||||
captor.bind('click', onClick);
|
|
||||||
captor.bind('mousedown', onMove);
|
|
||||||
captor.bind('mouseup', onMove);
|
|
||||||
captor.bind('mousemove', onMove);
|
|
||||||
captor.bind('mouseout', onOut);
|
|
||||||
captor.bind('doubleclick', onDoubleClick);
|
|
||||||
captor.bind('rightclick', onRightClick);
|
|
||||||
self.bind('render', onMove);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0, l = this.captors.length; i < l; i++)
|
|
||||||
bindCaptor(this.captors[i]);
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,220 +0,0 @@
|
|||||||
;(function(undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (typeof sigma === 'undefined')
|
|
||||||
throw 'sigma is not declared';
|
|
||||||
|
|
||||||
// Initialize packages:
|
|
||||||
sigma.utils.pkg('sigma.misc');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method listens to "overNode", "outNode", "overEdge" and "outEdge"
|
|
||||||
* events from a renderer and renders the nodes differently on the top layer.
|
|
||||||
* The goal is to make any node label readable with the mouse, and to
|
|
||||||
* highlight hovered nodes and edges.
|
|
||||||
*
|
|
||||||
* It has to be called in the scope of the related renderer.
|
|
||||||
*/
|
|
||||||
sigma.misc.drawHovers = function(prefix) {
|
|
||||||
var self = this,
|
|
||||||
hoveredNodes = {},
|
|
||||||
hoveredEdges = {};
|
|
||||||
|
|
||||||
this.bind('overNode', function(event) {
|
|
||||||
var node = event.data.node;
|
|
||||||
if (!node.hidden) {
|
|
||||||
hoveredNodes[node.id] = node;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.bind('outNode', function(event) {
|
|
||||||
delete hoveredNodes[event.data.node.id];
|
|
||||||
draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.bind('overEdge', function(event) {
|
|
||||||
var edge = event.data.edge;
|
|
||||||
if (!edge.hidden) {
|
|
||||||
hoveredEdges[edge.id] = edge;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.bind('outEdge', function(event) {
|
|
||||||
delete hoveredEdges[event.data.edge.id];
|
|
||||||
draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.bind('render', function(event) {
|
|
||||||
draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
function draw() {
|
|
||||||
// Clear self.contexts.hover:
|
|
||||||
self.contexts.hover.canvas.width = self.contexts.hover.canvas.width;
|
|
||||||
|
|
||||||
var k,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
hoveredNode,
|
|
||||||
hoveredEdge,
|
|
||||||
defaultNodeType = self.settings('defaultNodeType'),
|
|
||||||
defaultEdgeType = self.settings('defaultEdgeType'),
|
|
||||||
nodeRenderers = sigma.canvas.hovers,
|
|
||||||
edgeRenderers = sigma.canvas.edgehovers,
|
|
||||||
extremitiesRenderers = sigma.canvas.extremities,
|
|
||||||
embedSettings = self.settings.embedObjects({
|
|
||||||
prefix: prefix
|
|
||||||
});
|
|
||||||
|
|
||||||
// Node render: single hover
|
|
||||||
if (
|
|
||||||
embedSettings('enableHovering') &&
|
|
||||||
embedSettings('singleHover') &&
|
|
||||||
Object.keys(hoveredNodes).length
|
|
||||||
) {
|
|
||||||
hoveredNode = hoveredNodes[Object.keys(hoveredNodes)[0]];
|
|
||||||
(
|
|
||||||
nodeRenderers[hoveredNode.type] ||
|
|
||||||
nodeRenderers[defaultNodeType] ||
|
|
||||||
nodeRenderers.def
|
|
||||||
)(
|
|
||||||
hoveredNode,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node render: multiple hover
|
|
||||||
if (
|
|
||||||
embedSettings('enableHovering') &&
|
|
||||||
!embedSettings('singleHover')
|
|
||||||
)
|
|
||||||
for (k in hoveredNodes)
|
|
||||||
(
|
|
||||||
nodeRenderers[hoveredNodes[k].type] ||
|
|
||||||
nodeRenderers[defaultNodeType] ||
|
|
||||||
nodeRenderers.def
|
|
||||||
)(
|
|
||||||
hoveredNodes[k],
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
|
|
||||||
// Edge render: single hover
|
|
||||||
if (
|
|
||||||
embedSettings('enableEdgeHovering') &&
|
|
||||||
embedSettings('singleHover') &&
|
|
||||||
Object.keys(hoveredEdges).length
|
|
||||||
) {
|
|
||||||
hoveredEdge = hoveredEdges[Object.keys(hoveredEdges)[0]];
|
|
||||||
source = self.graph.nodes(hoveredEdge.source);
|
|
||||||
target = self.graph.nodes(hoveredEdge.target);
|
|
||||||
|
|
||||||
if (! hoveredEdge.hidden) {
|
|
||||||
(
|
|
||||||
edgeRenderers[hoveredEdge.type] ||
|
|
||||||
edgeRenderers[defaultEdgeType] ||
|
|
||||||
edgeRenderers.def
|
|
||||||
) (
|
|
||||||
hoveredEdge,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
|
|
||||||
if (embedSettings('edgeHoverExtremities')) {
|
|
||||||
(
|
|
||||||
extremitiesRenderers[hoveredEdge.type] ||
|
|
||||||
extremitiesRenderers.def
|
|
||||||
)(
|
|
||||||
hoveredEdge,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Avoid edges rendered over nodes:
|
|
||||||
(
|
|
||||||
sigma.canvas.nodes[source.type] ||
|
|
||||||
sigma.canvas.nodes.def
|
|
||||||
) (
|
|
||||||
source,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
(
|
|
||||||
sigma.canvas.nodes[target.type] ||
|
|
||||||
sigma.canvas.nodes.def
|
|
||||||
) (
|
|
||||||
target,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edge render: multiple hover
|
|
||||||
if (
|
|
||||||
embedSettings('enableEdgeHovering') &&
|
|
||||||
!embedSettings('singleHover')
|
|
||||||
) {
|
|
||||||
for (k in hoveredEdges) {
|
|
||||||
hoveredEdge = hoveredEdges[k];
|
|
||||||
source = self.graph.nodes(hoveredEdge.source);
|
|
||||||
target = self.graph.nodes(hoveredEdge.target);
|
|
||||||
|
|
||||||
if (!hoveredEdge.hidden) {
|
|
||||||
(
|
|
||||||
edgeRenderers[hoveredEdge.type] ||
|
|
||||||
edgeRenderers[defaultEdgeType] ||
|
|
||||||
edgeRenderers.def
|
|
||||||
) (
|
|
||||||
hoveredEdge,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
|
|
||||||
if (embedSettings('edgeHoverExtremities')) {
|
|
||||||
(
|
|
||||||
extremitiesRenderers[hoveredEdge.type] ||
|
|
||||||
extremitiesRenderers.def
|
|
||||||
)(
|
|
||||||
hoveredEdge,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Avoid edges rendered over nodes:
|
|
||||||
(
|
|
||||||
sigma.canvas.nodes[source.type] ||
|
|
||||||
sigma.canvas.nodes.def
|
|
||||||
) (
|
|
||||||
source,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
(
|
|
||||||
sigma.canvas.nodes[target.type] ||
|
|
||||||
sigma.canvas.nodes.def
|
|
||||||
) (
|
|
||||||
target,
|
|
||||||
self.contexts.hover,
|
|
||||||
embedSettings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}).call(this);
|
|
@ -1,76 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edgehovers');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This hover renderer will display the edge with a different color or size.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edgehovers.arrow =
|
|
||||||
function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor'),
|
|
||||||
size = edge[prefix + 'size'] || 1,
|
|
||||||
tSize = target[prefix + 'size'],
|
|
||||||
sX = source[prefix + 'x'],
|
|
||||||
sY = source[prefix + 'y'],
|
|
||||||
tX = target[prefix + 'x'],
|
|
||||||
tY = target[prefix + 'y'];
|
|
||||||
|
|
||||||
size = (edge.hover) ?
|
|
||||||
settings('edgeHoverSizeRatio') * size : size;
|
|
||||||
var aSize = size * 2.5,
|
|
||||||
d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)),
|
|
||||||
aX = sX + (tX - sX) * (d - aSize - tSize) / d,
|
|
||||||
aY = sY + (tY - sY) * (d - aSize - tSize) / d,
|
|
||||||
vX = (tX - sX) * aSize / d,
|
|
||||||
vY = (tY - sY) * aSize / d;
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings('edgeHoverColor') === 'edge') {
|
|
||||||
color = edge.hover_color || color;
|
|
||||||
} else {
|
|
||||||
color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(sX, sY);
|
|
||||||
context.lineTo(
|
|
||||||
aX,
|
|
||||||
aY
|
|
||||||
);
|
|
||||||
context.stroke();
|
|
||||||
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(aX + vX, aY + vY);
|
|
||||||
context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
|
|
||||||
context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
|
|
||||||
context.lineTo(aX + vX, aY + vY);
|
|
||||||
context.closePath();
|
|
||||||
context.fill();
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,64 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edgehovers');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This hover renderer will display the edge with a different color or size.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edgehovers.curve =
|
|
||||||
function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1),
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor'),
|
|
||||||
cp = {},
|
|
||||||
sSize = source[prefix + 'size'],
|
|
||||||
sX = source[prefix + 'x'],
|
|
||||||
sY = source[prefix + 'y'],
|
|
||||||
tX = target[prefix + 'x'],
|
|
||||||
tY = target[prefix + 'y'];
|
|
||||||
|
|
||||||
cp = (source.id === target.id) ?
|
|
||||||
sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) :
|
|
||||||
sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings('edgeHoverColor') === 'edge') {
|
|
||||||
color = edge.hover_color || color;
|
|
||||||
} else {
|
|
||||||
color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(sX, sY);
|
|
||||||
if (source.id === target.id) {
|
|
||||||
context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY);
|
|
||||||
} else {
|
|
||||||
context.quadraticCurveTo(cp.x, cp.y, tX, tY);
|
|
||||||
}
|
|
||||||
context.stroke();
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,96 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edgehovers');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This hover renderer will display the edge with a different color or size.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edgehovers.curvedArrow =
|
|
||||||
function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor'),
|
|
||||||
cp = {},
|
|
||||||
size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1),
|
|
||||||
tSize = target[prefix + 'size'],
|
|
||||||
sX = source[prefix + 'x'],
|
|
||||||
sY = source[prefix + 'y'],
|
|
||||||
tX = target[prefix + 'x'],
|
|
||||||
tY = target[prefix + 'y'],
|
|
||||||
d,
|
|
||||||
aSize,
|
|
||||||
aX,
|
|
||||||
aY,
|
|
||||||
vX,
|
|
||||||
vY;
|
|
||||||
|
|
||||||
cp = (source.id === target.id) ?
|
|
||||||
sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) :
|
|
||||||
sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
|
|
||||||
|
|
||||||
if (source.id === target.id) {
|
|
||||||
d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2));
|
|
||||||
aSize = size * 2.5;
|
|
||||||
aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d;
|
|
||||||
aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d;
|
|
||||||
vX = (tX - cp.x1) * aSize / d;
|
|
||||||
vY = (tY - cp.y1) * aSize / d;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2));
|
|
||||||
aSize = size * 2.5;
|
|
||||||
aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d;
|
|
||||||
aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d;
|
|
||||||
vX = (tX - cp.x) * aSize / d;
|
|
||||||
vY = (tY - cp.y) * aSize / d;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings('edgeHoverColor') === 'edge') {
|
|
||||||
color = edge.hover_color || color;
|
|
||||||
} else {
|
|
||||||
color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(sX, sY);
|
|
||||||
if (source.id === target.id) {
|
|
||||||
context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY);
|
|
||||||
} else {
|
|
||||||
context.quadraticCurveTo(cp.x, cp.y, aX, aY);
|
|
||||||
}
|
|
||||||
context.stroke();
|
|
||||||
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(aX + vX, aY + vY);
|
|
||||||
context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
|
|
||||||
context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
|
|
||||||
context.lineTo(aX + vX, aY + vY);
|
|
||||||
context.closePath();
|
|
||||||
context.fill();
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,57 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edgehovers');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This hover renderer will display the edge with a different color or size.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edgehovers.def =
|
|
||||||
function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
size = edge[prefix + 'size'] || 1,
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor');
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings('edgeHoverColor') === 'edge') {
|
|
||||||
color = edge.hover_color || color;
|
|
||||||
} else {
|
|
||||||
color = edge.hover_color || settings('defaultEdgeHoverColor') || color;
|
|
||||||
}
|
|
||||||
size *= settings('edgeHoverSizeRatio');
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(
|
|
||||||
source[prefix + 'x'],
|
|
||||||
source[prefix + 'y']
|
|
||||||
);
|
|
||||||
context.lineTo(
|
|
||||||
target[prefix + 'x'],
|
|
||||||
target[prefix + 'y']
|
|
||||||
);
|
|
||||||
context.stroke();
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,66 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edges');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This edge renderer will display edges as arrows going from the source node
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edges.arrow = function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor'),
|
|
||||||
size = edge[prefix + 'size'] || 1,
|
|
||||||
tSize = target[prefix + 'size'],
|
|
||||||
sX = source[prefix + 'x'],
|
|
||||||
sY = source[prefix + 'y'],
|
|
||||||
tX = target[prefix + 'x'],
|
|
||||||
tY = target[prefix + 'y'],
|
|
||||||
aSize = Math.max(size * 2.5, settings('minArrowSize')),
|
|
||||||
d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)),
|
|
||||||
aX = sX + (tX - sX) * (d - aSize - tSize) / d,
|
|
||||||
aY = sY + (tY - sY) * (d - aSize - tSize) / d,
|
|
||||||
vX = (tX - sX) * aSize / d,
|
|
||||||
vY = (tY - sY) * aSize / d;
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(sX, sY);
|
|
||||||
context.lineTo(
|
|
||||||
aX,
|
|
||||||
aY
|
|
||||||
);
|
|
||||||
context.stroke();
|
|
||||||
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(aX + vX, aY + vY);
|
|
||||||
context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
|
|
||||||
context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
|
|
||||||
context.lineTo(aX + vX, aY + vY);
|
|
||||||
context.closePath();
|
|
||||||
context.fill();
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,57 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edges');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This edge renderer will display edges as curves.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edges.curve = function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
size = edge[prefix + 'size'] || 1,
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor'),
|
|
||||||
cp = {},
|
|
||||||
sSize = source[prefix + 'size'],
|
|
||||||
sX = source[prefix + 'x'],
|
|
||||||
sY = source[prefix + 'y'],
|
|
||||||
tX = target[prefix + 'x'],
|
|
||||||
tY = target[prefix + 'y'];
|
|
||||||
|
|
||||||
cp = (source.id === target.id) ?
|
|
||||||
sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) :
|
|
||||||
sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(sX, sY);
|
|
||||||
if (source.id === target.id) {
|
|
||||||
context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY);
|
|
||||||
} else {
|
|
||||||
context.quadraticCurveTo(cp.x, cp.y, tX, tY);
|
|
||||||
}
|
|
||||||
context.stroke();
|
|
||||||
};
|
|
||||||
})();
|
|
@ -1,88 +0,0 @@
|
|||||||
;(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
sigma.utils.pkg('sigma.canvas.edges');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This edge renderer will display edges as curves with arrow heading.
|
|
||||||
*
|
|
||||||
* @param {object} edge The edge object.
|
|
||||||
* @param {object} source node The edge source node.
|
|
||||||
* @param {object} target node The edge target node.
|
|
||||||
* @param {CanvasRenderingContext2D} context The canvas context.
|
|
||||||
* @param {configurable} settings The settings function.
|
|
||||||
*/
|
|
||||||
sigma.canvas.edges.curvedArrow =
|
|
||||||
function(edge, source, target, context, settings) {
|
|
||||||
var color = edge.color,
|
|
||||||
prefix = settings('prefix') || '',
|
|
||||||
edgeColor = settings('edgeColor'),
|
|
||||||
defaultNodeColor = settings('defaultNodeColor'),
|
|
||||||
defaultEdgeColor = settings('defaultEdgeColor'),
|
|
||||||
cp = {},
|
|
||||||
size = edge[prefix + 'size'] || 1,
|
|
||||||
tSize = target[prefix + 'size'],
|
|
||||||
sX = source[prefix + 'x'],
|
|
||||||
sY = source[prefix + 'y'],
|
|
||||||
tX = target[prefix + 'x'],
|
|
||||||
tY = target[prefix + 'y'],
|
|
||||||
aSize = Math.max(size * 2.5, settings('minArrowSize')),
|
|
||||||
d,
|
|
||||||
aX,
|
|
||||||
aY,
|
|
||||||
vX,
|
|
||||||
vY;
|
|
||||||
|
|
||||||
cp = (source.id === target.id) ?
|
|
||||||
sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) :
|
|
||||||
sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
|
|
||||||
|
|
||||||
if (source.id === target.id) {
|
|
||||||
d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2));
|
|
||||||
aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d;
|
|
||||||
aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d;
|
|
||||||
vX = (tX - cp.x1) * aSize / d;
|
|
||||||
vY = (tY - cp.y1) * aSize / d;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2));
|
|
||||||
aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d;
|
|
||||||
aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d;
|
|
||||||
vX = (tX - cp.x) * aSize / d;
|
|
||||||
vY = (tY - cp.y) * aSize / d;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!color)
|
|
||||||
switch (edgeColor) {
|
|
||||||
case 'source':
|
|
||||||
color = source.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
case 'target':
|
|
||||||
color = target.color || defaultNodeColor;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = defaultEdgeColor;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = size;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(sX, sY);
|
|
||||||
if (source.id === target.id) {
|
|
||||||
context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY);
|
|
||||||
} else {
|
|
||||||
context.quadraticCurveTo(cp.x, cp.y, aX, aY);
|
|
||||||
}
|
|
||||||
context.stroke();
|
|
||||||
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.beginPath();
|
|
||||||
context.moveTo(aX + vX, aY + vY);
|
|
||||||
context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
|
|
||||||
context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
|
|
||||||
context.lineTo(aX + vX, aY + vY);
|
|
||||||
context.closePath();
|
|
||||||
context.fill();
|
|
||||||
};
|
|
||||||
})();
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user