Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file picker with png preview of the .freckl files. #12

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions lib/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
library utils;

import 'dart:async';
import 'dart:html';

/// The index file is a list of maps with the following structure:
/// [
/// ...
/// {
/// "id": <data entry ID>,
/// "img": <absolute URL to the data entry image>,
/// "freckl": <absolute URL to the data entry freckl file>,
/// }
/// ...
/// }
void validateIndexFileContents(indexContents) {
assert(indexContents is List);
for (var element in indexContents) {
assert(element is Map);
Map map = element;
assert(map.containsKey('id'));
assert(map.containsKey('img'));
assert(map.containsKey('freckl'));
}
}

/// A freckl file is a list of maps with the following structure:
/// [
/// ...
/// {
/// 'point': [<num x>, <num y>],
/// 'value': <data point value>,
/// 'color': <color as an int or string>,
/// 'uri': <optional uri to more data point information>
/// }
/// ...
/// ]
void validateFrecklFileContents(frecklContents) {
assert(frecklContents is List);
for (var element in frecklContents) {
assert(element is Map);
Map map = element;
// Check for point
assert(map.containsKey('point'));
assert(map['point'] is List);
assert(map['point'].length == 2);
// Check for value
assert(map.containsKey('value'));
assert(map.containsKey('color'));
}
}

/// Color can be an int (the format of the image package for Dart)
/// or a (hex) string.
String readColor(color) {
String returnColor;
if (color is int) {
int c = color;
int r = c & 0xff;
int g = (c >> 8) & 0xff;
int b = (c >> 16) & 0xff;
int a = (c >> 24) & 0xff;
returnColor = 'rgba($r, $g, $b, $a)';
} else {
returnColor = color;
}
return returnColor;
}

/// Turns a freckl data point map to a string.
Future stringifyFreckl(Map frecklData) async {
StringBuffer result = new StringBuffer();
for (String key in frecklData.keys) {
result.writeln('$key : ${frecklData[key]}');
}
if (frecklData.containsKey('uri')) {
String response = await HttpRequest.getString(frecklData['uri']);
result.writeln(response);
}
return new Future.value(result.toString());
}
3 changes: 3 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ dependencies:
svg_pan_zoom:
git:
url: git://github.com/marianamarasoiu/svg_pan_zoom.git
infinity_view:
git:
url: git://github.com/marianamarasoiu/infinity_view.git
4 changes: 2 additions & 2 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<body>

<div id="path-selector">
<input id="path-to-json" size="180" type="text" value="http://localhost:9090/">
<button id="path-button" type="button">Get data</button>
<input id="path-to-index-file" size="180" type="text" value="http://localhost:9090/index.json"/>
<button id="path-button" type="button">Open data picker</button>
</div>

<div id='visualisation-container' style='width:1000px; height:500px'>
Expand Down
220 changes: 136 additions & 84 deletions web/main.dart
Original file line number Diff line number Diff line change
@@ -1,64 +1,23 @@
import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:html';
import 'dart:math' as math;
import 'dart:svg' as svg;
import 'dart:convert' show JSON;

import 'package:svg_pan_zoom/svg_pan_zoom.dart' as panzoom;
import 'package:infinity_view/infinity_view.dart' as inf;
import 'package:freckl/utils.dart' as utils;

panzoom.SvgPanZoom panZoom;
String visSelector = '.inner';
int visWidth = 1000;
int visHeight = 500;

void main() {
querySelector('#path-to-json').onKeyUp.listen(pathToJsonHander);
querySelector('#path-button').onMouseUp.listen(buttonPressHandler);

querySelectorAll('.splitter').forEach((Element element) {
bool vertical = element.classes.contains('vertical');
bool horizontal = element.classes.contains('horizontal');

element.onMouseDown.listen((MouseEvent e) {
if (e.which != 1) {
return;
}

e.preventDefault();
Point offset = e.offset;
querySelector('#path-to-index-file').onKeyUp.listen(indexFileKeyHandler);
querySelector('#path-button').onMouseUp.listen(indexFileButtonHandler);

StreamSubscription moveSubscription, upSubscription;
Function cancel = () {
if (moveSubscription != null) {
moveSubscription.cancel();
}
if (upSubscription != null) {
upSubscription.cancel();
}
};

moveSubscription = document.onMouseMove.listen((e) {
List neighbors = element.parent.children;
Element target = neighbors[neighbors.indexOf(element) - 1];

if (e.which != 1) {
cancel();
} else {
Point current = e.client - element.parent.client.topLeft - offset;
current -= target.marginEdge.topLeft;
if (vertical) {
target.style.width = '${current.x}px';
} else if (horizontal) {
target.style.height = '${current.y}px';
}
}
});

upSubscription = document.onMouseUp.listen((e) {
cancel();
});
});
});
querySelectorAll('.splitter').forEach(setupSplitter);

panZoom = new panzoom.SvgPanZoom.selector('.inner-svg');
panZoom
Expand All @@ -67,23 +26,94 @@ void main() {
..zoomSensitivity = 0.02;
}

void pathToJsonHander(KeyboardEvent event) {
if (event.keyCode == 13) _loadData();
void indexFileKeyHandler(KeyboardEvent event) {
if (event.keyCode == 13) {
_loadIndexFileFromUrl(
(querySelector('#path-to-index-file') as InputElement).value);
}
}

void indexFileButtonHandler(MouseEvent event) {
_loadIndexFileFromUrl(
(querySelector('#path-to-index-file') as InputElement).value);
}

void buttonPressHandler(MouseEvent event) {
_loadData();
void _loadIndexFileFromUrl(String fileURL) {
HttpRequest.getString(fileURL).then((String response) {
var fileContents = JSON.decode(response);
utils.validateIndexFileContents(fileContents);
displayImageList(fileContents);
});
}

void _loadData() {
InputElement input = querySelector('#path-to-json');
String pathToJson = input.value;
HttpRequest.getString(pathToJson).then((String response) {
List data = JSON.decode(response);
displayData(data);
void _loadFrecklFileFromUrl(String fileURL) {
HttpRequest.getString(fileURL).then((String response) {
var fileContents = JSON.decode(response);
utils.validateIndexFileContents(fileContents);
displayData(fileContents);
});
}

void displayImageList(List indexList) {
DivElement selectorWindow = new DivElement();
selectorWindow.id = 'data-selector-window';
document.body.append(selectorWindow);

DivElement selectorContainer = new DivElement();
selectorContainer.id = 'selector-items-container';
selectorWindow.append(selectorContainer);

inf.InfinityView view;

Function createItemElement = (Map dataEntryMap) {
DivElement wrapper = new DivElement();
wrapper.classes.add('item-selector');

DivElement imgWrapper = new DivElement();
imgWrapper.classes.add('image');
wrapper.append(imgWrapper);

ImageElement img = new ImageElement();
img.src = dataEntryMap['img'];
img.style
..maxWidth = '100%'
..maxHeight = '100%';
imgWrapper.append(img);

DivElement text = new DivElement();
text.classes.add('text');
text.text = dataEntryMap['id'];
wrapper.append(text);

StreamSubscription windowClick;
windowClick = window.onClick.listen((MouseEvent event) {
selectorWindow.remove();
view = null;
windowClick.cancel();
});

wrapper.onClick.listen((MouseEvent event) {
event.preventDefault();
event.stopPropagation();
selectorWindow.remove();
view = null;
windowClick.cancel();
_loadFrecklFileFromUrl(dataEntryMap['freckl']);
});

window.onClick.listen((MouseEvent event) {
selectorWindow.remove();
view = null;
});
return wrapper;
};

view = new inf.InfinityView(indexList, createItemElement);
view.pageHorizontalItemCount = 5;
view.pageVerticalItemCount = 7;
view.attachToElement(selectorContainer);
}

void displayData(List data) {
// Before doing anything else, reset the viewport of the visualisation.
resetVisualisation();
Expand All @@ -98,19 +128,6 @@ void displayData(List data) {
maxX = maxY = double.NEGATIVE_INFINITY;

for (Map dataPoint in data) {
// [dataPoint] looks like:
// {'point': [x, y],
// 'value': v,
// 'color': c,
// 'uri': path,
// ...
// }
assert(dataPoint.containsKey('point'));
assert(dataPoint['point'] is List);
assert(dataPoint['point'].length == 2);
assert(dataPoint.containsKey('value'));
assert(dataPoint.containsKey('color'));

minX = dataPoint['point'][0] < minX ? dataPoint['point'][0] : minX;
minY = dataPoint['point'][1] < minY ? dataPoint['point'][1] : minY;
maxX = dataPoint['point'][0] > maxX ? dataPoint['point'][0] : maxX;
Expand All @@ -134,19 +151,7 @@ void displayData(List data) {
}

for (Map dataPoint in data) {
// Color can be an int (the format of the image package for Dart)
// or a (hex) string.
String color;
if (dataPoint['color'] is int) {
int c = dataPoint['color'];
int r = c & 0xff;
int g = (c >> 8) & 0xff;
int b = (c >> 16) & 0xff;
int a = (c >> 24) & 0xff;
color = 'rgba($r, $g, $b, $a)';
} else {
color = dataPoint['color'];
}
String color = utils.readColor(dataPoint['color']);

// Add an area aura
var aura = _createDataCircle(dataPoint, 25, color, 0.01);
Expand All @@ -159,7 +164,9 @@ void displayData(List data) {
point.onMouseOver.listen((MouseEvent event) {
DivElement tooltip = querySelector('#info-left');
PreElement preElement = tooltip.querySelector('pre');
prettyString(dataPoint).then((String text) => preElement.text = text);
utils
.stringifyFreckl(dataPoint)
.then((String text) => preElement.text = text);
});

group.append(point);
Expand Down Expand Up @@ -208,3 +215,48 @@ Future prettyString(Map map) async {
}
return new Future.value(result.toString());
}

void setupSplitter(Element element) {
bool vertical = element.classes.contains('vertical');
bool horizontal = element.classes.contains('horizontal');

element.onMouseDown.listen((MouseEvent e) {
if (e.which != 1) {
return;
}

e.preventDefault();
Point offset = e.offset;

StreamSubscription moveSubscription, upSubscription;
Function cancel = () {
if (moveSubscription != null) {
moveSubscription.cancel();
}
if (upSubscription != null) {
upSubscription.cancel();
}
};

moveSubscription = document.onMouseMove.listen((e) {
List neighbors = element.parent.children;
Element target = neighbors[neighbors.indexOf(element) - 1];

if (e.which != 1) {
cancel();
} else {
Point current = e.client - element.parent.client.topLeft - offset;
current -= target.marginEdge.topLeft;
if (vertical) {
target.style.width = '${current.x}px';
} else if (horizontal) {
target.style.height = '${current.y}px';
}
}
});

upSubscription = document.onMouseUp.listen((e) {
cancel();
});
});
}
Loading