Unlock a world of possibilities! Login now and discover the exclusive benefits awaiting you.
Hi, I'm creating a custom visualisation using Plotly (a violin chart specifically).
I've got it all working pretty well, there's one thing I'm stuck on though. I'd like for when I click on the charts to create the greying effect native to Qlik charts.
I may suspect that could be a parameter that lives in there: const initialProperties. But I'm really not sure. Full code below. Thanks !
define([
"qlik",
"jquery",
"text!./plotly-violin.css",
"https://cdn.plot.ly/plotly-2.32.0.min.js"
], function (qlik, $, cssContent, Plotly) {
// inject CSS
$("<style>").html(cssContent).appendTo("head");
// ─── Property panel definition ────────────────────────────────────────────
const definition = {
type: "items",
component: "accordion",
items: {
dims: { uses: "dimensions", min: 2, max: 2 },
meas: { uses: "measures", min: 1, max: 1 },
points:{
type: "items",
label: "Points",
items: {
showSidePoints: {
type: "boolean",
ref: "props.showSidePoints",
label:"Show side-points",
defaultValue: false
}
}
},
settings: { uses: "settings" }
}
};
// ─── Initial properties (CONFIRM selection mode) ─────────────────────────
const initialProperties = {
selectionMode: "QUICK",
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
qInitialDataFetch: [{ qWidth: 3, qHeight: 1000 }]
},
props: {
showSidePoints: false
}
};
// ─── Paint/render function ─────────────────────────────────────────────────
async function paint($element, layout) {
// 1) ensure container
const divId = "plotly_" + layout.qInfo.qId;
if (!$element.find("#" + divId).length) {
$element.html(
`<div id="${divId}" class="plotly-violin-extension" style="width:100%;height:100%;"></div>`
);
}
const plotDiv = document.getElementById(divId);
// 2) load theme variables & styling
const app = qlik.currApp(this);
let vars = {}, axisCfg = {}, legendCfg = {}, palette = [];
let tickFont = { family: "", size: 12, color: "#000" },
lineColor = "#333", gridColor = "#444",
legendTitleFont = { family: "", size: 12, color: "#000" },
legendFont = { family: "", size: 12, color: "#000" };
try {
const theme = await app.theme.getApplied();
vars = theme.properties._variables || {};
axisCfg = theme.properties.object?.axis || {};
legendCfg = theme.properties.object?.legend || {};
const resolve = v =>
(typeof v === "string" && v.startsWith("@")) ? (vars[v] || v) : v;
// build palette @c1…@c12
for (let i = 1; i <= 12; i++) {
const c = vars[`@c${i}`];
if (c) palette.push(c);
}
// tick font
const lf = axisCfg.label?.name || {};
tickFont = {
family: resolve(lf.fontFamily) || "",
size: parseInt(resolve(lf.fontSize), 10) || 12,
color: resolve(lf.color) || "#000"
};
// axis line/grid colors
lineColor = resolve(axisCfg.line?.major?.color) || lineColor;
gridColor = resolve(axisCfg.line?.minor?.color) || gridColor;
// legend title font
const lt = legendCfg.title || {};
legendTitleFont = {
family: resolve(lt.fontFamily) || "",
size: parseInt(resolve(lt.fontSize), 10) || tickFont.size,
color: resolve(lt.color) || tickFont.color
};
// legend label font
const ll = legendCfg.label || {};
legendFont = {
family: resolve(ll.fontFamily) || legendTitleFont.family,
size: parseInt(resolve(ll.fontSize), 10) || legendTitleFont.size,
color: resolve(ll.color) || legendTitleFont.color
};
}
catch (e) {
console.warn("Theme load error:", e);
}
// 3) fetch all data pages
const hc = this.backendApi;
async function fetchAll(rowsPerPage = 1000) {
let top = 0, all = [];
while (true) {
const pages = await hc.getData([{
qTop: top, qLeft: 0,
qWidth: 3, qHeight: rowsPerPage
}]);
const mat = pages[0].qMatrix;
if (!mat.length) break;
all.push(...mat);
if (mat.length < rowsPerPage) break;
top += rowsPerPage;
}
return all;
}
const rows = await fetchAll();
// 4) group values by first dimension
const grouped = {}, elemMap = {};
rows.forEach(r => {
const cat = r[0].qText, val = r[2].qNum;
elemMap[cat] = r[0].qElemNumber;
(grouped[cat] = grouped[cat] || []).push(val);
});
// 5) build violin traces
const entries = Object.entries(grouped);
const traces = entries.map(([cat, vals], idx) => {
const themePrimary = layout.theme?.properties?.dataColors?.primaryColor;
const color = palette.length
? palette[idx % palette.length]
: themePrimary;
const t = {
type: "violin",
x: Array(vals.length).fill(cat),
y: vals,
name: cat,
box: { visible: true },
meanline: { visible: true },
hoverinfo: "none",
points: layout.props.showSidePoints ? "all" : false,
side: layout.props.showSidePoints ? "positive" : "both"
};
if (color) {
t.marker = { color, line: { color, width: 1 } };
t.line = { color };
}
return t;
});
// 6) axis titles
const xTitle = layout.qHyperCube.qDimensionInfo[0].qFallbackTitle;
const yTitle = layout.qHyperCube.qMeasureInfo[0].qFallbackTitle;
// 7) build responsive layout
const plotLayout = {
autosize: true,
margin: { t: 20, l: 80, r: 10, b: 60 },
paper_bgcolor: "transparent",
plot_bgcolor: "transparent",
violingap: 0.4,
violingroupgap:0.2,
xaxis: {
title: { text: xTitle, font: tickFont, standoff: 20 },
tickfont: tickFont,
linecolor: lineColor,
gridcolor: gridColor
},
yaxis: {
title: { text: yTitle, font: tickFont, standoff: 30 },
tickfont: tickFont,
linecolor: lineColor,
gridcolor: gridColor
},
legend: {
title: { font: legendTitleFont },
font: legendFont,
bgcolor:"rgba(0,0,0,0)"
}
};
// 😎 render
await Plotly.react(divId, traces, plotLayout, {
displayModeBar: false,
responsive: true
});
// 9) bind click → Qlik confirm‐mode selection
if (!plotDiv._clickBound) {
plotDiv.on("plotly_click", ev => {
const pts = ev.points || [];
if (pts.length) {
const cat = pts[0].x;
const qElem = elemMap[cat];
this.selectValues(0, [qElem], true);
}
});
plotDiv._clickBound = true;
}
}
// ─── Export extension object ───────────────────────────────────────────────
return {
definition,
initialProperties,
paint
};
});