Skip to content
This repository has been archived by the owner on Dec 28, 2021. It is now read-only.

Commit

Permalink
Create prototype heatmap visualization. (#1438)
Browse files Browse the repository at this point in the history
  • Loading branch information
BinarySoftware authored Apr 4, 2021
1 parent 2b60985 commit 10e08be
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 2 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
will be lost. In this build we added notification in statusbar to signalize
that the connection was lost and IDE must be restarted. In future IDE will try
to automatically reconnect.
- [Visualization can be extended to the whole screen][1355] by selecting the
node and pressing space twice. To quit this view, press space again.
- [Visualization can be extended to the whole screen][1355] by selecting the
node and pressing space twice. To quit this view, press space again.
- [Visualization preview on output port hover.][1363] There is now a quick
preview for visualizations and error descriptions. Hovering a node output will
first show a tooltip with the type information and then after some time, will
Expand All @@ -32,6 +32,7 @@
different color, shape and size, all as defined by the data within the
`Table`.
- [Many small visual improvements.][1419] See the source issue for more details.
- [Added Heatmap visualization.][1438]

<br/>![Bug Fixes](/docs/assets/tags/bug_fixes.svg)

Expand Down Expand Up @@ -150,6 +151,7 @@ you can find their release notes
[1419]: https://github.com/enso-org/ide/pull/1419
[1413]: https://github.com/enso-org/ide/pull/1413
[1428]: https://github.com/enso-org/ide/pull/1428
[1438]: https://github.com/enso-org/ide/pull/1438

<br/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ pub fn histogram_visualization() -> visualization::java_script::FallibleDefiniti
visualization::java_script::Definition::new_builtin(source)
}

/// Return a `JavaScript` Heatmap visualization.
pub fn heatmap_visualization() -> visualization::java_script::FallibleDefinition {
let loading_scripts = include_str!("java_script/helpers/loading.js");
let number = include_str!("java_script/helpers/number.js");
let source = include_str!("java_script/heatmap.js");
let source = format!("{}{}{}",loading_scripts,number,source);

visualization::java_script::Definition::new_builtin(source)
}

/// Return a `JavaScript` Map visualization.
pub fn geo_map_visualization() -> visualization::java_script::FallibleDefinition {
let loading_scripts = include_str!("java_script/helpers/loading.js");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/** Heatmap Visualization. */
// TODO refactor this to avoid loading on startup. See issue #985 .
loadScript('https://d3js.org/d3.v4.min.js')
loadStyle('https://fontlibrary.org/face/dejavu-sans-mono')

/**
* A d3.js heatmap visualization.
*/
class Heatmap extends Visualization {
static inputType = 'Standard.Table.Data.Table.Table | Standard.Base.Data.Vector.Vector'
static label = 'Heatmap'

constructor(data) {
super(data)
this.setPreprocessorModule('Standard.Visualization.Table.Visualization')
this.setPreprocessorCode(`x -> here.prepare_visualization x 1000`)
}

onDataReceived(data) {
const { parsedData, isUpdate } = this.parseData(data)
if (!ok(parsedData)) {
console.error('Heatmap got invalid data: ' + data.toString())
}
this.updateState(parsedData, isUpdate)

if (!this.isInitialised()) {
this.initCanvas()
}

this.initHeatmap()
}

parseData(data) {
let parsedData
if (typeof data === 'string' || data instanceof String) {
parsedData = JSON.parse(data)
} else {
parsedData = data
}
const isUpdate = parsedData.update === 'diff'
return { parsedData, isUpdate }
}

/**
* Indicates whether this visualisation has been initialised.
*/
isInitialised() {
ok(this.svg)
}

/**
* Update the internal data and plot settings with the ones from the new incoming data.
* If no new settings/data have been provided the old ones will be kept.
*/
updateState(data, isUpdate) {
if (isUpdate) {
this._dataValues = ok(data.data) ? data.data : this.data
} else {
this._dataValues = this.extractValues(data)
}
}

extractValues(rawData) {
/// Note this is a workaround for #1006, we allow raw arrays and JSON objects to be consumed.
if (ok(rawData.data)) {
return rawData.data
} else if (Array.isArray(rawData)) {
return rawData
}
return []
}

/**
* Return vales to plot.
*/
data() {
return this._dataValues || {}
}

/**
* Return the layout measurements for the plot. This includes the outer dimensions of the
* drawing area as well as the inner dimensions of the plotting area and the margins.
*/
canvasDimensions() {
const width = this.dom.getAttributeNS(null, 'width')
const height = this.dom.getAttributeNS(null, 'height')
const margin = { top: 20, right: 20, bottom: 20, left: 25 }
return {
inner: {
width: width - margin.left - margin.right,
height: height - margin.top - margin.bottom,
},
outer: { width, height },
margin,
}
}

/**
* Creates HTML div element as container for plot.
*/
createOuterContainerWithStyle(width, height) {
const divElem = document.createElementNS(null, 'div')
divElem.setAttributeNS(null, 'class', 'vis-heatmap')
divElem.setAttributeNS(null, 'viewBox', 0 + ' ' + 0 + ' ' + width + ' ' + height)
divElem.setAttributeNS(null, 'width', '100%')
divElem.setAttributeNS(null, 'height', '100%')

return divElem
}

/**
* Initialise the drawing svg and related properties, e.g., canvas size and margins.
*/
initCanvas() {
while (this.dom.firstChild) {
this.dom.removeChild(this.dom.lastChild)
}

this.canvas = this.canvasDimensions()
const container = this.createOuterContainerWithStyle(
this.canvas.outer.width,
this.canvas.outer.height
)
this.dom.appendChild(container)

this.svg = d3
.select(container)
.append('svg')
.attr('width', this.canvas.outer.width)
.attr('height', this.canvas.outer.height)
.append('g')
.attr(
'transform',
'translate(' + this.canvas.margin.left + ',' + this.canvas.margin.top + ')'
)
}

/**
* Initialise the heatmap with the current data and settings.
*/
initHeatmap() {
let data = this.data()
// Labels of row and columns
let myGroups = d3.map(data[0], d => d).keys()
let myVars = d3.map(data[1], d => d).keys()
let labelStyle = 'font-family: DejaVuSansMonoBook; font-size: 10px;'

// Build X scales and axis:
let x = d3.scaleBand().range([0, this.canvas.inner.width]).domain(myGroups).padding(0.05)
this.svg
.append('g')
.attr('style', labelStyle)
.attr('transform', 'translate(0,' + this.canvas.inner.height + ')')
.call(d3.axisBottom(x).tickSize(0))
.select('.domain')
.remove()

// Build Y scales and axis:
let y = d3.scaleBand().range([this.canvas.inner.height, 0]).domain(myVars).padding(0.05)
this.svg
.append('g')
.attr('style', labelStyle)
.call(d3.axisLeft(y).tickSize(0))
.select('.domain')
.remove()

// Build color scale
let fill = d3
.scaleSequential()
.interpolator(d3.interpolateViridis)
.domain([0, d3.max(data[2], d => d)])

let indices = Array.from(Array(data[0].length).keys())

this.svg
.selectAll()
.data(indices, d => data[0][d] + ':' + data[1][d])
.enter()
.append('rect')
.attr('x', d => x(data[0][d]))
.attr('y', d => y(data[1][d]))
.attr('rx', 4)
.attr('ry', 4)
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.style('fill', d => fill(data[2][d]))
.style('stroke-width', 4)
.style('stroke', 'none')
.style('opacity', 0.8)
}

/**
* Sets size of the main parent DOM object.
*/
setSize(size) {
this.dom.setAttributeNS(null, 'width', size[0])
this.dom.setAttributeNS(null, 'height', size[1])
}
}

/**
* Checks if `t` has defined type and is not null.
*/
function ok(t) {
return t !== undefined && t !== null
}

return Heatmap
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ impl Registry {
self.add(builtin::visualization::native::RawText::definition());
self.try_add_java_script(builtin::visualization::java_script::scatter_plot_visualization());
self.try_add_java_script(builtin::visualization::java_script::histogram_visualization());
self.try_add_java_script(builtin::visualization::java_script::heatmap_visualization());
self.try_add_java_script(builtin::visualization::java_script::table_visualization());
self.try_add_java_script(builtin::visualization::java_script::sql_visualization());
self.try_add_java_script(builtin::visualization::java_script::geo_map_visualization());
Expand Down

0 comments on commit 10e08be

Please sign in to comment.