Merge branch 'mgcore_T156_lintfix' into dev

This commit is contained in:
Marko Budiselic 2016-12-05 10:23:26 +01:00
commit 92f6004507
152 changed files with 69 additions and 26451 deletions

View File

@ -86,6 +86,7 @@ set(fmt_source_dir ${libs_dir}/fmt)
set(fmt_static_lib ${fmt_source_dir}/fmt/libfmt.a)
# 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)
# Catch (C++ Automated Test Cases in Headers)
set(catch_source_dir "${libs_dir}/Catch")
@ -156,7 +157,7 @@ if(CLANG_TIDY)
-config=''
--
-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()
# linter setup
@ -263,7 +264,7 @@ include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${src_dir})
include_directories(${build_include_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(${lexertl_dir})
include_directories(${libuv_source_dir}/include)

View File

@ -9,7 +9,7 @@
compile_cpu_path: "./compiled/cpu/"
# 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
template_cpu_hpp_path: "./template/template_code_cpu.hpp"

1
example/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.out

0
src/examples/timestamp.cpp → example/timestamp.cpp Executable file → Normal file
View File

View File

@ -5,6 +5,7 @@
#include <array>
#include <atomic>
#include <thread>
#include <cassert>
#include <sys/ioctl.h>
#include <unistd.h>
@ -47,6 +48,7 @@ int main(void)
auto max = std::accumulate(buckets.begin(), buckets.end(), 0u,
[](auto& acc, auto& x) { return std::max(acc, x.load()); });
assert(max != 0u);
std::cout << std::fixed;

View File

@ -90,7 +90,7 @@ std::string BoltDecoder::read_string()
size = marker & 0x0F;
}
// if the marker is 0xD0, size is an 8-bit unsigned integer
if (marker == pack::String8) {
else if (marker == pack::String8) {
size = read_byte();
}
// if the marker is 0xD1, size is a 16-bit big-endian unsigned integer

View File

@ -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
View File

@ -1 +0,0 @@
ve/

View File

@ -1,8 +0,0 @@
{
"esnext": true,
"browser": true,
"globals": {
"sigma": true,
"console": true
}
}

View File

View File

@ -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

View File

@ -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"]

View File

@ -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)

View File

@ -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"]

View File

@ -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

View File

@ -1,5 +0,0 @@
#!/bin/bash
docker stop memgraph_demo
docker rm memgraph_demo
docker run -it --rm memgraph_demo

View File

@ -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

View File

@ -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)()

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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()

View File

@ -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()

View File

@ -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
}

View File

@ -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()

View File

@ -1,8 +0,0 @@
*.txt text
*.js text
*.html text
*.md text
*.json text
*.yml text
*.css text
*.svg text

View File

@ -1,8 +0,0 @@
/node_modules
/npm-debug.log
/test*.html
.tern-*
*~
*.swp
.idea
*.iml

View File

@ -1,10 +0,0 @@
/node_modules
/demo
/doc
/test
/test*.html
/index.html
/mode/*/*test.js
/mode/*/*.html
/mode/index.html
.*

View File

@ -1,4 +0,0 @@
language: node_js
node_js:
- stable
sudo: false

View File

@ -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

View File

@ -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");
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
});
})();

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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();
};
})();

View File

@ -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();
};
})();

View File

@ -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();
};
})();

View File

@ -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();
};
})();

View File

@ -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();
};
})();

View File

@ -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();
};
})();

View File

@ -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