mirror of
https://github.com/wassname/train-seismic-interp.git
synced 2026-06-27 16:17:36 +08:00
init
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
notes/*
|
||||
secrets/*
|
||||
.awspublish-*
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,ipythonnotebook,linux,windows,bower
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
|
||||
### IPythonNotebook ###
|
||||
# Temporary data
|
||||
.ipynb_checkpoints/
|
||||
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### Bower ###
|
||||
bower_components
|
||||
.bower-cache
|
||||
.bower-registry
|
||||
.bower-tmp
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "js-seis-interp",
|
||||
"version": "0.0.1",
|
||||
"description": "Seismic interp in javascript",
|
||||
"main": "index.js",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "http-server src -oi"
|
||||
},
|
||||
"author": "wassname",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"fs": "0.0.2",
|
||||
"concurrent-transform": "^1.0.0",
|
||||
"gulp-awspublish": "^3.0.1",
|
||||
"gulp-debug": "^2.1.2",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-util": "^3.0.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
Interpret a synthetic seismic line so you can measure and improve your error.
|
||||
|
||||
Demo at [train-seismic-interp.wassname.com](http://train-seismic-interp.wassname.com/)
|
||||
|
||||

|
||||
|
||||
Seismic data source: "The Marmousi2 Model, Elastic Syntrain-seismic-interpthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment", Gary Martin, 2004, Thesis.
|
||||
@@ -0,0 +1,110 @@
|
||||
/* This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). */
|
||||
/* CSS specs as packaged in the D3 library (d3js.org). Please see license at http://colorbrewer.org/export/LICENSE.txt */
|
||||
.Dark2 svg .q0-3{stroke:rgb(27,158,119)}
|
||||
.Dark2 svg .q1-3{stroke:rgb(217,95,2)}
|
||||
.Dark2 svg .q2-3{stroke:rgb(117,112,179)}
|
||||
.Dark2 svg .q0-4{stroke:rgb(27,158,119)}
|
||||
.Dark2 svg .q1-4{stroke:rgb(217,95,2)}
|
||||
.Dark2 svg .q2-4{stroke:rgb(117,112,179)}
|
||||
.Dark2 svg .q3-4{stroke:rgb(231,41,138)}
|
||||
.Dark2 svg .q0-5{stroke:rgb(27,158,119)}
|
||||
.Dark2 svg .q1-5{stroke:rgb(217,95,2)}
|
||||
.Dark2 svg .q2-5{stroke:rgb(117,112,179)}
|
||||
.Dark2 svg .q3-5{stroke:rgb(231,41,138)}
|
||||
.Dark2 svg .q4-5{stroke:rgb(102,166,30)}
|
||||
.Dark2 svg .q0-6{stroke:rgb(27,158,119)}
|
||||
.Dark2 svg .q1-6{stroke:rgb(217,95,2)}
|
||||
.Dark2 svg .q2-6{stroke:rgb(117,112,179)}
|
||||
.Dark2 svg .q3-6{stroke:rgb(231,41,138)}
|
||||
.Dark2 svg .q4-6{stroke:rgb(102,166,30)}
|
||||
.Dark2 svg .q5-6{stroke:rgb(230,171,2)}
|
||||
.Dark2 svg .q0-7{stroke:rgb(27,158,119)}
|
||||
.Dark2 svg .q1-7{stroke:rgb(217,95,2)}
|
||||
.Dark2 svg .q2-7{stroke:rgb(117,112,179)}
|
||||
.Dark2 svg .q3-7{stroke:rgb(231,41,138)}
|
||||
.Dark2 svg .q4-7{stroke:rgb(102,166,30)}
|
||||
.Dark2 svg .q5-7{stroke:rgb(230,171,2)}
|
||||
.Dark2 svg .q6-7{stroke:rgb(166,118,29)}
|
||||
.Dark2 svg .q0-8{stroke:rgb(27,158,119)}
|
||||
.Dark2 svg .q1-8{stroke:rgb(217,95,2)}
|
||||
.Dark2 svg .q2-8{stroke:rgb(117,112,179)}
|
||||
.Dark2 svg .q3-8{stroke:rgb(231,41,138)}
|
||||
.Dark2 svg .q4-8{stroke:rgb(102,166,30)}
|
||||
.Dark2 svg .q5-8{stroke:rgb(230,171,2)}
|
||||
.Dark2 svg .q6-8{stroke:rgb(166,118,29)}
|
||||
.Dark2 svg .q7-8{stroke:rgb(102,102,102)}
|
||||
.Paired svg .q0-3{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-3{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-3{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q0-4{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-4{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-4{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-4{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q0-5{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-5{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-5{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-5{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-5{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q0-6{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-6{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-6{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-6{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-6{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-6{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q0-7{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-7{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-7{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-7{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-7{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-7{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q6-7{stroke:rgb(253,191,111)}
|
||||
.Paired svg .q0-8{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-8{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-8{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-8{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-8{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-8{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q6-8{stroke:rgb(253,191,111)}
|
||||
.Paired svg .q7-8{stroke:rgb(255,127,0)}
|
||||
.Paired svg .q0-9{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-9{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-9{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-9{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-9{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-9{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q6-9{stroke:rgb(253,191,111)}
|
||||
.Paired svg .q7-9{stroke:rgb(255,127,0)}
|
||||
.Paired svg .q8-9{stroke:rgb(202,178,214)}
|
||||
.Paired svg .q0-10{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-10{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-10{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-10{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-10{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-10{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q6-10{stroke:rgb(253,191,111)}
|
||||
.Paired svg .q7-10{stroke:rgb(255,127,0)}
|
||||
.Paired svg .q8-10{stroke:rgb(202,178,214)}
|
||||
.Paired svg .q9-10{stroke:rgb(106,61,154)}
|
||||
.Paired svg .q0-11{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-11{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-11{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-11{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-11{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-11{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q6-11{stroke:rgb(253,191,111)}
|
||||
.Paired svg .q7-11{stroke:rgb(255,127,0)}
|
||||
.Paired svg .q8-11{stroke:rgb(202,178,214)}
|
||||
.Paired svg .q9-11{stroke:rgb(106,61,154)}
|
||||
.Paired svg .q10-11{stroke:rgb(255,255,153)}
|
||||
.Paired svg .q0-12{stroke:rgb(166,206,227)}
|
||||
.Paired svg .q1-12{stroke:rgb(31,120,180)}
|
||||
.Paired svg .q2-12{stroke:rgb(178,223,138)}
|
||||
.Paired svg .q3-12{stroke:rgb(51,160,44)}
|
||||
.Paired svg .q4-12{stroke:rgb(251,154,153)}
|
||||
.Paired svg .q5-12{stroke:rgb(227,26,28)}
|
||||
.Paired svg .q6-12{stroke:rgb(253,191,111)}
|
||||
.Paired svg .q7-12{stroke:rgb(255,127,0)}
|
||||
.Paired svg .q8-12{stroke:rgb(202,178,214)}
|
||||
.Paired svg .q9-12{stroke:rgb(106,61,154)}
|
||||
.Paired svg .q10-12{stroke:rgb(255,255,153)}
|
||||
.Paired svg .q11-12{stroke:rgb(177,89,40)}
|
||||
@@ -0,0 +1,66 @@
|
||||
.seismic-container svg {
|
||||
/*border: 1px solid grey;*/
|
||||
}
|
||||
.seismic-container {
|
||||
/* to match error chart */
|
||||
/*margin-left: 70px;*/
|
||||
/*margin-right: 50px*/
|
||||
}
|
||||
|
||||
#score {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#interp {
|
||||
font-size: small;
|
||||
display: block;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
svg .line {
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
fill: none;
|
||||
/*opacity: 0.7;*/
|
||||
}
|
||||
svg .line.interp {
|
||||
/**/
|
||||
}
|
||||
|
||||
|
||||
|
||||
svg .line.ideal-horizon {
|
||||
stroke-dasharray: 5,5;
|
||||
}
|
||||
|
||||
/* this show the error between the interp and ideal answer */
|
||||
svg .line.error {
|
||||
/*stroke: green;*/
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
|
||||
fill: yellow;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
svg .well-trace {
|
||||
stroke-width: 2;
|
||||
stroke: red;
|
||||
fill: none;
|
||||
}
|
||||
svg .well .label {}
|
||||
|
||||
svg .well-top {
|
||||
stroke-width: 4px;
|
||||
opacity: 50%;
|
||||
/*stroke-dasharray: 1, 5;*/
|
||||
}
|
||||
|
||||
svg .axis path,
|
||||
svg .axis line {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
svg .axis.x .label {}
|
||||
svg .axis.y .label {}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"source": "'The Marmousi2 Model, Elastic Synthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment', Gary Martin, 2004, Thesis.",
|
||||
"model":"MODEL_P-WAVE_VELOCITY_1.25m.png",
|
||||
"seismic": "SYNTHETIC.png",
|
||||
"xinterval": 1.25,
|
||||
"xunit": "m",
|
||||
"yint": 5,
|
||||
"yunit": "m",
|
||||
"horizons": "FINAL_HORIZONS.json"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Source: "The Marmousi2 Model, Elastic Syntrain-seismic-interpthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment", Gary Martin, 2004, Thesis.
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>interpret synthetic seismic</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.4/dc.css" />
|
||||
|
||||
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/octicons/3.1.0/octicons.min.css"> -->
|
||||
<link rel="stylesheet" href="css/colorbrewer.css">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://cdn.jsdelivr.net/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>
|
||||
Click the image to interpret the synthetic seismic
|
||||
</h1>
|
||||
<button id="undo">Undo</button>
|
||||
<!-- <button id="reset">Reset</button> -->
|
||||
<button id="toggleModel" disabled>Toggle Seismic/VModel </button>
|
||||
<button id="Finish">Finish</button>
|
||||
<span style="">-</span>
|
||||
|
||||
|
||||
|
||||
<div id="svg" class="Paired seismic-container">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="error-graph">
|
||||
<div class="" id="interp-error-chart">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Show raw data here -->
|
||||
<div class="">
|
||||
<label>Error</label>
|
||||
<textbox id="score">N/A</textbox>
|
||||
(meters squared per meter)
|
||||
</div>
|
||||
|
||||
<svg height=110 width=170 id="svg-color-ordinal"></svg>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="">
|
||||
<h2>Instructions</h2>
|
||||
<ol>
|
||||
<li>
|
||||
Click the image to interpret the blue line
|
||||
</li>
|
||||
<li>Click finish to see how much error you had</li>
|
||||
</ol>
|
||||
|
||||
<h2>FAQ</h2>
|
||||
|
||||
<strong>How do you know where the true horizon is? Isn't it subjective</strong> This is synthetic seismic produced from a velocity model, so we know the real location of horizons, letting us get real feedback on our interpreation. To see the velocity model click "Toggle Seismic/VModel" after you have finished.
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<h4>
|
||||
Horizon data:
|
||||
</h4>
|
||||
<textbox id="interp"></textbox>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<small>Synthetic seismic data from <a href="#ref-1">[1]</a></small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<small><h4>References:</h4>
|
||||
<ol>
|
||||
<li id="ref-1">"The Marmousi2 Model, Elastic Synthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment", <a href="http://www.agl.uh.edu/downloads/downloads.htm">Gary Martin, 2004, Thesis.</a></li>
|
||||
</ol></small>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.4/dc.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.13.0/d3-legend.js"></script>
|
||||
<!-- app -->
|
||||
<script src="js/seismic-plot.js"></script>
|
||||
<script src="js/error-plot.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,167 @@
|
||||
function ErrorPlot(id, options){
|
||||
this.id=id
|
||||
this.options=_.defaultsDeep(options,{
|
||||
width: 2721/2, // must preserve aspect ratio
|
||||
height: 701/2,
|
||||
margin: {
|
||||
left:70,
|
||||
right:130
|
||||
}
|
||||
})
|
||||
this.data=[]
|
||||
}
|
||||
ErrorPlot.prototype.init = function(x,y){
|
||||
this.totalWidth = this.options.margin.left+this.options.width+this.options.margin.right
|
||||
this.totalHeight = this.options.margin.top+this.options.height+this.options.margin.bottom
|
||||
|
||||
this.vis = d3.select(this.id).append("svg")
|
||||
.attr("width", this.totalWidth)
|
||||
.attr("height", this.totalHeight);
|
||||
|
||||
// create x and y transforms
|
||||
var {xmin, xmax, ymin, ymax} = this.options.extent
|
||||
this.x = d3.scale.linear()
|
||||
.domain([xmin, xmax])
|
||||
.range([this.options.margin.left, this.options.width+this.options.margin.left]);
|
||||
|
||||
this.y = d3.scale.linear()
|
||||
// this.y = d3.scale.log().clamp([1e-7,1e7]).domain([1e-7, 100])
|
||||
.domain([-100, 100])
|
||||
.range([this.options.margin.top, this.options.height+this.options.margin.top]);
|
||||
|
||||
|
||||
// setup axis
|
||||
this.xAxis = d3.svg.axis().scale(this.x).orient("top");
|
||||
this.vis
|
||||
.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate("+0+"," + this.options.margin.top + ")")
|
||||
.call(this.xAxis)
|
||||
// LABEL
|
||||
.append("text")
|
||||
.attr("class", "label")
|
||||
.attr("x", "20em") // y margin
|
||||
.attr("y", "1em") // xmargin
|
||||
.attr("dx", ".71em") // additional shift
|
||||
.style("text-anchor", "start")
|
||||
.text("Distance (m)");
|
||||
|
||||
|
||||
this.yAxis = d3.svg.axis().scale(this.y).orient("left");
|
||||
this.vis.append("g")
|
||||
.attr("class", "y axis")
|
||||
.attr("transform", "translate(" + this.options.margin.left + ","+0+")")
|
||||
.call(this.yAxis)
|
||||
.append("text")
|
||||
.attr("class", "label")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("x", "-10em")
|
||||
.attr("y", "1em")
|
||||
.style("text-anchor", "end")
|
||||
.text("Error (m)");
|
||||
|
||||
this.series = this.vis.append("g")
|
||||
.attr("class","Paired series")
|
||||
}
|
||||
ErrorPlot.prototype.plot = function(x,y){
|
||||
var self = this
|
||||
|
||||
var data = x.map((n,i)=>({
|
||||
x:n,
|
||||
y:y[i],
|
||||
i:i
|
||||
}))
|
||||
this.data.push(data)
|
||||
|
||||
var allData = _.flatten(this.data)
|
||||
var yMin = _.min(allData.map(r=>r.y))
|
||||
var yMax = _.max(allData.map(r=>r.y))
|
||||
var yMax2 = _.max([-yMin,yMax])
|
||||
|
||||
// update y axis https://gist.github.com/phoebebright/3098488
|
||||
this.y.domain([-yMax2,yMax2])
|
||||
this.vis.select(".y.axis")
|
||||
.transition().duration(1500).ease("sin-in-out") // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
|
||||
.call(this.yAxis);
|
||||
|
||||
var i = this.series[0][0].children.length
|
||||
var series = this.series.append("g")
|
||||
.attr("id",`dots-${i}`)
|
||||
.attr("class",`dots dots-${i}`)
|
||||
this.series.selectAll(".dot")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("class", "dot")
|
||||
.attr("r", 3.5)
|
||||
.attr("x", d=>self.x(d.x))
|
||||
.attr("y", d=>self.y(d.y))
|
||||
.attr("cx", d=>self.x(d.x))
|
||||
.attr("cy", d=>self.y(d.y))
|
||||
.attr("class",`q${i}-12`)
|
||||
// .style("fill", function(d) { return color(d.species); });
|
||||
|
||||
}
|
||||
ErrorPlot.prototype.legend = function(x,y){
|
||||
|
||||
|
||||
}
|
||||
ErrorPlot.prototype.clear = function(x,y){
|
||||
this.vis.selectAll(".dot").clear()
|
||||
this.data=[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// function errorPlot(x,y,id, options){
|
||||
//
|
||||
// options=_.defaultsDeep(options,{
|
||||
// width: 2721/2, // must preserve aspect ratio
|
||||
// height: 701/2,
|
||||
// margin: {
|
||||
// left:70,
|
||||
// right:130
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// // format dc data and groups
|
||||
// var data = x.map((n,i)=>({
|
||||
// x:n,
|
||||
// y:y[i],
|
||||
// i:i
|
||||
// }))
|
||||
// var ndx = crossfilter(data);
|
||||
// var errorDim = ndx.dimension(d=>([d.x,d.y]))
|
||||
// var errorGroup = errorDim.group()
|
||||
//
|
||||
// // work out axis lengths
|
||||
// var xMax = _.max(data.map(r=>r.x))
|
||||
// var yMin = _.min(data.map(r=>r.y))
|
||||
// var yMax = _.min(data.map(r=>r.y))
|
||||
// var yMax2=_.max([yMax,-yMin]) || 100
|
||||
//
|
||||
// // this is a HACK to render an empty graph when y values are null
|
||||
// var symbolSize = yMin==undefined?0:8
|
||||
//
|
||||
// // http://dc-js.github.io/dc.js/examples/scatter.html
|
||||
// errorChart = dc.scatterPlot(id);
|
||||
// errorChart
|
||||
// .width(options.width+options.margin.left+options.margin.right)
|
||||
// .height(options.height/2)
|
||||
// .x(d3.scale.linear().domain([0, xMax]))
|
||||
// .y(d3.scale.linear().domain([-yMax2,yMax2]))
|
||||
// .brushOn(false)
|
||||
// .symbolSize(symbolSize)
|
||||
// .clipPadding(10)
|
||||
// .yAxisLabel("Error (m)")
|
||||
// .xAxisLabel("Length (m)")
|
||||
// .dimension(errorDim)
|
||||
// .group(errorGroup);
|
||||
// errorChart.margins().left=options.margin.left
|
||||
// errorChart.margins().right=options.margin.right
|
||||
// errorChart.margins().top=0
|
||||
// errorChart.margins().bottom=0
|
||||
//
|
||||
// errorChart.render();
|
||||
// }
|
||||
@@ -0,0 +1,80 @@
|
||||
// list of horizons by length
|
||||
// var horizons_whitelist = ['H089-UC', 'Fault1', 'H042', 'H033', 'Fault2', 'H046', 'H038', 'H039', 'H045', 'H064', 'H047', 'H037', 'H048', 'H053', 'H050', 'H051', 'H052', 'H044', 'H034', 'H032', 'H049', 'H065', 'H068', 'H069', 'H070', 'Fault3', 'H029', 'H035', 'H031', 'H036', 'H030', 'H040', 'H077', 'H027', 'H072', 'H078', 'H079', 'H003', 'H028', 'H088_Top_Salt', 'H025', 'H021', 'H061', 'H023', 'H022', 'H080', 'H062', 'H024', 'H013', 'H020', 'H019', 'H017', 'H054', 'H056', 'H015', 'H057', 'H016', 'H018', 'H055', 'H176', 'H059', 'H058', 'H060', 'H175', 'H014', 'H012', 'H026', 'H071', 'H011', 'H063', 'H073', 'H067', 'H081', 'H174', 'H066', 'H182', 'H007', 'H183', 'H082', 'H074', 'H008', 'H173', 'H009', 'H010', 'H181', 'H172', 'H083', 'H184', 'H180', 'H075', 'H084', 'H179', 'H171', 'H005', 'H076', 'H185', 'H159', 'H170', 'H186', 'H169', 'H160-UC', 'H157', 'H168', 'H158', 'H156', 'H085', 'H187', 'H041_levee', 'H006', 'H086', 'H188', 'H155', 'H087', 'H154', 'H153', 'H152', 'H189', 'H151', 'H177', 'H150', 'H043_channel', 'H162', 'H167', 'H004', 'H149', 'H148', 'H190', 'H178', 'H147', 'H139', 'H142', 'H161', 'H055-056_noAIp', 'H144', 'H143', 'H145', 'H140', 'H138', 'H166', 'H141', 'H163', 'H137', 'H146', 'H128', 'H136', 'H130', 'H134', 'H191', 'H135', 'H129', 'H165', 'H132', 'H133', 'H164', 'H121', 'H131', 'H124', 'H127', 'H125', 'H126', 'H123', 'H122', 'H119', 'H093', 'H120', 'H101', 'H102', 'H104', 'H103', 'H097', 'H092', 'H115', 'H107', 'H105', 'H118', 'H109', 'H094', 'H108', 'H112', 'H095', 'H106', 'H099', 'H113', 'H096', 'H116', 'H111', 'H098', 'H100', 'H110', 'H117', 'H091', 'H114', 'H090', 'H050-051_OIL', 'H002', 'H052-053_OIL', 'H001-wb', 'H048-049_OIL', 'H070-071_OIL']
|
||||
|
||||
/**
|
||||
* Interp one array onto another like scipys interp1d
|
||||
* @param {Array} a - array of objects e.g. [{x:1,y:2},{x:2,y:1.5}]
|
||||
* @return {function} - an interpolation function: yNew = f(xNew)
|
||||
*/
|
||||
function interp1d(a){
|
||||
var aSorted = _.sortBy(a, ar=>ar.x)
|
||||
aSorted = _.uniqBy(aSorted,ar=>ar.x)
|
||||
|
||||
/** yNew = f(xNew) **/
|
||||
return function(xNew){
|
||||
return xNew.map(xn => {
|
||||
// find item before and after
|
||||
var i = _.sortedIndexBy(aSorted, {x:xn}, a=>a.x)
|
||||
i = _.clamp(i,1,aSorted.length-1)
|
||||
var a0 = aSorted[i-1]
|
||||
var a1 = aSorted[i]
|
||||
// work out where xn fits between a0 and a1 as a fraction
|
||||
var xnFrac = (xn-a0.x)/(a1.x-a0.x)
|
||||
return d3.interpolateNumber(a0.y,a1.y)(xnFrac)
|
||||
})
|
||||
}
|
||||
}
|
||||
// TODO test
|
||||
var interp1dTestInput = [{"x":864.6820640353673,"y":1978.4236804564907}, {"x":864.6820640353673,"y":1978.4236804564907},{"x":3238.809413832805,"y":1598.965763195435},{"x":6562.587436618545,"y":2118.2239657631953},{"x":6562.587436618545,"y":2118.2239657631953},{"x":9586.475712987527,"y":1239.4793152639086}]
|
||||
var result = interp1d(interp1dTestInput)([-1,100,3333,90000])
|
||||
var idealResult = [2116.786061504474, 2100.6431828814566, 1613.6807153181974, -22128.763105338083]
|
||||
console.assert(_.isEqual(result, idealResult),'interp1d should work for predefined inputs')
|
||||
|
||||
|
||||
var dataPath = 'data/Marmousi2';
|
||||
d3.json(dataPath+'/metadata.json', function(error, metadata){
|
||||
if (error) console.error(error)
|
||||
d3.json(dataPath+'/'+metadata.horizons, function(error, horizons){
|
||||
if (error) console.error(error)
|
||||
|
||||
|
||||
|
||||
// TODO legend, well labels, show joints,
|
||||
|
||||
var errorChart
|
||||
var seismicPlot
|
||||
var filtered_horizons = _.filter(horizons,horizon=>['H037'].includes(horizon.name))
|
||||
|
||||
$(document).ready(function () {
|
||||
seismicPlot = new SeismicPlot({
|
||||
model: dataPath+'/'+metadata.model,
|
||||
seismic: dataPath+'/'+metadata.seismic,
|
||||
horizons: filtered_horizons,
|
||||
onmousedown: (obj)=>{
|
||||
document.querySelectorAll('#interp')[0].innerText = JSON.stringify(seismicPlot.interp, '\n')
|
||||
}
|
||||
})
|
||||
seismicPlot.init()
|
||||
seismicPlot.showWells(5)
|
||||
|
||||
d3.select('#undo').on('click', ()=>seismicPlot.undo())
|
||||
d3.select('#reset').on('click', ()=>seismicPlot.reset())
|
||||
d3.select('#toggleModel').on('click', ()=>seismicPlot.toggleModel())
|
||||
d3.select('#Finish').on('click', ()=>{
|
||||
var errors=seismicPlot.finish()
|
||||
var meanError = _.mean(_.flatten(errors.map(r=>r.y)))
|
||||
var formatter = d3.format('.2f')
|
||||
document.querySelectorAll('#score')[0].innerText = ''+formatter(meanError)
|
||||
errors.map(r=>errorPlot.plot(r.x,r.y,r.name))
|
||||
})
|
||||
|
||||
// make empty error plot
|
||||
var x = _.flatten(_.values(seismicPlot.options.horizons[0].data)).reverse().map(r=>r.x)
|
||||
var errorPlot = new ErrorPlot('#interp-error-chart',seismicPlot.options)
|
||||
errorPlot.init()
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
||||
})})
|
||||
@@ -0,0 +1,363 @@
|
||||
|
||||
/**
|
||||
* Class to display a seismic line and let a user interp on it
|
||||
*/
|
||||
function SeismicPlot(options) {
|
||||
|
||||
this.options=_.defaultsDeep(options,{
|
||||
width: 2721/2, // must preserve aspect ratio
|
||||
height: 701/2,
|
||||
wellFractions: [0.1,0.9],
|
||||
extent:{
|
||||
xmin:0,
|
||||
xmax:17000,
|
||||
ymin:0,
|
||||
ymax:3500,
|
||||
},
|
||||
margin: {
|
||||
left:70,
|
||||
right:130,
|
||||
top:50,
|
||||
bottom:10
|
||||
},
|
||||
model: "data/MODEL_P-WAVE_VELOCITY_1.25m.png",
|
||||
seismic: 'data/SYNTHETIC.png',
|
||||
horizons: [],
|
||||
onmousedown: function(){}, // callback
|
||||
})
|
||||
|
||||
this.interp = []
|
||||
}
|
||||
SeismicPlot.prototype.init = function (arguments) {
|
||||
|
||||
var self = this;
|
||||
this.totalWidth = this.options.margin.left+this.options.width+this.options.margin.right
|
||||
this.totalHeight = this.options.margin.top+this.options.height+this.options.margin.bottom
|
||||
this.vis = d3.select("#svg").append("svg")
|
||||
.attr("width", this.totalWidth)
|
||||
.attr("height", this.totalHeight)
|
||||
.on("mousedown", function(){
|
||||
self.mousedown(this)
|
||||
return self.options.onmousedown(this)
|
||||
});
|
||||
|
||||
this.active_interp = 0
|
||||
|
||||
|
||||
// this.formatter = d3.format('.2f')
|
||||
|
||||
|
||||
// create x and y transforms
|
||||
var {xmin, xmax, ymin, ymax} = this.options.extent
|
||||
this.x = d3.scale.linear()
|
||||
.domain([xmin, xmax])
|
||||
.range([this.options.margin.left, this.options.width+this.options.margin.left]);
|
||||
|
||||
this.y = d3.scale.linear()
|
||||
.domain([ymin, ymax])
|
||||
.range([this.options.margin.top, this.options.height+this.options.margin.top]);
|
||||
|
||||
|
||||
// line function
|
||||
this.linef = d3.svg.line()
|
||||
.x(d => this.x(d.x))
|
||||
.y(d => this.y(d.y));
|
||||
|
||||
// load images
|
||||
this.seismicImage = this.vis.append("image")
|
||||
.attr("xlink:href", this.options.seismic)
|
||||
.attr("id", 'image')
|
||||
.attr("x",this.options.margin.left)
|
||||
.attr("y",this.options.margin.top)
|
||||
.attr("width", this.options.width)
|
||||
.attr("height", this.options.height)
|
||||
.attr("preserveAspectRatio","xMinYMin meet");
|
||||
|
||||
|
||||
this.modelImage = this.vis.append("image")
|
||||
.attr("xlink:href", this.options.model)
|
||||
.attr("id", 'image')
|
||||
.attr("x",this.options.margin.left)
|
||||
.attr("y",this.options.margin.top)
|
||||
.attr("width", this.options.width)
|
||||
.attr("height", this.options.height)
|
||||
.attr("style", 'display:none')
|
||||
.attr("preserveAspectRatio","xMinYMin meet");
|
||||
|
||||
// setup axis
|
||||
this.xAxis = d3.svg.axis().scale(this.x).orient("top");
|
||||
this.vis
|
||||
.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate("+0+"," + this.options.margin.top + ")")
|
||||
.call(this.xAxis)
|
||||
// LABEL
|
||||
.append("text")
|
||||
.attr("class", "label")
|
||||
.attr("x", "20em") // y margin
|
||||
.attr("y", "1em") // xmargin
|
||||
.attr("dx", ".71em") // additional shift
|
||||
.style("text-anchor", "start")
|
||||
.text("Distance (m)");
|
||||
|
||||
|
||||
this.yAxis = d3.svg.axis().scale(this.y).orient("left");
|
||||
this.vis.append("g")
|
||||
.attr("class", "y axis")
|
||||
.attr("transform", "translate(" + this.options.margin.left + ","+0+")")
|
||||
.call(this.yAxis)
|
||||
.append("text")
|
||||
.attr("class", "label")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("x", "-10em")
|
||||
.attr("y", "1em")
|
||||
.style("text-anchor", "end")
|
||||
.text("Depth (m)");
|
||||
|
||||
// add container groups
|
||||
this.wellsGroup = this.vis.append("g")
|
||||
.attr("id", `wells`)
|
||||
|
||||
this.linesGroup = this.vis.append("g")
|
||||
.attr("id", `lines`)
|
||||
|
||||
this.legendGroup = this.vis.append("g")
|
||||
.attr("id","legend")
|
||||
.attr("transform", "translate("+(self.options.margin.left+self.options.width)+","+(this.options.margin.top+70)+")"); // place legend
|
||||
|
||||
// button, this should be done outside this class
|
||||
// d3.select('#undo').on('click', ()=>this.undo())
|
||||
// d3.select('#reset').on('click', ()=>this.reset())
|
||||
// d3.select('#toggleModel').on('click', ()=>this.toggleModel())
|
||||
// d3.select('#Finish').on('click', ()=>this.finish())
|
||||
|
||||
this.legend()
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.legend = function () {
|
||||
var data = _.concat(
|
||||
this.options.horizons.map((h,i)=>({
|
||||
text:'horizon '+h.name,
|
||||
class:`line interp q${i}-12`
|
||||
})),
|
||||
this.options.horizons.map((h,i)=>({
|
||||
text:'well top '+h.name,
|
||||
class:`line well-top q${i}-12`
|
||||
})),
|
||||
this.options.horizons.map((h,i)=>({
|
||||
text:'answer '+h.name,
|
||||
class:`line ideal-horizon q${i}-12`
|
||||
}))
|
||||
)
|
||||
|
||||
// var width = 200 // this.options.width+this.options.margin.left
|
||||
var dh = 20 // height of each legend element
|
||||
var padding = 1
|
||||
var height = this.options.margin.top
|
||||
var legend = this.legendGroup.selectAll(".legend")
|
||||
.data(data)
|
||||
.enter().append("g")
|
||||
.attr("class", "legend")
|
||||
.attr("transform", function(d, i) { return "translate(0," + i * dh + ")"; });
|
||||
|
||||
// add white background
|
||||
legend.append("rect")
|
||||
.attr("x", - dh)
|
||||
.attr("width", dh)
|
||||
.attr("height", dh)
|
||||
.style("fill", 'white');
|
||||
|
||||
legend.append("path")
|
||||
// .attr("d", linef([{x:width,y:0},{x:width,y:10}]))
|
||||
.attr("d", d3.svg.line()([[-dh,dh/2],[0,dh/2]]))
|
||||
.attr("dy","0.5em")
|
||||
.attr("dx","0.5em")
|
||||
.attr("class", d=>d.class)
|
||||
|
||||
legend.append("text")
|
||||
.attr("x", - 24)
|
||||
.attr("y", 9)
|
||||
.attr("dy", ".35em")
|
||||
.style("text-anchor", "end")
|
||||
.text(function(d) { return d.text; });
|
||||
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.updateLine = function () {
|
||||
|
||||
var i = this.active_interp
|
||||
|
||||
// make sure each line has a class
|
||||
var path = this.vis.select(`#line-${i}`)
|
||||
if (!path.node()){
|
||||
path = this.linesGroup.append("path")
|
||||
.attr("class", `line interp q${i}-12`)
|
||||
.attr("id", `line-${i}`)
|
||||
}
|
||||
|
||||
path
|
||||
.transition()
|
||||
.attr("d", this.linef(this.interp[i]));
|
||||
|
||||
// this.interpBox.innerText = JSON.stringify(this.interp, '\n')
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.mousedown = function(context) {
|
||||
var m = d3.mouse(context);
|
||||
if (!this.interp[this.active_interp]) this.interp[this.active_interp]=[]
|
||||
this.interp[this.active_interp].push({
|
||||
x: this.x.invert(m[0]),
|
||||
y: this.y.invert(m[1])
|
||||
})
|
||||
this.updateLine()
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.undo = function() {
|
||||
this.interp[this.active_interp].pop()
|
||||
this.updateLine()
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.reset = function() {
|
||||
this.interp = []
|
||||
this.updateLine()
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.toggleModel = function(on) {
|
||||
if ((on!==undefined && on )|| (on===undefined && this.modelImage.attr("style") == 'display:none'))
|
||||
this.modelImage.attr("style", '')
|
||||
else
|
||||
this.modelImage.attr("style", 'display:none')
|
||||
|
||||
}
|
||||
SeismicPlot.prototype.finish = function(){
|
||||
// compare interp to ideal horizon, and show area between, turn on fill I guess
|
||||
|
||||
// TODO make interp into an array of horizons
|
||||
var errors = this.interp.map((interp,i)=>{
|
||||
var horizon = this.options.horizons[i] // HACK just use first for now
|
||||
var ideal = _.flatten(_.values(horizon.data)).reverse()
|
||||
var data = _.concat(ideal,this.interp[i])
|
||||
|
||||
// join your interp and the ideal to make a polygon, then display with fill
|
||||
var path = this.linesGroup.append("path")
|
||||
.attr("class", `line error q${i%11}-12`)
|
||||
.attr("id", `error-${i}`)
|
||||
.attr("d", this.linef(data));
|
||||
|
||||
var yPred = interp1d(interp)(ideal.map(r=>r.x))
|
||||
var yTrue = ideal.map(r=>r.y)
|
||||
|
||||
var yError = yTrue.map((n,i)=>yPred[i]-n)
|
||||
// TODO plot error, scatter plot and hist
|
||||
|
||||
// bin length
|
||||
var xLength = yPred.map((a,i)=>{
|
||||
var yErr = Math.abs(a-yTrue[i])
|
||||
|
||||
// half distance between left and right neigbours
|
||||
var i0= Math.max(i-1,0)
|
||||
var i1 = Math.min(i+1,yTrue.length-1)
|
||||
var xLength = Math.abs(ideal[i1].x-ideal[i0].x)/2
|
||||
|
||||
return xLength
|
||||
})
|
||||
|
||||
// weight error by bin
|
||||
var error = _.sum(xLength.map((xLen,i)=>{
|
||||
var yErr = yError[i]
|
||||
return yErr*xLen
|
||||
}))/_.sum(xLength)
|
||||
|
||||
var xError = ideal.map(r=>r.x)
|
||||
return {x:xError,y:yError,name:horizon.name}
|
||||
})
|
||||
|
||||
// this.scoreBox.innerText = ''+this.formatter(_(errors).mean())
|
||||
this.showHorizons() // show ideal
|
||||
$('#toggleModel').attr('disabled',false) // let us see model
|
||||
this.toggleModel(true)
|
||||
return errors
|
||||
}
|
||||
|
||||
SeismicPlot.prototype.showWells = function(n){
|
||||
// show horizons at traces at 10 and 90%
|
||||
// TODO move traces to options
|
||||
var {xmin, xmax, ymin, ymax} = this.options.extent
|
||||
let traces = this.options.wellFractions.map(f=>(xmax-xmin)*f)
|
||||
|
||||
// TODO add rotated text at well position
|
||||
|
||||
// draw lines at each trace
|
||||
this.wells = traces.map((x0,i)=>{
|
||||
// group
|
||||
let wellGroup = this.wellsGroup.append("g").attr("class","well well-"+i)
|
||||
|
||||
// well trace
|
||||
let well = wellGroup.append("line")
|
||||
.attr("class", "well-trace")
|
||||
.attr("x1", this.x(x0)) //<<== change your code here
|
||||
.attr("y1", this.y(ymin))
|
||||
.attr("x2", this.x(x0)) //<<== and here
|
||||
.attr("y2", this.y(ymax))
|
||||
.style("stroke-width", 2)
|
||||
.style("stroke", "red")
|
||||
.style("fill", "none");
|
||||
|
||||
// label
|
||||
wellGroup
|
||||
.append("text")
|
||||
.attr("class", "label")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("x", -(this.y(ymin)+this.y(ymax))/2) // margin-top
|
||||
.attr("y", this.x(x0))
|
||||
.attr("dy", "-0.2em") // margin-left
|
||||
.style("text-anchor", "start")
|
||||
.text("Well "+i);
|
||||
|
||||
// draw horizons near well
|
||||
let cutoff = (xmax-xmin)*0.02 // 5%
|
||||
|
||||
this.options.horizons.map((horizon,j)=>{
|
||||
let data = _.flatten(_.values(horizon.data)) // join segments
|
||||
let segment = data.filter(d=>Math.abs(x0-d.x)<cutoff)
|
||||
|
||||
// get the x and y location nearest the well
|
||||
// plot line
|
||||
var path = this.linesGroup.append("path")
|
||||
.attr("class", `line well-top well-${i} top-${j} q${i%11}-12 `)
|
||||
.attr("id", `well-${i}-top-${j}`)
|
||||
.attr("d", this.linef(segment));
|
||||
|
||||
// add tooltip https://gist.github.com/ilyabo/1339996
|
||||
title=path.append("svg:title")
|
||||
.text("Well top: "+horizon.name)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show some horizons, n will limit it to n points in the first segment (as a interp prompt)
|
||||
* TODO make a fake well instead
|
||||
**/
|
||||
SeismicPlot.prototype.showHorizons = function(n){
|
||||
if (n===undefined) n=1E5
|
||||
|
||||
this.options.horizons.forEach((horizon,i)=>{
|
||||
var segments = Object.keys(horizon.data)
|
||||
if (n<1E5) segments=segments.slice(0,1)
|
||||
segments.forEach(segment=>{
|
||||
|
||||
// plot line
|
||||
var path = this.linesGroup.append("path")
|
||||
.attr("class", `line q${i%11}-12 ideal-horizon`)
|
||||
.attr("id", `ideal-horizon-${i}`)
|
||||
.attr("d", this.linef(horizon.data[segment].slice(0,n)));
|
||||
|
||||
// add tooltip https://gist.github.com/ilyabo/1339996
|
||||
title=path.append("svg:title")
|
||||
.text(horizon.name)
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user