Merge branch 'feature/globebrowsing' of github.com:OpenSpace/OpenSpace-Development into feature/globebrowsing

This commit is contained in:
Kalle Bladin
2016-06-27 16:17:22 -04:00
14 changed files with 569 additions and 36 deletions

View File

@@ -0,0 +1,19 @@
<GDAL_WMS>
<Service name="TMS">
<ServerUrl>http://dzw9r5p966egh.cloudfront.net/catalog/Mars_Viking_MDIM21_ClrMosaic_global_232m/1.0.0//default/default028mm/${z}/${y}/${x}.jpg</ServerUrl>
</Service>
<DataWindow>
<UpperLeftX>-180.0</UpperLeftX>
<UpperLeftY>90</UpperLeftY>
<LowerRightX>180.0</LowerRightX>
<LowerRightY>-90</LowerRightY>
<TileLevel>8</TileLevel>
<TileCountX>2</TileCountX>
<TileCountY>1</TileCountY>
<YOrigin>top</YOrigin>
</DataWindow>
<Projection>EPSG:4326</Projection>
<BlockSizeX>256</BlockSizeX>
<BlockSizeY>256</BlockSizeY>
<BandsCount>3</BandsCount>
</GDAL_WMS>

View File

@@ -63,6 +63,7 @@ set(HEADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.h
${CMAKE_CURRENT_SOURCE_DIR}/other/threadpool.h
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentqueue.h
${CMAKE_CURRENT_SOURCE_DIR}/other/statscollector.h
)
@@ -104,6 +105,7 @@ set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/other/lrucache.inl
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.inl
${CMAKE_CURRENT_SOURCE_DIR}/other/threadpool.cpp
${CMAKE_CURRENT_SOURCE_DIR}/other/statscollector.cpp
)

View File

@@ -43,6 +43,8 @@
#define _USE_MATH_DEFINES
#include <math.h>
#include <ctime>
namespace {
const std::string _loggerCat = "ChunkLodGlobe";
}
@@ -52,18 +54,21 @@ namespace openspace {
const ChunkIndex ChunkedLodGlobe::LEFT_HEMISPHERE_INDEX = ChunkIndex(0, 0, 1);
const ChunkIndex ChunkedLodGlobe::RIGHT_HEMISPHERE_INDEX = ChunkIndex(1, 0, 1);
const GeodeticPatch ChunkedLodGlobe::COVERAGE = GeodeticPatch(0, 0, 90, 180);
ChunkedLodGlobe::ChunkedLodGlobe(
const Ellipsoid& ellipsoid,
size_t segmentsPerPatch,
std::shared_ptr<TileProviderManager> tileProviderManager)
: _ellipsoid(ellipsoid)
, _leftRoot(new ChunkNode(Chunk(this, LEFT_HEMISPHERE_INDEX)))
, _rightRoot(new ChunkNode(Chunk(this, RIGHT_HEMISPHERE_INDEX)))
, _leftRoot(std::make_unique<ChunkNode>(Chunk(this, LEFT_HEMISPHERE_INDEX)))
, _rightRoot(std::make_unique<ChunkNode>(Chunk(this, RIGHT_HEMISPHERE_INDEX)))
, minSplitDepth(2)
, maxSplitDepth(22)
, _savedCamera(nullptr)
, _tileProviderManager(tileProviderManager)
, stats(StatsCollector(absPath("test_stats")))
{
auto geometry = std::make_shared<SkirtedGrid>(
@@ -76,13 +81,13 @@ namespace openspace {
_chunkCullers.push_back(std::make_unique<HorizonCuller>());
_chunkCullers.push_back(std::make_unique<FrustumCuller>(AABB3(vec3(-1, -1, 0), vec3(1, 1, 1e35))));
_chunkEvaluatorByAvailableTiles = std::make_unique<EvaluateChunkLevelByAvailableTileData>();
_chunkEvaluatorByProjectedArea = std::make_unique<EvaluateChunkLevelByProjectedArea>();
_chunkEvaluatorByDistance = std::make_unique<EvaluateChunkLevelByDistance>();
_renderer = std::make_unique<ChunkRenderer>(geometry, tileProviderManager);
}
ChunkedLodGlobe::~ChunkedLodGlobe() {
@@ -116,6 +121,16 @@ namespace openspace {
return false;
}
const ChunkNode& ChunkedLodGlobe::findChunkNode(const Geodetic2 p) const {
ghoul_assert(COVERAGE.contains(p), "Point must be in lat [-90, 90] and lon [-180, 180]");
return p.lon < COVERAGE.center().lon ? _leftRoot->find(p) : _rightRoot->find(p);
}
ChunkNode& ChunkedLodGlobe::findChunkNode(const Geodetic2 p) {
ghoul_assert(COVERAGE.contains(p), "Point must be in lat [-90, 90] and lon [-180, 180]");
return p.lon < COVERAGE.center().lon ? _leftRoot->find(p) : _rightRoot->find(p);
}
int ChunkedLodGlobe::getDesiredLevel(const Chunk& chunk, const RenderData& renderData) const {
int desiredLevel = 0;
if (debugOptions.levelByProjAreaElseDistance) {
@@ -135,9 +150,23 @@ namespace openspace {
desiredLevel = glm::clamp(desiredLevel, minSplitDepth, maxSplitDepth);
return desiredLevel;
}
void ChunkedLodGlobe::render(const RenderData& data){
stats.startNewRecord();
int j2000s = Time::now().unsyncedJ2000Seconds();
int lastJ2000s = stats.i.previous("time");
if (j2000s == lastJ2000s) {
stats.disable();
}
else {
stats.enable();
}
stats.i["time"] = j2000s;
minDistToCamera = INFINITY;
_leftRoot->updateChunkTree(data);
@@ -151,10 +180,16 @@ namespace openspace {
// Render function
std::function<void(const ChunkNode&)> renderJob = [this, &data, &mvp](const ChunkNode& chunkNode) {
stats.i["chunks"]++;
const Chunk& chunk = chunkNode.getChunk();
if (chunkNode.isLeaf() && chunk.isVisible()) {
_renderer->renderChunk(chunkNode.getChunk(), data);
debugRenderChunk(chunk, mvp);
if (chunkNode.isLeaf()){
stats.i["chunks leafs"]++;
if (chunk.isVisible()) {
stats.i["rendered chunks"]++;
double t0 = Time::now().unsyncedJ2000Seconds();
_renderer->renderChunk(chunkNode.getChunk(), data);
debugRenderChunk(chunk, mvp);
}
}
};

View File

@@ -44,6 +44,8 @@
#include <modules/globebrowsing/chunk/chunkrenderer.h>
#include <modules/globebrowsing/tile/tileprovider.h>
#include <modules/globebrowsing/other/statscollector.h>
namespace ghoul {
namespace opengl {
@@ -72,6 +74,9 @@ namespace openspace {
void render(const RenderData& data) override;
void update(const UpdateData& data) override;
const ChunkNode& findChunkNode(const Geodetic2 location) const;
ChunkNode& findChunkNode(const Geodetic2 location);
void setStateMatrix(const glm::dmat3& stateMatrix);
bool testIfCullable(const Chunk& chunk, const RenderData& renderData) const;
@@ -112,11 +117,14 @@ namespace openspace {
bool levelByProjAreaElseDistance = true;
} debugOptions;
StatsCollector stats;
private:
void debugRenderChunk(const Chunk& chunk, const glm::dmat4& data) const;
static const GeodeticPatch COVERAGE;
// Covers all negative longitudes
std::unique_ptr<ChunkNode> _leftRoot;
@@ -141,6 +149,7 @@ namespace openspace {
std::shared_ptr<Camera> _savedCamera;
std::shared_ptr<TileProviderManager> _tileProviderManager;
};
} // namespace openspace

View File

@@ -154,6 +154,40 @@ void ChunkNode::reverseBreadthFirst(const std::function<void(const ChunkNode&)>&
}
}
#define CHUNK_NODE_FIND(node, p) \
while (!node->isLeaf()) { \
const Geodetic2 center = node->_chunk.surfacePatch().center();\
int index = 0;\
if (center.lon < p.lon) {\
++index;\
}\
if (p.lat < center.lat) {\
++index;\
++index;\
}\
node = &(node->getChild((Quad)index));\
}
const ChunkNode& ChunkNode::find(const Geodetic2& location) const {
const ChunkNode* node = this;
CHUNK_NODE_FIND(node, location);
return *node;
}
ChunkNode& ChunkNode::find(const Geodetic2& location) {
ChunkNode* node = this;
CHUNK_NODE_FIND(node, location);
return *node;
}
const ChunkNode& ChunkNode::getChild(Quad quad) const {
return *_children[quad];
}
ChunkNode& ChunkNode::getChild(Quad quad) {
return *_children[quad];
}
void ChunkNode::split(int depth) {
if (depth > 0 && isLeaf()) {
for (size_t i = 0; i < 4; i++) {
@@ -180,9 +214,6 @@ void ChunkNode::merge() {
ghoul_assert(isLeaf(), "ChunkNode must be leaf after merge");
}
const ChunkNode& ChunkNode::getChild(Quad quad) const {
return *_children[quad];
}
const Chunk& ChunkNode::getChunk() const {
return _chunk;

View File

@@ -65,8 +65,13 @@ public:
void depthFirst(const std::function<void(const ChunkNode&)>& f) const;
void breadthFirst(const std::function<void(const ChunkNode&)>& f) const;
void reverseBreadthFirst(const std::function<void(const ChunkNode&)>& f) const;
const ChunkNode& find(const Geodetic2& location) const;
ChunkNode& find(const Geodetic2& location);
const ChunkNode& getChild(Quad quad) const;
ChunkNode& getChild(Quad quad);
const Chunk& getChunk() const;
bool updateChunkTree(const RenderData& data);

View File

@@ -62,23 +62,18 @@ namespace openspace {
: _tileProviderManager(tileProviderManager)
, _grid(grid)
{
_globalRenderingShaderProvider = std::shared_ptr<LayeredTextureShaderProvider>
(new LayeredTextureShaderProvider(
_globalRenderingShaderProvider = std::make_shared<LayeredTextureShaderProvider>(
"GlobalChunkedLodPatch",
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_vs.glsl",
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl"));
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl");
_localRenderingShaderProvider = std::shared_ptr<LayeredTextureShaderProvider>
(new LayeredTextureShaderProvider(
_localRenderingShaderProvider = std::make_shared<LayeredTextureShaderProvider>(
"LocalChunkedLodPatch",
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_vs.glsl",
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl"));
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl");
_globalProgramUniformHandler = std::shared_ptr<LayeredTextureShaderUniformIdHandler>
(new LayeredTextureShaderUniformIdHandler());
_localProgramUniformHandler = std::shared_ptr<LayeredTextureShaderUniformIdHandler>
(new LayeredTextureShaderUniformIdHandler());
_globalProgramUniformHandler = std::make_shared<LayeredTextureShaderUniformIdHandler>();
_localProgramUniformHandler = std::make_shared<LayeredTextureShaderUniformIdHandler>();
}
@@ -170,8 +165,7 @@ namespace openspace {
LayeredTextures::NUM_TEXTURE_CATEGORIES> tileProviders;
LayeredTexturePreprocessingData layeredTexturePreprocessingData;
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++)
{
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++) {
tileProviders[category] = _tileProviderManager->getActivatedLayerCategory(
LayeredTextures::TextureCategory(category));
@@ -209,19 +203,16 @@ namespace openspace {
ghoul::opengl::TextureUnit blendTexture1;
ghoul::opengl::TextureUnit blendTexture2;
};
std::array<std::vector<BlendTexUnits>,
LayeredTextures::NUM_TEXTURE_CATEGORIES> texUnits;
std::array<std::vector<BlendTexUnits>, LayeredTextures::NUM_TEXTURE_CATEGORIES> texUnits;
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++) {
texUnits[category].resize(tileProviders[category].size());
}
// Go through all the categories
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++)
{
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++) {
// Go through all the providers in this category
int i = 0;
for (auto it = tileProviders[category].begin(); it != tileProviders[category].end(); it++)
{
for (auto it = tileProviders[category].begin(); it != tileProviders[category].end(); it++) {
auto tileProvider = it->get();
// Get the texture that should be used for rendering
@@ -272,8 +263,9 @@ namespace openspace {
// Go through all the height maps and set depth tranforms
int i = 0;
for (auto it = tileProviders[LayeredTextures::HeightMaps].begin();
it != tileProviders[LayeredTextures::HeightMaps].end(); it++) {
auto it = tileProviders[LayeredTextures::HeightMaps].begin();
auto end = tileProviders[LayeredTextures::HeightMaps].end();
for (; it != end; it++) {
auto tileProvider = *it;
TileDepthTransform depthTransform = tileProvider->depthTransform();
@@ -314,7 +306,7 @@ namespace openspace {
break;
}
}
if(performAnyBlending) {
if (performAnyBlending) {
float distanceScaleFactor = chunk.owner()->lodScaleFactor * ellipsoid.minimumRadius();
programObject->setUniform("cameraPosition", vec3(data.camera.positionVec3()));
programObject->setUniform("distanceScaleFactor", distanceScaleFactor);

View File

@@ -181,7 +181,12 @@ namespace openspace {
return _center.lon + _halfSize.lon;
}
bool GeodeticPatch::contains(const Geodetic2& p) const {
Geodetic2 diff = _center - p;
return glm::abs(diff.lat) <= _halfSize.lat && glm::abs(diff.lon) <= _halfSize.lon;
}
Scalar GeodeticPatch::edgeLatitudeNearestEquator() const {
return _center.lat + _halfSize.lat * (isNorthern() ? -1 : 1);

View File

@@ -127,6 +127,11 @@ public:
Scalar minLon() const;
Scalar maxLon() const;
/**
* returns true if the specified coordinate is contained within the patch
*/
bool contains(const Geodetic2& p) const;
/**
* Clamps a point to the patch region

View File

@@ -197,8 +197,8 @@ namespace openspace {
// Get the uv coordinates to sample from
Geodetic2 geodeticPosition = _ellipsoid.cartesianToGeodetic2(position);
int chunkLevel =
tileProvider->getAsyncTileReader()->getTextureDataProvider()->getMaximumLevel();
int chunkLevel = _chunkedLodGlobe->findChunkNode(geodeticPosition).getChunk().index().level;
ChunkIndex chunkIdx = ChunkIndex(geodeticPosition, chunkLevel);
GeodeticPatch patch = GeodeticPatch(chunkIdx);
Geodetic2 geoDiffPatch = patch.getCorner(Quad::NORTH_EAST) - patch.getCorner(Quad::SOUTH_WEST);

View File

@@ -0,0 +1,34 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2016 *
* *
* 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. *
****************************************************************************************/
#include <modules/globebrowsing/other/statscollector.h>
namespace openspace {
} // namespace openspace

View File

@@ -0,0 +1,262 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2016 *
* *
* 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. *
****************************************************************************************/
#ifndef __STATS_TRACKER_H__
#define __STATS_TRACKER_H__
#include <ghoul/logging/logmanager.h>
#include <ghoul/filesystem/filesystem>
#include <fstream>
#include <unordered_map>
#include <vector>
#include <set>
#include <memory>
namespace openspace {
template <typename T>
struct StatsRecord : public std::unordered_map<std::string, T> {
};
template <typename T>
struct StatsCollection : public std::vector<StatsRecord<T>> {
std::set<std::string> keys;
};
template <typename T> class TemplatedStatsCollector{
public:
TemplatedStatsCollector(bool& enabled, const std::string& delimiter)
: _enabled(enabled)
, _delimiter(delimiter)
, _writePos(0) { };
~TemplatedStatsCollector() { };
void startNewRecord() {
if(_enabled)
_data.push_back(StatsRecord<T>());
}
T& operator[](const std::string& name) {
if (_enabled) {
_data.keys.insert(name);
return _data.back()[name];
}
else return _dummy;
}
T previous(const std::string& name) {
if (_data.size() > 1) {
return _data[_data.size() - 2][name];
}
return T();
}
bool hasHeaders() {
return _data.keys.size() > 0;
}
bool hasRecordsToWrite() {
return _writePos < _data.size() - 1;
}
void reset() {
_data.clear();
_writePos = 0;
}
void writeHeader(std::ostream& os) {
auto keyIt = _data.keys.begin();
os << *keyIt;
while (++keyIt != _data.keys.end()) {
os << _delimiter << *keyIt;
}
}
void writeNextRecord(std::ostream& os) {
if (hasRecordsToWrite()) {
// output line by line
StatsRecord<T>& record = _data[_writePos];
// Access every key. Records with no entry will get a default value
auto keyIt = _data.keys.begin();
if (keyIt != _data.keys.end()) {
os << record[(*keyIt)];
while (++keyIt != _data.keys.end()) {
os << _delimiter << record[(*keyIt)];
}
}
_writePos++;
}
}
private:
StatsCollection<T> _data;
T _dummy; // used when disabled
bool& _enabled;
size_t _writePos;
std::string _delimiter;
};
class StatsCollector {
public:
StatsCollector() = delete;
StatsCollector(const std::string& filename, const std::string& delimiter = ",", bool enabled = true)
: _filename(filename)
, _enabled(enabled)
, _delimiter(delimiter)
, _hasWrittenHead(false)
, i(TemplatedStatsCollector<int>(_enabled, delimiter))
, d(TemplatedStatsCollector<double>(_enabled, delimiter))
{
};
~StatsCollector() {
dumpToDisk();
}
void startNewRecord() {
if (_enabled) {
i.startNewRecord();
d.startNewRecord();
}
}
void disable() {
_enabled = false;
}
void enable() {
_enabled = true;
}
void dumpToDisk() {
if (!_hasWrittenHead) {
writeHead();
}
writeData();
}
TemplatedStatsCollector<int> i;
TemplatedStatsCollector<double> d;
private:
void writeHead() {
std::ofstream ofs(_filename);
if (i.hasHeaders()) {
i.writeHeader(ofs);
if (d.hasHeaders()) {
ofs << _delimiter;
d.writeHeader(ofs);
}
}
else {
d.writeHeader(ofs);
}
ofs << std::endl;
ofs.close();
}
void writeData() {
std::ofstream ofs(_filename, std::ofstream::out | std::ofstream::app);
while (i.hasRecordsToWrite() || d.hasRecordsToWrite()) {
if (i.hasHeaders() && d.hasHeaders()) {
i.writeNextRecord(ofs); ofs << _delimiter; d.writeNextRecord(ofs);
}
else {
i.writeNextRecord(ofs); d.writeNextRecord(ofs);
}
ofs << std::endl;
}
i.reset(); d.reset();
ofs.close();
}
std::string _filename;
std::string _delimiter;
bool _enabled;
bool _hasWrittenHead;
};
/*
template <typename T>
struct StatsCsvWriter {
StatsCsvWriter(const std::string& delimiter)
: _delimiter(delimiter) { };
virtual void write(const StatsCollection<T>& stats, const std::string& filename) {
std::ofstream ofs(filename);
// output headers
auto keyIt = stats.keys.begin();
ofs << *keyIt;
while (keyIt++ != stats.keys.end()) {
ofs << _delimiter << *keyIt;
}
ofs << std::endl;
// output line by line
for (const StatsRecord<T>& record : stats) {
// Access every key. Records with no entry will get a default value
auto keyIt = stats.keys.begin();
ofs << record[*keyIt];
while (keyIt++ != stats.keys.end()) {
ofs << _delimiter << record[*keyIt];
}
ofs << std::endl;
}
ofs.close();
}
private:
std::string _delimiter;
};
*/
} // namespace openspace
#endif // __STATS_TRACKER_H__

114
monitor/index.html Normal file
View File

@@ -0,0 +1,114 @@
<html lang="en">
<head>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="http://getbootstrap.com/examples/justified-nav/justified-nav.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<svg></svg>
</body>
<script type="text/javascript">
function doVis(){
var filename = "../data/scene/debugglobe/test_stats";
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
//.interpolate("basis")
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.value); });
d3.select("svg").remove();
var svg = d3.select("body").append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv(filename, function(error, data) {
if (error) throw error;
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "time"; }));
data.forEach(function(d) {
d.time = new Date(+d.time);
});
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {time: d.time, value: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.value; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.value; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.time) + "," + y(d.value.value) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
});
}
doVis();
//setInterval(doVis, 1000);
</script>
</html>

20
monitor/style.css Normal file
View File

@@ -0,0 +1,20 @@
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}