const colors = {
RdBu: ["rgb(255, 13, 87)", "rgb(30, 136, 229)"],
GnPR: ["rgb(24, 196, 93)", "rgb(124, 82, 255)"],
CyPU: ["#0099C6", "#990099"],
PkYg: ["#DD4477", "#66AA00"],
DrDb: ["#B82E2E", "#316395"],
LpLb: ["#994499", "#22AA99"],
YlDp: ["#AAAA11", "#6633CC"],
OrId: ["#E67300", "#3E0099"]
}
window.forceplot = (function() {
var svgNS = 'http://www.w3.org/2000/svg';
var xhtmlNS = 'http://www.w3.org/1999/xhtml';
let view = {};
var layoutContainerID = "layoutContainer";
var minWidth = 100;
var minHeight = 100;
var ForcePlot = function () {
this._representation = null;
this._value = null;
this._table = null;
this._infoWrapperMinHeight = 80;
this._svgs = null;
this._colNamesToShapIndex = null;
}
ForcePlot.prototype.init = function(representation, value) {
// If no data is available, set error message.
if (!representation.table) {
d3.select('body')
.append('p')
.text('Error: No data available');
return;
}
//console.log(ReactDOM);
//console.log(_);
this._representation = representation;
this._value = value || {};
this._value.currentPage = value.currentPage || 1;
this._infoWrapperMinHeight = 80;
this._svgs = {};
this._table = new kt();
this._table.setDataTable(representation.table);
var repColNames = this._representation.table.spec.colNames;
this._colNames = repColNames;
this._colNamesToShapIndex = {};
repColNames.forEach((hmColName) => {
var indexOfShapCol = repColNames.indexOf("SHAP " + hmColName);
if (indexOfShapCol > -1){
this._colNamesToShapIndex[repColNames.indexOf(hmColName)] = indexOfShapCol;
}
this._colNames[repColNames.indexOf(hmColName)] = hmColName;
});
d3.select("html").style("width", "100%").style("height", "100%");
//d3.select("body").style("width", "100%").style("height", "100%");
/*var layoutContainer = body.append("div")
.attr("id", layoutContainerID)
.attr("class", "knime-layout-container")
.style("min-width", minWidth + "px")
.style("min-height", minHeight + "px");*/
//let l = function(React, ReactDOM, _) {
var container = document.createElementNS(xhtmlNS, 'div');
container.classList.add('knime-layout-container');
document.body.appendChild(container);
this.createForcePlot();
this.registerOneTimeEvents();
console.log("internal value ");
console.log(this._value)
console.log("KNIME value: ");
console.log(value);
//};
/*knimeService.loadConditionally(["react", "react-dom", "lodash"],
(arg) => l(arg[0], arg[1], arg[2]),
(err) => console.log("kinmeService failed to install " + err),
requirejs_config);*/
};
/**
* Create very basic pagination data from rows
* @param {Array} data
* @return {Object} pagination data
*/
ForcePlot.prototype.createPagination = function (data) {
if (!data) {
return { rows: data };
}
var pageSize = 1;
var pageCount = Math.ceil(data.length / pageSize);
// jump to page 1 if total number of pages exceeds current page
this._value.currentPage = this._value.currentPage <= pageCount ? this._value.currentPage : 1;
var pageRowEndIndex = pageSize * this._value.currentPage;
var pageRowStartIndex = pageSize * (this._value.currentPage - 1);
var rows = data.slice(pageRowStartIndex, pageRowEndIndex);
return {
totalRowCount: data.length,
rows: rows,
pageCount: pageCount,
pageRowEndIndex: pageRowEndIndex > data.length ? data.length : pageRowEndIndex,
pageRowStartIndex: pageRowStartIndex,
next: pageRowEndIndex < data.length ? this._value.currentPage + 1 : false,
prev: pageRowStartIndex > 0 ? this._value.currentPage - 1 : false
};
};
/**
* Create intervals from the pagination data to limit the amount of links shown
* @param {Object} pagination data
* @return {Object} pagination intervals
*/
ForcePlot.prototype.createPaginationIntervals = function(pagination) {
var delta = 2; // number of pages displayed left and right to "center"
var left = this._value.currentPage - delta;
var right = this._value.currentPage + delta;
var range = [];
var paginationRange = [];
var curPage;
for (var i = 1; i <= pagination.pageCount; i++) {
if (i === 1 || i === pagination.pageCount || (left <= i && i <= right)) {
range.push(i);
}
}
range.forEach(function (page) {
if (curPage) {
if (page - curPage !== 1) {
paginationRange.push('...');
}
}
paginationRange.push(page);
curPage = page;
});
return {
prev: pagination.prev,
next: pagination.next,
pages: paginationRange
};
};
ForcePlot.prototype.getPaginationHtml = function(pagination) {
var paginationRange = this.createPaginationIntervals(pagination);
var self = this;
if (paginationRange.pages.length <= 1) {
return '';
}
var html = '
';
return html;
};
ForcePlot.prototype.drawMetaInfo = function(paginationData) {
var displayedRows = '';
var paginationHtml = '';
// Flex containers need this so the pagination is centered.
var emptyBlock = '';
//if (this._representation.enablePaging) {
paginationHtml += this.getPaginationHtml(paginationData);
displayedRows +=
'Showing entry ' +
(paginationData.totalRowCount > 1
? paginationData.pageRowStartIndex + 1
: paginationData.totalRowCount) +
' of ' +
paginationData.totalRowCount +
' entries
';
//}
var infoWrapper = document.body.querySelector('.info-wrapper');
infoWrapper.innerHTML = displayedRows + paginationHtml + emptyBlock;
infoWrapper.style.minHeight = this._infoWrapperMinHeight + 'px';
};
ForcePlot.prototype.registerEvents = function () {
var self = this;
var pagination = document.body.querySelector('.pagination');
console.log("Add events to pagination" + pagination);
if (pagination) {
document.body.querySelector('.pagination').addEventListener('click', function (e) {
if (e.target.tagName === 'A') {
var pageNumber = parseInt(e.target.getAttribute('href').substr(1), 10);
self._value.currentPage = pageNumber;
self.createForcePlot();
}
});
}
// Events for the svg are native js event listeners not
// d3 event listeners for better performance
/*var domWrapper = document.querySelector('.knime-svg-container svg .transformer');
// Highlight mouseover cell and show tooltip
domWrapper.addEventListener('mouseover', function (e) {
domWrapper.addEventListener('mousemove', self.onMousemove.bind(self));
});
domWrapper.addEventListener('mouseout', function (e) {
self.hideTooltip();
domWrapper.removeEventListener('mouseover', self.onMousemove.bind(self));
});
*/
};
ForcePlot.prototype.getComponentValue = () => {
/*console.log("GetComponentValue");
let i = 0;
for(var svg in this._svgs){
console.log(svg);
this._value.forcePlotsSVG[i] = svg;
++i;
}
console.log(this._value);*/
return this._value;
};
ForcePlot.prototype.validate = function () {
return true;
};
ForcePlot.prototype.getSVG = function () {
var svgElement = d3.select('.additiveForceVizSVG').node();
knimeService.inlineSvgStyles(svgElement);
return (new XMLSerializer()).serializeToString(svgElement);
};
ForcePlot.prototype.getSVG = function (nr) {
console.log(d3.select('.additiveForceVizSVG' + nr).node());
var svgElement = d3.select('.additiveForceVizSVG' + nr).node();
knimeService.inlineSvgStyles(svgElement);
return (new XMLSerializer()).serializeToString(svgElement);
};
ForcePlot.prototype.drawSvgRow = function (row, rowIndex) {
var self = this;
const exampleData = {
outNames: ["Probability of flower"],
baseValue: self._table.getCell(row.rowKey, "Bias"),
link: "identity",
features: [],
number: rowIndex
};
row.data.forEach(function (value, currentIndex) {
if (typeof self._colNamesToShapIndex[currentIndex] === 'undefined') {
return;
}
var feature = {
name: self._colNames[currentIndex],
effect: self._table.getCell(row.rowKey, "SHAP " + self._colNames[currentIndex]),
value: self._table.getCell(row.rowKey, self._colNames[currentIndex])
}
exampleData.features.push(feature);
});
console.log(exampleData);
return exampleData;
};
ForcePlot.prototype.registerOneTimeEvents = function () {
var self = this;
window.addEventListener('resize', function () {
self.reset();
self.createForcePlot();
});
};
ForcePlot.prototype.createForcePlot = function() {
var data = this._table.getRows();
let body = document.getElementsByTagName("body")[0];
const title = `Force plot
`;
const infoWrapper = ``;
var plots = ``;
for(let j = 0; j < data.length; j++){
plots += ``;
}
body.innerHTML = title + plots + infoWrapper;
// Meta info
var paginationData = this.createPagination(data);
this.drawMetaInfo(paginationData);
if (!this._representation.appendImagesToTable){
var svgRow = this.drawSvgRow(paginationData.rows[0], paginationData.pageRowStartIndex);
}
const select = d3.select;
const scaleLinear = d3.scaleLinear;
const format = d3.format;
const axisBottom = d3.axisBottom;
const line = d3.line;
const hsl = d3.hsl;
const sortBy = _.sortBy;
const map = _.map;
const each = _.each;
const sum = _.sum;
const filter = _.filter;
const findIndex = _.findIndex;
const debounce = _.debounce;
class AdditiveForceVisualizer extends React.Component {
constructor(props) {
super(props);
console.log(props);
window.lastAdditiveForceVisualizer = this;
this.effectFormat = format(".2");
this.redraw = debounce(() => this.draw(), 500);
}
componentDidMount() {
// create our permanent elements
this.mainGroup = this.svg.append("g");
this.axisElement = this.mainGroup
.append("g")
.attr("transform", "translate(0,35)")
.attr("class", "force-bar-axis");
this.onTopGroup = this.svg.append("g");
this.baseValueTitle = this.svg.append("text");
this.joinPointLine = this.svg.append("line");
this.joinPointLabelOutline = this.svg.append("text");
this.joinPointLabel = this.svg.append("text");
this.joinPointTitleLeft = this.svg.append("text");
this.joinPointTitleLeftArrow = this.svg.append("text");
this.joinPointTitle = this.svg.append("text");
this.joinPointTitleRightArrow = this.svg.append("text");
this.joinPointTitleRight = this.svg.append("text");
// Define the tooltip objects
this.hoverLabelBacking = this.svg.append("text")
.attr("x", 10)
.attr("y", 20)
.attr("text-anchor", "middle")
.attr("font-size", 12)
.attr("stroke", "#fff")
.attr("fill", "#fff")
.attr("stroke-width", "4")
.attr("stroke-linejoin", "round")
.text("")
.on("mouseover", () => {
this.hoverLabel.attr("opacity", 1);
this.hoverLabelBacking.attr("opacity", 1);
})
.on("mouseout", () => {
this.hoverLabel.attr("opacity", 0);
this.hoverLabelBacking.attr("opacity", 0);
});
this.hoverLabel = this.svg.append("text")
.attr("x", 10)
.attr("y", 20)
.attr("text-anchor", "middle")
.attr("font-size", 12)
.attr("fill", "#0f0")
.text("")
.on("mouseover", () => {
this.hoverLabel.attr("opacity", 1);
this.hoverLabelBacking.attr("opacity", 1);
})
.on("mouseout", () => {
this.hoverLabel.attr("opacity", 0);
this.hoverLabelBacking.attr("opacity", 0);
});
// Create our colors and color gradients
// Verify custom color map
let plot_colors=colors.RdBu;
if (typeof this.props.plot_cmap === "string")
{
if (!(this.props.plot_cmap in colors))
{
console.log("Invalid color map name, reverting to default.");
plot_colors=colors.RdBu;
}
else
{
plot_colors = colors[this.props.plot_cmap]
}
}
else if (Array.isArray(this.props.plot_cmap)){
plot_colors = this.props.plot_cmap
}
this.colors = plot_colors.map(x => hsl(x));
this.brighterColors = [1.45, 1.6].map((v, i) => this.colors[i].brighter(v));
this.colors.map((c, i) => {
let grad = this.svg
.append("linearGradient")
.attr("id", "linear-grad-" + i)
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "100%");
grad
.append("stop")
.attr("offset", "0%")
.attr("stop-color", c)
.attr("stop-opacity", 0.6);
grad
.append("stop")
.attr("offset", "100%")
.attr("stop-color", c)
.attr("stop-opacity", 0);
let grad2 = this.svg
.append("linearGradient")
.attr("id", "linear-backgrad-" + i)
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "100%");
grad2
.append("stop")
.attr("offset", "0%")
.attr("stop-color", c)
.attr("stop-opacity", 0.5);
grad2
.append("stop")
.attr("offset", "100%")
.attr("stop-color", c)
.attr("stop-opacity", 0);
});
// create our x axis
this.tickFormat = format(",.4");
this.scaleCentered = scaleLinear();
this.axis = axisBottom()
.scale(this.scaleCentered)
.tickSizeInner(4)
.tickSizeOuter(0)
.tickFormat(d => this.tickFormat(this.invLinkFunction(d)))
.tickPadding(-18);
// draw and then listen for resize events
this.draw();
window.addEventListener("resize", this.redraw);
window.setTimeout(this.redraw, 50); // re-draw after interface has updated
}
componentDidUpdate() {
this.draw();
}
draw() {
// copy the feature names onto the features
each(this.props.featureNames, (n, i) => {
if (this.props.features[i]) this.props.features[i].name = n;
});
// create our link function
if (this.props.link === "identity") {
this.invLinkFunction = x => this.props.baseValue + x;
} else if (this.props.link === "logit") {
this.invLinkFunction = x =>
1 / (1 + Math.exp(-(this.props.baseValue + x))); // logistic is inverse of logit
} else {
console.log("ERROR: Unrecognized link function: ", this.props.link);
}
// Set the dimensions of the plot
let width = this.svg.node().parentNode.offsetWidth;
if (width == 0) return setTimeout(() => this.draw(this.props), 500);
this.svg.style("height", 150 + "px");
this.svg.style("width", width + "px");
let topOffset = 50;
let data = sortBy(this.props.features, x => -1 / (x.effect + 1e-10));
let totalEffect = sum(map(data, x => Math.abs(x.effect)));
let totalPosEffects =
sum(map(filter(data, x => x.effect > 0), x => x.effect)) || 0;
let totalNegEffects =
sum(map(filter(data, x => x.effect < 0), x => -x.effect)) || 0;
this.domainSize = Math.max(totalPosEffects, totalNegEffects) * 3;
let scale = scaleLinear()
.domain([0, this.domainSize])
.range([0, width]);
let scaleOffset = width / 2 - scale(totalNegEffects);
this.scaleCentered
.domain([-this.domainSize / 2, this.domainSize / 2])
.range([0, width])
.clamp(true);
this.axisElement
.attr("transform", "translate(0," + topOffset + ")")
.call(this.axis);
// calculate the position of the join point between positive and negative effects
// and also the positions of each feature effect block
let pos = 0,
i,
joinPoint,
joinPointIndex;
for (i = 0; i < data.length; ++i) {
data[i].x = pos;
if (data[i].effect < 0 && joinPoint === undefined) {
joinPoint = pos;
joinPointIndex = i;
}
pos += Math.abs(data[i].effect);
}
if (joinPoint === undefined) {
joinPoint = pos;
joinPointIndex = i;
}
let lineFunction = line()
.x(d => d[0])
.y(d => d[1]);
let getLabel = d => {
if (d.value !== undefined && d.value !== null && d.value !== "") {
return (
d.name +
" = " +
(isNaN(d.value) ? d.value : this.tickFormat(d.value))
);
} else return d.name;
};
data = this.props.hideBars ? [] : data;
let blocks = this.mainGroup.selectAll(".force-bar-blocks").data(data);
blocks
.enter()
.append("path")
.attr("class", "force-bar-blocks")
.merge(blocks)
.attr("d", (d, i) => {
let x = scale(d.x) + scaleOffset;
let w = scale(Math.abs(d.effect));
let pointShiftStart = d.effect < 0 ? -4 : 4;
let pointShiftEnd = pointShiftStart;
if (i === joinPointIndex) pointShiftStart = 0;
if (i === joinPointIndex - 1) pointShiftEnd = 0;
return lineFunction([
[x, 6 + topOffset],
[x + w, 6 + topOffset],
[x + w + pointShiftEnd, 14.5 + topOffset],
[x + w, 23 + topOffset],
[x, 23 + topOffset],
[x + pointShiftStart, 14.5 + topOffset]
]);
})
.attr("fill", d => (d.effect > 0 ? this.colors[0] : this.colors[1]))
.on("mouseover", d => {
if (scale(Math.abs(d.effect)) < scale(totalEffect) / 50 ||
scale(Math.abs(d.effect)) < 10) {
let x = scale(d.x) + scaleOffset;
let w = scale(Math.abs(d.effect));
this.hoverLabel
.attr("opacity", 1)
.attr("x", x + w/2)
.attr("y", topOffset + 0.5)
.attr("fill", d.effect > 0 ? this.colors[0] : this.colors[1])
.text(getLabel(d));
this.hoverLabelBacking
.attr("opacity", 1)
.attr("x", x + w/2)
.attr("y", topOffset + 0.5)
.text(getLabel(d));
}
})
.on("mouseout", () => {
this.hoverLabel.attr("opacity", 0);
this.hoverLabelBacking.attr("opacity", 0);
});
blocks.exit().remove();
let filteredData = filter(data, d => {
return (
scale(Math.abs(d.effect)) > scale(totalEffect) / 50 &&
scale(Math.abs(d.effect)) > 10
);
});
let labels = this.onTopGroup
.selectAll(".force-bar-labels")
.data(filteredData);
labels.exit().remove();
labels = labels
.enter()
.append("text")
.attr("class", "force-bar-labels")
.attr("font-size", "12px")
.attr("y", 48 + topOffset)
.merge(labels)
.text(d => {
if (d.value !== undefined && d.value !== null && d.value !== "") {
return (
d.name +
" = " +
(isNaN(d.value) ? d.value : this.tickFormat(d.value))
);
} else return d.name;
})
.attr("fill", d => (d.effect > 0 ? this.colors[0] : this.colors[1]))
.attr("stroke", function(d) {
d.textWidth = Math.max(
this.getComputedTextLength(),
scale(Math.abs(d.effect)) - 10
);
d.innerTextWidth = this.getComputedTextLength();
return "none";
});
this.filteredData = filteredData;
// compute where the text labels should go
if (data.length > 0) {
pos = joinPoint + scale.invert(5);
for (let i = joinPointIndex; i < data.length; ++i) {
data[i].textx = pos;
pos += scale.invert(data[i].textWidth + 10);
}
pos = joinPoint - scale.invert(5);
for (let i = joinPointIndex - 1; i >= 0; --i) {
data[i].textx = pos;
pos -= scale.invert(data[i].textWidth + 10);
}
}
labels
.attr(
"x",
d =>
scale(d.textx) +
scaleOffset +
(d.effect > 0 ? -d.textWidth / 2 : d.textWidth / 2)
)
.attr("text-anchor", "middle"); //d => d.effect > 0 ? 'end' : 'start');
// Now that we know the text widths we further filter by what fits on the screen
filteredData = filter(filteredData, d => {
return (
scale(d.textx) + scaleOffset > this.props.labelMargin &&
scale(d.textx) + scaleOffset < width - this.props.labelMargin
);
});
this.filteredData2 = filteredData;
// Build an array with one extra feature added
let filteredDataPlusOne = filteredData.slice();
let ind = findIndex(data, filteredData[0]) - 1;
if (ind >= 0) filteredDataPlusOne.unshift(data[ind]);
let labelBacking = this.mainGroup
.selectAll(".force-bar-labelBacking")
.data(filteredData);
labelBacking
.enter()
.append("path")
.attr("class", "force-bar-labelBacking")
.attr("stroke", "none")
.attr("opacity", 0.2)
.merge(labelBacking)
.attr("d", d => {
return lineFunction([
[
scale(d.x) + scale(Math.abs(d.effect)) + scaleOffset,
23 + topOffset
],
[
(d.effect > 0 ? scale(d.textx) : scale(d.textx) + d.textWidth) +
scaleOffset +
5,
33 + topOffset
],
[
(d.effect > 0 ? scale(d.textx) : scale(d.textx) + d.textWidth) +
scaleOffset +
5,
54 + topOffset
],
[
(d.effect > 0 ? scale(d.textx) - d.textWidth : scale(d.textx)) +
scaleOffset -
5,
54 + topOffset
],
[
(d.effect > 0 ? scale(d.textx) - d.textWidth : scale(d.textx)) +
scaleOffset -
5,
33 + topOffset
],
[scale(d.x) + scaleOffset, 23 + topOffset]
]);
})
.attr("fill", d => `url(#linear-backgrad-${d.effect > 0 ? 0 : 1})`);
labelBacking.exit().remove();
let labelDividers = this.mainGroup
.selectAll(".force-bar-labelDividers")
.data(filteredData.slice(0, -1));
labelDividers
.enter()
.append("rect")
.attr("class", "force-bar-labelDividers")
.attr("height", "21px")
.attr("width", "1px")
.attr("y", 33 + topOffset)
.merge(labelDividers)
.attr(
"x",
d =>
(d.effect > 0 ? scale(d.textx) : scale(d.textx) + d.textWidth) +
scaleOffset +
4.5
)
.attr("fill", d => `url(#linear-grad-${d.effect > 0 ? 0 : 1})`);
labelDividers.exit().remove();
let labelLinks = this.mainGroup
.selectAll(".force-bar-labelLinks")
.data(filteredData.slice(0, -1));
labelLinks
.enter()
.append("line")
.attr("class", "force-bar-labelLinks")
.attr("y1", 23 + topOffset)
.attr("y2", 33 + topOffset)
.attr("stroke-opacity", 0.5)
.attr("stroke-width", 1)
.merge(labelLinks)
.attr("x1", d => scale(d.x) + scale(Math.abs(d.effect)) + scaleOffset)
.attr(
"x2",
d =>
(d.effect > 0 ? scale(d.textx) : scale(d.textx) + d.textWidth) +
scaleOffset +
5
)
.attr("stroke", d => (d.effect > 0 ? this.colors[0] : this.colors[1]));
labelLinks.exit().remove();
let blockDividers = this.mainGroup
.selectAll(".force-bar-blockDividers")
.data(data.slice(0, -1));
blockDividers
.enter()
.append("path")
.attr("class", "force-bar-blockDividers")
.attr("stroke-width", 2)
.attr("fill", "none")
.merge(blockDividers)
.attr("d", d => {
let pos = scale(d.x) + scale(Math.abs(d.effect)) + scaleOffset;
return lineFunction([
[pos, 6 + topOffset],
[pos + (d.effect < 0 ? -4 : 4), 14.5 + topOffset],
[pos, 23 + topOffset]
]);
})
.attr("stroke", (d, i) => {
if (joinPointIndex === i + 1 || Math.abs(d.effect) < 1e-8)
return "#rgba(0,0,0,0)";
else if (d.effect > 0) return this.brighterColors[0];
else return this.brighterColors[1];
});
blockDividers.exit().remove();
this.joinPointLine
.attr("x1", scale(joinPoint) + scaleOffset)
.attr("x2", scale(joinPoint) + scaleOffset)
.attr("y1", 0 + topOffset)
.attr("y2", 6 + topOffset)
.attr("stroke", "#F2F2F2")
.attr("stroke-width", 1)
.attr("opacity", 1);
this.joinPointLabelOutline
.attr("x", scale(joinPoint) + scaleOffset)
.attr("y", -5 + topOffset)
.attr("color", "#fff")
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("stroke", "#fff")
.attr("stroke-width", 6)
.text(format(",.2f")(this.invLinkFunction(joinPoint - totalNegEffects)))
.attr("opacity", 1);
console.log(
"joinPoint",
joinPoint,
scaleOffset,
topOffset,
totalNegEffects
);
this.joinPointLabel
.attr("x", scale(joinPoint) + scaleOffset)
.attr("y", -5 + topOffset)
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("fill", "#000")
.text(format(",.2f")(this.invLinkFunction(joinPoint - totalNegEffects)))
.attr("opacity", 1);
this.joinPointTitle
.attr("x", scale(joinPoint) + scaleOffset)
.attr("y", -22 + topOffset)
.attr("text-anchor", "middle")
.attr("font-size", "12")
.attr("fill", "#000")
.text(this.props.outNames[0])
.attr("opacity", 0.5);
if (!this.props.hideBars) {
this.joinPointTitleLeft
.attr("x", scale(joinPoint) + scaleOffset - 16)
.attr("y", -38 + topOffset)
.attr("text-anchor", "end")
.attr("font-size", "13")
.attr("fill", this.colors[0])
.text("higher")
.attr("opacity", 1.0);
this.joinPointTitleRight
.attr("x", scale(joinPoint) + scaleOffset + 16)
.attr("y", -38 + topOffset)
.attr("text-anchor", "start")
.attr("font-size", "13")
.attr("fill", this.colors[1])
.text("lower")
.attr("opacity", 1.0);
this.joinPointTitleLeftArrow
.attr("x", scale(joinPoint) + scaleOffset + 7)
.attr("y", -42 + topOffset)
.attr("text-anchor", "end")
.attr("font-size", "13")
.attr("fill", this.colors[0])
.text("→")
.attr("opacity", 1.0);
this.joinPointTitleRightArrow
.attr("x", scale(joinPoint) + scaleOffset - 7)
.attr("y", -36 + topOffset)
.attr("text-anchor", "start")
.attr("font-size", "13")
.attr("fill", this.colors[1])
.text("←")
.attr("opacity", 1.0);
}
if (!this.props.hideBaseValueLabel) {
this.baseValueTitle
.attr("x", this.scaleCentered(0))
.attr("y", -22 + topOffset)
.attr("text-anchor", "middle")
.attr("font-size", "12")
.attr("fill", "#000")
.text("base value")
.attr("opacity", 0.5);
}
}
componentWillUnmount() {
window.removeEventListener("resize", this.redraw);
}
render() {
const ref = x => this.svg = select(x);
const style = { userSelect: "none", display: "block", fontFamily: "arial", sansSerif: true};
const style2 = {
__html: `
.force-bar-axis path {
fill: none;
opacity: 0.4;
}
.force-bar-axis paths {
display: none;
}
.tick line {
stroke: #000;
stroke-width: 1px;
opacity: 0.4;
}
.tick text {
fill: #000;
opacity: 0.5;
font-size: 12px;
padding: 0px;
}`
};
const className = 'additiveForceVizSVG' + this.props.number;
return (React.createElement('svg', {className: className, ref: ref, style: style},
React.createElement('style', {dangerouslySetInnerHTML: style2})));
}
}
const exampleData = {
outNames: ["Probability of flower"],
baseValue: 0.2,
link: "identity",
features: [
{ name: "F1", effect: 0.6, value: 1 },
{ name: "F2", effect: -0.6, value: 1 },
{ name: "F3", effect: -0.2, value: 2 },
{ name: "F4", effect: 0.2, value: 0 }
]
// range(20).map(i => ({
// name: 'value_'+i,
// effect: random()-0.5
// }))
};
console.log(data)
if (this._representation.appendImagesToTable && (this._value.forcePlotsSVG[0] == null || this._value.forcePlotsSVG[0] == "")){
var i = 0;
for (var rowIndex in data){
let row = data[rowIndex];
console.log("Element " + i);
console.log(row);
console.time();
var el = React.createElement(AdditiveForceVisualizer, this.drawSvgRow(row, rowIndex), null);
ReactDOM.render(el, document.getElementById('additiveForceViz'+i));
//this._svgs[rowIndex] = this.getSVG(i);
this._value.forcePlotsSVG[rowIndex] = this.getSVG(i);
console.timeEnd();
++i;
}
}
/*const exampleDataJSON = JSON.stringify(exampleData);*/
if(!this._representation.appendImagesToTable){
const element = React.createElement(AdditiveForceVisualizer, svgRow, null);
ReactDOM.render(element, document.getElementById('additiveForceViz' + paginationData.pageRowStartIndex));
}
this.registerEvents();
}
return new ForcePlot();
})();